Intersected beams stacking rebars issue

Hi All, @c.poupin

I’m trying, through the attached code, to create longitudinal and stirrup rebars for intersecting beams. At the beam intersections (supports), the longitudinal rebars should be stacked at the top and bottom according to the beam’s direction, i.e., whether the beam is in the principal or secondary direction.

I determine the beam direction by checking the following condition within the get_intersecting_end_beams_data function:

dir_flag = abs(direction.X) > abs(direction.Y)

When the code is executed, the longitudinal rebars are correctly generated and positioned at the top and bottom. However, there is a problem with the rebars in the secondary direction: they are misplaced and appear to overlap with or penetrate into the rebars in the principal direction, as shown in the image below.

I expected the secondary-direction rebars to be shifted further away from the principal-direction rebars so that the two layers are properly stacked.

I believe that I have correctly implemented the stacking logic in the curve_multiply_offset function, particularly in the following section:

    # -------------------------------------------------
    # stacking
    # -------------------------------------------------

    if dir_flag:

        top_stack = stirup_type.BarNominalDiameter + 0.5 * top_type.BarNominalDiameter

        btm_stack = stirup_type.BarNominalDiameter + 0.5 * btm_type.BarNominalDiameter

    else:

        top_stack = stirup_type.BarNominalDiameter + 1.5 * top_type.BarNominalDiameter
        
        btm_stack = stirup_type.BarNominalDiameter + 1.5 * btm_type.BarNominalDiameter

The idea is that the principal-direction rebars remain closer to the beam’s main reinforcement position, while the secondary-direction rebars are offset further to avoid interference with the principal-direction rebars.

However, the resulting geometry does not behave as expected. The secondary-direction rebars still appear to overlap or penetrate the principal-direction rebars.

This makes me wonder whether Revit is applying some rebar constraints, shape constraints, or automatic geometric adjustments after the rebars are created, which may be modifying their final position.

So my question is:

  • Does Revit automatically apply any constraints or geometric adjustments to rebars created using the Revit API that could affect their final position and how to deal with to get rebar rebars stacked as expected?

Please check the main code:

Intersected_beams_rebars
import clr
import sys
import System
import math
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


# collecting rebar type in the active document
rebar_types = (
    FilteredElementCollector(doc)
    .OfCategory(BuiltInCategory.OST_Rebar)
    .WhereElementIsElementType()
    .WherePasses(ElementClassFilter(RebarBarType))
    .ToElements()
)

# get desired diameter for beam's top rebar
top_bar_type = next((r for r in rebar_types if r.LookupParameter('Diamètre de barre').AsValueString() == "14 mm"), None)

# get desired diameter for beam's aditional top rebar
top_add_bar_type = next((r for r in rebar_types if r.LookupParameter('Diamètre de barre').AsValueString() == "12 mm"), None)

# get desired diameter for beam's bottom rebar
btm_bar_type = next((r for r in rebar_types if r.LookupParameter('Diamètre de barre').AsValueString() == "16 mm"), None)

# get desired diameter for beam's aditional bottom rebar
btm_add_bar_type = next((r for r in rebar_types if r.LookupParameter('Diamètre de barre').AsValueString() == "10 mm"), None)

# get desired diameter for beam's stirrups rebar
stirrup_type = next((r for r in rebar_types if r.LookupParameter('Diamètre de barre').AsValueString() == "6 mm"), None)

# collecting rebar hooks type in the active document
hook_types = list(FilteredElementCollector(doc).OfClass(RebarHookType).ToElements())

# getting standard hooks types from rebar hooks
standard_hook_types = [hk for hk in hook_types if hk.Style == RebarStyle.Standard]

# getting stirrup hooks type from rebar hooks
stirrup_hook_types = [hk for hk in hook_types if hk.Style == RebarStyle.StirrupTie]

# choosed hook type for longitudinal rebars
hook_90 = next((h for h in standard_hook_types if Element.Name.GetValue(h) == "Standard - 90 deg."), None)

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

# collecting beams cover BuiltInParameter
cover_faces = {
    "Top": BuiltInParameter.CLEAR_COVER_TOP,
    "Bottom": BuiltInParameter.CLEAR_COVER_BOTTOM,
    "Other": BuiltInParameter.CLEAR_COVER_OTHER
}

# collecting covers type in the active document
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

# getting beam's covers per face    
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

# getting beam's solid geometry
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

# getting the center of the face extracted from solid 
def get_face_center(face):

    bbox = face.GetBoundingBox()

    u_mid = (bbox.Min.U + bbox.Max.U) / 2.0
    v_mid = (bbox.Min.V + bbox.Max.V) / 2.0

    return face.Evaluate(
        UV(u_mid, v_mid)
    )

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

# Retrieving data from the ends of each intersected beam to generate 
#the main top and bottom rebar curve and concerned parameters to generate rebars.
def get_intersecting_end_beams_data(
        single_beam,
        all_beams,
        tol=1e-3):

    loc_curve = single_beam.Location.Curve
    
    normal = single_beam.FacingOrientation

    direction = loc_curve.Direction.Normalize()
    dir_flag = abs(direction.X) > abs(direction.Y)
    
    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 = None
    w_end = None

    new_start = None
    new_end = None

    start_side_cover = None
    end_side_cover = None

    # =====================================================
    # PART 1 → INTERSECTING BEAMS DATA
    # =====================================================

    for beam in all_beams:

        if beam.Id == single_beam.Id:
            continue

        other_curve = beam.Location.Curve

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

        # Beam width
        other_width = beam.Symbol.LookupParameter("b").AsDouble()

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

        proj1 = other_curve.Project(start)
        proj2 = other_curve.Project(end)

        # -------------------------------------------------
        # START CONNECTION
        # -------------------------------------------------

        if proj1.Distance < other_width and w_start is None:

            start_side_cover = other_side_cover

            v1 = start.Subtract(proj1.XYZPoint)
            d1 = v1.DotProduct(direction)

            if abs(d1) < other_width / 2 - tol:

                w_start = other_width / 2

                new_start = start.Add(
                    direction.Negate().Multiply(w_start - start_side_cover)
                )

            elif d1 > 0:

                w_start = other_width

                new_start = start.Add(
                    direction.Negate().Multiply(w_start - start_side_cover)
                )

            else:

                w_start = 0
                new_start = start.Add(direction.Multiply(start_side_cover))

        # -------------------------------------------------
        # END CONNECTION
        # -------------------------------------------------

        elif proj2.Distance < other_width and w_end is None:

            end_side_cover = other_side_cover

            v2 = end.Subtract(proj2.XYZPoint)
            d2 = v2.DotProduct(direction)

            if abs(d2) < other_width / 2 - tol:

                w_end = other_width / 2

                new_end = end.Add(
                    direction.Multiply(w_end - end_side_cover)
                )

            elif d2 > 0:

                w_end = 0
                new_end = end.Add(direction.Negate().Multiply(end_side_cover))

            else:

                w_end = other_width

                new_end = end.Add(
                    direction.Multiply(w_end - end_side_cover)
                )

    # -----------------------------------------------------
    # Corrected beam location curve
    # -----------------------------------------------------

    top_curve = Line.CreateBound(
        new_start,
        new_end
    )

    btm_curve = Line.CreateBound(
        new_end,
        new_start
    )

    return {

        "top_curve": top_curve,
        "btm_curve": btm_curve,

        "beam_width": w,
        "beam_height": h,
        
        "start_width": w_start,
        "end_width": w_end,
        "normal_vector": normal,

        "beam_top_cover": top_cover,
        "beam_btm_cover": btm_cover,
        "beam_side_cover": side_cover,

        "dir_flag": dir_flag
    }

# ordering a curveloop to apply a transform from the covers offset in the predefined order 
def ordering_curveloop(curveloop):
    
    new_loop = CurveLoop()
    
    edges = [c for c in curveloop]
    
    top_index = max(
        range(len(edges)),
        key=lambda i: (
            edges[i].GetEndPoint(0).Z +
            edges[i].GetEndPoint(1).Z
        ) / 2.0
    )
    
    ordered_edges = edges[top_index:] + edges[:top_index]
    
    [new_loop.Append(c) for c in ordered_edges]
    
    return new_loop
    
# generating the base curveloop for strirrup rebar by applying a transformation
# to the original extracted curveloop
def stirrup_curves(solid, intersection_data, tol = 1e-3):

    if not solid:

        return None

    direction = intersection_data["top_curve"].Direction
    
    start = intersection_data["top_curve"].GetEndPoint(0)
    
    w_start = intersection_data["start_width"]
    
    top_cover = intersection_data["beam_top_cover"]
    
    btm_cover = intersection_data["beam_btm_cover"]
    
    side_cover = intersection_data["beam_side_cover"]
    
    offset = None
    
    start_face = None

    for f in solid.Faces:


        if not isinstance(
                f,
                PlanarFace):

            continue


        norm = f.FaceNormal.Normalize()

        # Face oriented toward beam start
        if norm.IsAlmostEqualTo(
                direction.Negate()):

            start_face = f

    if start_face is not None:

        curveloop = (
            start_face
            .GetEdgesAsCurveLoops()[0]
        )

        new_loop = ordering_curveloop(curveloop)
        new_loop = CurveLoop.CreateViaOffset(new_loop, List[System.Double]([top_cover, side_cover, btm_cover, side_cover]), direction)
        
        center = get_face_center(start_face)
        
        if  center.Subtract(start).DotProduct(direction) < 0:
            offset = 2 * w_start      
        
        elif center.Subtract(start).DotProduct(direction) < w_start - tol:
            offset = w_start

        else:
            offset = 0   
                
        translation_vector = direction.Multiply(offset)

        trans = Transform.CreateTranslation(translation_vector)
        
        stirrup_curveloop = CurveLoop.CreateViaTransform(new_loop, trans)        
        
    return stirrup_curveloop     
        

def curve_multiply_offset(curve, beam_data, stirup_type, top_type, btm_type, is_top):
    
    h = beam_data["beam_height"]
    w = beam_data["beam_width"]
    norm = beam_data["normal_vector"]
    top_cover = beam_data["beam_top_cover"]
    btm_cover = beam_data["beam_btm_cover"]
    side_cover = beam_data["beam_side_cover"]
    dir_flag = beam_data["dir_flag"]

    # -------------------------------------------------
    # stacking
    # -------------------------------------------------

    if dir_flag:

        top_stack = stirup_type.BarNominalDiameter + 0.5 * top_type.BarNominalDiameter

        btm_stack = stirup_type.BarNominalDiameter + 0.5 * btm_type.BarNominalDiameter

    else:

        top_stack = stirup_type.BarNominalDiameter + 1.5 * top_type.BarNominalDiameter
        
        btm_stack = stirup_type.BarNominalDiameter + 1.5 * btm_type.BarNominalDiameter


    # -------------------------------------------------
    # independent TOP transform
    # -------------------------------------------------

    top_transform = Transform.CreateTranslation(

        XYZ(0, 0, -1).Multiply(

            top_cover + top_stack

        ).Add(

            norm.Multiply(
                w /2 - side_cover - 0.5 * stirup_type.BarNominalDiameter
            )
        )
    )

    # -------------------------------------------------
    # independent BOTTOM transform
    # -------------------------------------------------

    btm_transform = Transform.CreateTranslation(

        XYZ(0, 0, -1).Multiply(

            h - (
                btm_cover + btm_stack
            )

        ).Add(

            norm.Multiply(
                w /2 - side_cover
            )
        )
    )

    # -------------------------------------------------
    # apply proper transform
    # -------------------------------------------------

    transform = (
        top_transform
        if is_top
        else btm_transform
    )

    transformed_curve = curve.CreateTransformed(transform)


    return transformed_curve
    
# function to create stirrups rebars     
def create_stirrups_rebars(
        beam,
        stirrup_curveloop,
        beam_data,
        stirrup_type,
        hook_type, 
        base_cover, 
        unit_length,
        spacing1,
        spacing2):

    
    top_curve = beam_data["top_curve"]
    direction = beam_data["top_curve"].Direction
    w_start = beam_data["start_width"]
    w_end = beam_data["end_width"]
    
    base_cover = base_cover / 0.3048
    
    unit_length = unit_length / 0.3048
    
    spacing1 = spacing1 / 0.3048
    
    spacing2 = spacing2 / 0.3048

    # -----------------------------------------------------
    # Base loop
    # -----------------------------------------------------
    stirrup_rebars = []
    stirrup_curveloops = []
        
    # first transform for the base curveloop
    base_loop = CurveLoop.CreateViaTransform(
        stirrup_curveloop,
        Transform.CreateTranslation(
            direction.Multiply(base_cover)
        )
    )
    stirrup_curveloops.append(base_loop)
    
    # ---------------------------------------------
    # Rebar distribution length
    # ---------------------------------------------
    # main beam clear length   
    length = top_curve.Length - 2 * (w_start + w_end)
    
    # 2nd distribution length   
    length2 = length -  2 * (unit_length + base_cover)
    
    # 2nd transform for the base curveloop    
    transform1 = Transform.CreateTranslation(
        direction.Multiply(unit_length + spacing1)
    )
        
    # 3rd transform for the base curveloop
    transform2 = Transform.CreateTranslation(
        direction.Multiply(length - base_cover)
    )    
        
    
    segment_loop1 = CurveLoop.CreateViaTransform(
        base_loop,
        transform1
    )
    
    stirrup_curveloops.append(segment_loop1)
        
    segment_loop2 = CurveLoop.CreateViaTransform(
        base_loop,
        transform2
    )
        
    stirrup_curveloops.append(segment_loop2)
    

    # ---------------------------------------------
    # Curves
    # ---------------------------------------------
    
    stirup_curves1 = List[Curve](
        [c for c in base_loop]
    )
    
    stirup_curves2 = List[Curve](
        [c for c in segment_loop1]
    )
    
    stirup_curves3 = List[Curve](
        [c for c in segment_loop2]
    )
    
    # ---------------------------------------------
    # Create rebar
    # ---------------------------------------------
    
    rebar1 = Rebar.CreateFromCurves(
        doc,
        RebarStyle.StirrupTie,
        stirrup_type,
        hook_type,
        hook_type,
        beam,
        direction,
        stirup_curves1,
        RebarHookOrientation.Right,
        RebarHookOrientation.Right,
        True,
        True
    )
    
    # stirrup rebars for the first segment "unit_length"
    rebar1.GetShapeDrivenAccessor().SetLayoutAsMaximumSpacing(
        spacing1,
        unit_length,
        True,
        True,
        True
    )
    
    stirrup_rebars.append(rebar1)
    
    # stirrup rebars for the second segment "length2"
    rebar2 = Rebar.CreateFromCurves(
        doc,
        RebarStyle.StirrupTie,
        stirrup_type,
        hook_type,
        hook_type,
        beam,
        direction,
        stirup_curves2,
        RebarHookOrientation.Right,
        RebarHookOrientation.Right,
        True,
        True
    )
    
    rebar2.GetShapeDrivenAccessor().SetLayoutAsMaximumSpacing(
        spacing2,
        length2,
        True,
        False,
        False
    )
    
    stirrup_rebars.append(rebar2)
           
    # stirrup rebars for the third segment "unit_length"
    rebar3 = Rebar.CreateFromCurves(
        doc,
        RebarStyle.StirrupTie,
        stirrup_type,
        hook_type,
        hook_type,
        beam,
        direction.Negate(),
        stirup_curves3,
        RebarHookOrientation.Left,
        RebarHookOrientation.Left,
        True,
        True
    )
    
    rebar3.GetShapeDrivenAccessor().SetLayoutAsMaximumSpacing(
        spacing1,
        unit_length,
        True,
        True,
        True
    )
    
    stirrup_rebars.append(rebar3)
        
    return stirrup_rebars


# function to create longitudinal "top" and "bottom" rebars
def create_longitudinal_rebars(beam, curvlst, beam_data, stirrup_type, top_bar_type, btm_bar_type, hook_type,n):
    
    normal = beam_data["normal_vector"]
    w = beam_data["beam_width"]
    side_cover = beam_data["beam_side_cover"]
    top_curve = curvlst[0]
    btm_curve = curvlst[1]
    top_start = top_curve.GetEndPoint(0).Add(top_curve.Direction.Multiply(2 * side_cover))
    top_end = top_curve.GetEndPoint(1).Add(top_curve.Direction.Negate().Multiply(2 * side_cover))
    new_top_curve = Line.CreateBound(top_start, top_end)

    btm_start = btm_curve.GetEndPoint(0).Add(btm_curve.Direction.Multiply(2 * side_cover))
    btm_end = btm_curve.GetEndPoint(1).Add(btm_curve.Direction.Negate().Multiply(2 * side_cover))
    new_top_curve = Line.CreateBound(top_start, top_end)    
    new_btm_curve = Line.CreateBound(btm_start, btm_end)   
    top_curv = List[Curve]()
    btm_curv = List[Curve]()
    
    top_curv.Add(new_top_curve)
    
    btm_curv.Add(new_btm_curve)      
    
    top_rebar = Rebar.CreateFromCurves(
                doc,
                RebarStyle.Standard,
                top_bar_type,
                hook_type,
                hook_type,
                beam,
                normal.Negate(),
                top_curv,
                RebarHookOrientation.Right,
                RebarHookOrientation.Right,
                True, True
                )
    top_rebar.GetShapeDrivenAccessor().SetLayoutAsFixedNumber(
        n, w - 2 * (stirrup_type.BarNominalDiameter + side_cover), True, True, True
    )

    btm_rebar = Rebar.CreateFromCurves(
                doc,
                RebarStyle.Standard,
                btm_bar_type,
                hook_type,
                hook_type,
                beam,
                normal.Negate(),
                btm_curv,
                RebarHookOrientation.Right,
                RebarHookOrientation.Right,
                True, True
                )
    btm_rebar.GetShapeDrivenAccessor().SetLayoutAsFixedNumber(
        n, w - 2 * side_cover, True, True, True
    )

    return top_rebar, btm_rebar

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

with Transaction(doc, "create stirrup rebars") as t:
    t.Start()
    for beam in beams:
        beam_curvelist = []
        #print(beam.Id)
        solid = get_solid(beam)
        beam_data = get_intersecting_end_beams_data(
            beam,
            beams)
        top_curve = beam_data["top_curve"]
        btm_curve = beam_data["btm_curve"]
        top_curve = curve_multiply_offset(top_curve, beam_data, stirrup_type, top_bar_type, btm_bar_type, True)
        btm_curve = curve_multiply_offset(btm_curve, beam_data, stirrup_type, top_bar_type, btm_bar_type, False)
        beam_curvelist.append(top_curve)
        beam_curvelist.append(btm_curve)    
        stirrup_curveloop = stirrup_curves(solid, beam_data)
        
        stirrup_rebar = create_stirrups_rebars(
            beam,
            stirrup_curveloop,
            beam_data,
            stirrup_type,
            hook_135, 
            0.05, 
            1.00,
            0.10,
            0.15)
        stirrups_rebar.append(stirrup_rebar)    
        
        rebar = create_longitudinal_rebars(beam, beam_curvelist, beam_data, stirrup_type, top_bar_type, btm_bar_type, hook_90,3)    
        rebars.append(stirrup_rebar)    
        
            
    t.Commit()
    
OUT = stirrup_rebar, rebars

here my used model:
intersected_beams.rvt (2.5 MB)

Any guidance to solve this issue would be greatly appreciated.

Thanks.

After a few tests, I realize that the SetLayoutAsFixedNumber() method prevents alignment with the top curve; I simply replaced + by -

I don’t know if the bug comes from there.

Hi @c.poupin,

Sorry for this late feedback.
Indeed, I saw your reply in time; however, I was testing another approach, where I discovered that my issue was due to rebar constraints and it was solved Here

Thanks