Stacking rebars issue within intersected beams

Stacking rebars issue within intersected beams

REDO10
Collaborator Collaborator
178 Views
3 Replies
Message 1 of 4

Stacking rebars issue within intersected beams

REDO10
Collaborator
Collaborator

Hi All, @longt61 , @Radwan-Almsora 

I’m using Dynamo inside Revit 2025 and 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.

stacked rebars.png

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 here:

 

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

 

Please check my attached model

 

Any guidance to solve this issue would be greatly appreciated.

Thanks.

0 Likes
179 Views
3 Replies
Replies (3)
Message 2 of 4

REDO10
Collaborator
Collaborator

No one can help me?

0 Likes
Message 3 of 4

sharafutdinov_di_dev
Contributor
Contributor

@REDO10, yes. Revit moves the bars after creation, and your stacking math is correct. I ran your script on your attached model (Revit 2026.5, build 26.5.0.55; I could not test 2025 on my machine) and measured every longitudinal bar before and after commit.

What happens. CreateFromCurves gives a shape-driven bar default constraints. For the straight segment of your longitudinal bars, Revit picks ToOtherRebar with the bend of the beam's own stirrup as the target. After regeneration it snaps the bar there and discards the Z you calculated:

BAR beam=459457 dir_flag=True  TOP requested Z=-38.0 mm actual Z=-43.5 mm
BAR beam=459530 dir_flag=False TOP requested Z=-52.0 mm actual Z=-43.5 mm
CONSTRAINT handle=Bar Segment 1 type=Edge constraint=ToOtherRebar target=stirrup (BarBend)

Both directions end up on the same stirrup level. At the support, the top axes are 0.0 mm apart where you need 14 mm (full overlap). The bottom axes are 11.5 mm apart where you need 16 mm, so the bars penetrate each other by 4.5 mm. That matches your screenshot. IsRebarConstrainedPlacementEnabled is already False in your model and makes no difference. Creating the longitudinal bars before the stirrups doesn't help either: the same constraints show up as soon as the stirrups exist.

Fix. After everything is created, call Regenerate once, then give each longitudinal bar an explicit preferred ToCover constraint with your offset. Note that SetDistanceToTargetCover is measured to the bar surface, not the axis, so subtract half the diameter:

def set_vertical_cover_constraint(rebar, beam, stirrup_type, bar_type, dir_flag):
    manager = rebar.GetRebarConstraintsManager()
    multiplier = 0.5 if dir_flag else 1.5
    axis_from_cover = stirrup_type.BarNominalDiameter + multiplier * bar_type.BarNominalDiameter
    clear_offset = axis_from_cover - 0.5 * bar_type.BarNominalDiameter

    for handle in manager.GetAllHandles():
        if str(handle.GetHandleType()) != "Edge":
            continue
        candidates = [c for c in manager.GetConstraintCandidatesForHandle(handle, beam.Id)
                      if c.IsToCover()]
        if not candidates:
            raise Exception("No ToCover candidate for longitudinal rebar")
        candidate = min(candidates, key=lambda c: abs(c.GetDistanceToTargetCover()))
        sign = 1.0 if candidate.GetDistanceToTargetCover() >= 0 else -1.0
        candidate.SetDistanceToTargetCover(sign * clear_offset)
        manager.SetPreferredConstraint(candidate)

In your main loop, collect (rebar, beam, bar_type, dir_flag) for every top and bottom bar. After the loop, still inside the same transaction:

doc.Regenerate()
for rebar, beam, bar_type, dir_flag in longitudinal:
    set_vertical_cover_constraint(rebar, beam, stirrup_type, bar_type, dir_flag)

Result on your model, all 8 longitudinal bars:

layer              requested Z   actual Z before   actual Z after
principal top        -38.0          -43.5             -38.0
secondary top        -52.0          -43.5             -52.0
principal bottom    -561.0         -556.5            -561.0
secondary bottom    -545.0         -545.0            -545.0

axis distance at the support   before    after   required
top                            0.0 mm   14.0 mm   14.0 mm
bottom                        11.5 mm   16.0 mm   16.0 mm

Now the bars touch without overlapping. If your detailing rules require a clear gap between the layers, add it to the secondary offset.

Two side notes. First, I tested only your model: orthogonal straight beams at the same level. Sloped, curved or skewed beams were not tested. Second, selecting bar types by the localized parameter name 'Diamètre de barre' breaks on any non-French Revit. RebarBarType.BarNominalDiameter (or BarModelDiameter) works in every language.

Dinar Sharafutdinov · BIM coordinator · Revit API · Belgrade
Revit Model MCP   GitHub   LinkedIn   sharafutdinov.online
Message 4 of 4

REDO10
Collaborator
Collaborator

Hi @sharafutdinov_di_dev 

First of all, sorry for the late feedback. I was traveling and didn’t have the opportunity to see your reply earlier.

You explained very clearly and in detail how Revit constrains rebars after they are created.

I tested your helper function set_vertical_cover_constraint() in my code, and it works exactly as expected as you can see in the image below

 

stacked_rebars.png

However, the main goal of my code is to create overlapped rebars for intersecting beams, splitting the rebars whenever their length exceeds 12.00 m. Depending on the beam length, three different rebar shapes can be generated and used: L-shaped, U-shaped, or straight rebars, as shown in the code below.

Considering the attached model, where the intersecting beams exceed 12.00 m in length, the overlapped rebars are automatically generated. However, I encounter an issue when applying your set_vertical_cover_constraint() function, especially for the secondary beam (dir_flag == False).

In this case, the first segment of the top rebar is not stacked correctly and ends up outside the stirrup!....how to fix this issue?

 

stacking issue.png

 

 

Here my updated code and sorry if it's too long!!

 

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.BarNominalDiameter * 304.8 == 14.00), None)

# get desired diameter for beam's aditional top rebar
top_add_bar_type = next((r for r in rebar_types if r.BarNominalDiameter * 304.8 == 12.00), None)

# get desired diameter for beam's bottom rebar
btm_bar_type = next((r for r in rebar_types if r.BarNominalDiameter * 304.8 == 16.00), None)

# get desired diameter for beam's aditional bottom rebar
btm_add_bar_type = next((r for r in rebar_types if r.BarNominalDiameter * 304.8 == 14.00), None)

# get desired diameter for beam's stirrups rebar
stirrup_type = next((r for r in rebar_types if r.BarNominalDiameter * 304.8 == 6.00), 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())

# RollBack transaction to return uncut solid geometry for intersected beams 
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):
    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

# setting new covers per beam faces 
@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")

# 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 beams 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

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

# getting center for targeted face from solid faces
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
        )
    )

# ordering curveloop to apply transform according to predifined offsets
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

# creating a curveloop when dealing with skewed beam 
def create_virtual_section(
        solid,
        start,
        direction,
        top_cover, 
        btm_cover,
        side_cover):

    # Horizontal direction perpendicular to beam direction
    section_y = XYZ.BasisZ.CrossProduct(direction)

    # Second perpendicular direction
    section_z = direction.CrossProduct(section_y)

    y_values = []
    z_values = []


    # Use vertices from all edges
    for edge in solid.Edges:

        curve = edge.AsCurve()

        p0 = curve.GetEndPoint(0)
        p1 = curve.GetEndPoint(1)


        # Coordinate along section_y
        y0 = p0.Subtract(
            start
        ).DotProduct(
            section_y
        )

        y1 = p1.Subtract(
            start
        ).DotProduct(
            section_y
        )


        # Coordinate along section_z
        z0 = p0.Subtract(
            start
        ).DotProduct(
            section_z
        )

        z1 = p1.Subtract(
            start
        ).DotProduct(
            section_z
        )


        y_values.append(y0)
        y_values.append(y1)

        z_values.append(z0)
        z_values.append(z1)


    # No usable geometry
    if not y_values or not z_values:

        return None


    # --------------------------------------------------------
    # Get actual section extents
    # --------------------------------------------------------

    y_min = min(y_values)
    y_max = max(y_values)

    z_min = min(z_values)
    z_max = max(z_values)


    # --------------------------------------------------------
    # Create the four corners of the virtual section
    #
    # The section plane passes through start and is
    # perpendicular to beam_dir.
    # --------------------------------------------------------

    p1 = (
        start
        .Add(
            section_y.Multiply(y_min)
        )
        .Add(
            section_z.Multiply(z_max)
        )
    )


    p2 = (
        start
        .Add(
            section_y.Multiply(y_max)
        )
        .Add(
            section_z.Multiply(z_max)
        )
    )


    p3 = (
        start
        .Add(
            section_y.Multiply(y_max)
        )
        .Add(
            section_z.Multiply(z_min)
        )
    )


    p4 = (
        start
        .Add(
            section_y.Multiply(y_min)
        )
        .Add(
            section_z.Multiply(z_min)
        )
    )


    # --------------------------------------------------------
    # Create closed CurveLoop
    # --------------------------------------------------------

    new_loop = CurveLoop()


    new_loop.Append(
        Line.CreateBound(
            p1,
            p2
        )
    )


    new_loop.Append(
        Line.CreateBound(
            p2,
            p3
        )
    )


    new_loop.Append(
        Line.CreateBound(
            p3,
            p4
        )
    )


    new_loop.Append(
        Line.CreateBound(
            p4,
            p1
        )
    )
    
    new_loop = CurveLoop.CreateViaOffset(new_loop, List[System.Double]([top_cover, side_cover, btm_cover, side_cover]), direction)


    return new_loop

# generating the base curveloop for strirrup rebar by applying a transformation
# to the original extracted curveloop
def stirrup_curves(solid, beam_data, tol = 1e-3):

    if not solid:

        return None
    top_cover = beam_data["beam_top_cover"]
    btm_cover = beam_data["beam_btm_cover"]
    side_cover = beam_data["beam_side_cover"]
    top_curve = beam_data["top_curve"]
    beam_length = top_curve.Length
    start = top_curve.GetEndPoint(0)
    end = top_curve.GetEndPoint(1)
    direction = top_curve.Direction
    d0 = beam_data["column_dimensions"][0]
    d1 = beam_data["column_dimensions"][-1]

 
    start_face = None

    opposite_face = None
    stirrup_curveloop = None
    offset = 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


        # Face oriented toward beam end
        elif norm.IsAlmostEqualTo(
                direction):

            opposite_face = f


    # ========================================================
    # CASE 1
    #
    # A real perpendicular face exists at the beam start.
    # ========================================================

    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 = d0        
        
        elif center.Subtract(start).DotProduct(direction) < d0 /2 - tol:
            offset = d0 /2

        else:
            offset = 0   
                
        #print("center is: {}".format(center.ToPoint()))

        translation_vector = direction.Multiply(offset)

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


    # ========================================================
    # CASE 2
    #
    # No start face, but the opposite perpendicular face
    # exists.
    # ========================================================

    elif opposite_face is not None:

        curveloop = opposite_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(opposite_face)
        
        if d1 - center.Subtract(end).DotProduct(direction) < tol:
            offset = beam_length - d0
        elif d0/2 - center.Subtract(end).DotProduct(direction) < tol:
            offset = beam_length - d1/2 - d0
        else:
            offset = beam_length - d0 - d1
    
        translation_vector = direction.Negate().Multiply(offset)


        trans = Transform.CreateTranslation(translation_vector)


        stirrup_curveloop = new_loop



    # ========================================================
    # CASE 3
    #
    # No perpendicular face exists.
    # ========================================================

    else:

        virtual_loop = create_virtual_section(solid, new_start, direction)
        #stirrup_curveloop = virtual_loop
        center = get_face_center(virtual_loop)

        translation_vector = center.Add(direction.Multiply(d0))

        trans = Transform.CreateTranslation(translation_vector)


        stirrup_curveloop = CurveLoop.CreateViaTransform(virtual_loop, trans)
        


    return stirrup_curveloop, center

# returning the base curveloop for strirrup rebar 
@decoTransaction(commit = False)
def get_beam_stirrup_curveloop(beam, beam_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()

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

    stirrup_curveloop = stirrup_curves(solid, beam_data)

    return stirrup_curveloop

# Retrieving data from the ends of each intersected beam to generate the main rebar curve.
def get_intersecting_end_beams_data(
        single_beam,
        all_beams,
        all_columns,
        top_ext_factor=1.0/5.0,
        btm_ext_factor=1.0/10.0,
        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
    )

    # =====================================================
    # PART 2 → COLUMN DIMENSIONS ON CURRENT BEAM AXIS
    # =====================================================

    column_dimension = []
    column_location = []

    for col in all_columns:

        loc = col.Location

        if not isinstance(loc, LocationPoint):
            continue

        pt = loc.Point

        # Check if column lies on beam axis
        proj = top_curve.Project(pt)

        if proj is None:
            continue

        if proj.Distance > tol:
            continue

        # -------------------------------------------------
        # Column local axes
        # -------------------------------------------------

        axis_h = col.FacingOrientation.Normalize()
        axis_b = col.HandOrientation.Normalize()

        cross_h = direction.CrossProduct(axis_h).GetLength()
        cross_b = direction.CrossProduct(axis_b).GetLength()

        # -------------------------------------------------
        # Column dimensions
        # -------------------------------------------------

        h_val = col.Symbol.LookupParameter("h").AsDouble()
        b_val = col.Symbol.LookupParameter("b").AsDouble()

        # Determine dimension aligned with beam direction
        if cross_h < cross_b:
            aligned_dim = h_val
        else:
            aligned_dim = b_val

        column_dimension.append(aligned_dim)
        column_location.append(pt)

    # =====================================================
    # SORT COLUMNS ALONG BEAM DIRECTION
    # =====================================================

    data = zip(
        column_location,
        column_dimension
    )

    data = sorted(
        data,
        key=lambda x:
            x[0]
            .Subtract(start)
            .DotProduct(direction)
    )

    column_locations = [
        x[0]
        for x in data
    ]

    column_dimensions = [
        x[1]
        for x in data
    ]
    
     

    # =====================================================
    # PART 3 → SEGMENTS + ADDITIONAL REBAR CURVES
    # =====================================================

    clear_lengths = []
    segments = []
    segment_data = []

    # -----------------------------------------------------
    # Additional longitudinal curves
    #
    # top    = one curve at each support
    # bottom = one curve in each beam segment
    # -----------------------------------------------------

    top_additional_curves = []
    btm_additional_curves = []

    count = len(column_locations)

    # -----------------------------------------------------
    # Reference point:
    # beginning of first column
    # -----------------------------------------------------

    p0 = column_locations[0].Add(
        direction.Multiply(
            column_dimensions[0] / 2
        )
    )

    k = 0

    previous_distance = 0
    previous_top_extension = None
    previous_btm_extension = None
    
    while k < count - 1:

        start_pt = column_locations[k]
        end_pt = column_locations[k + 1]

        # -------------------------------------------------
        # COLUMN DIMENSIONS
        # -------------------------------------------------

        start_dim = column_dimensions[k]
        end_dim = column_dimensions[k + 1]

        # -------------------------------------------------
        # CLEAR LENGTH OF CURRENT BEAM SEGMENT
        # -------------------------------------------------

        clear_length = (
            end_pt.Add(
                direction.Negate().Multiply(
                    end_dim / 2
                )
            )
            .Subtract(
                start_pt.Add(
                    direction.Multiply(
                        start_dim / 2
                    )
                )
            )
            .DotProduct(direction)
        )

        # -------------------------------------------------
        # ADDITIONAL REBAR EXTENSIONS
        # -------------------------------------------------

        top_extension = (
            clear_length * top_ext_factor
        )

        btm_extension = (
            clear_length * btm_ext_factor
        )

        # -------------------------------------------------
        # ABSOLUTE DISTANCE TO START OF NEXT SEGMENT
        # -------------------------------------------------

        distance = (
            end_dim / 2
            + end_pt.Subtract(p0).DotProduct(direction)
        )

        # -------------------------------------------------
        # STORE CLEAR LENGTH
        # -------------------------------------------------

        clear_lengths.append(
            clear_length
        )

        # -------------------------------------------------
        # STORE DISTANCE TO FOLLOWING SEGMENT
        # -------------------------------------------------

        segments.append(
            distance
        )

        # =================================================
        # TOP ADDITIONAL REBAR
        # =================================================

        if k == 0:

            # -------------------------------------------------
            # FIRST SUPPORT
            #
            # From slightly inside the first column
            # to the first top extension
            # -------------------------------------------------

            start_offset = (
                start_dim / 2
                - 0.05 / 0.3048
            )

            top_start_pt = start_pt.Add(
                direction.Negate().Multiply(
                    start_offset
                )
            )

            top_end_pt = start_pt.Add(
                direction.Multiply(
                    start_dim / 2
                    + top_extension
                )
            )

            top_add_curve = Line.CreateBound(
                top_start_pt,
                top_end_pt
            )

            top_additional_curves.append(
                top_add_curve
            )

            # First segment starts from distance 0
            start_distance = 0

        else:

            # -------------------------------------------------
            # MIDDLE SUPPORT
            #
            # Top bar extends into:
            #
            # previous segment
            # +
            # current support width
            # +
            # current segment
            # -------------------------------------------------

            start_distance = previous_distance

            top_length = (
                previous_top_extension
                + start_dim
                + top_extension
            )

            top_start_pt = start_pt.Add(
                direction.Negate().Multiply(
                    top_length / 2
                )
            )

            top_end_pt = start_pt.Add(
                direction.Multiply(
                    top_length / 2
                )
            )

            top_add_curve = Line.CreateBound(
                top_start_pt,
                top_end_pt
            )

            top_additional_curves.append(
                top_add_curve
            )

        # =================================================
        # BOTTOM ADDITIONAL REBAR
        # =================================================

        btm_start_pt = start_pt.Add(
            direction.Multiply(
                btm_extension + start_dim / 2
            )
        )

        btm_end_pt = end_pt.Add(
            direction.Negate().Multiply(
                btm_extension + end_dim / 2
            )
        )

        btm_add_curve = Line.CreateBound(
            btm_start_pt,
            btm_end_pt
        )

        btm_additional_curves.append(
            btm_add_curve
        )

        # =================================================
        # SEGMENT DATA
        # =================================================

        segment_data.append(
            (
                start_distance,
                clear_length
            )
        )

        # -------------------------------------------------
        # Save values for next segment
        # -------------------------------------------------

        previous_distance = distance

        previous_top_extension = top_extension
        previous_btm_extension = btm_extension

        # =================================================
        # LAST SUPPORT
        # =================================================

        if k == count - 2:

            last_center = column_locations[-1]
            last_dim = column_dimensions[-1]

            # -------------------------------------------------
            # Last support top bar
            # -------------------------------------------------

            top_start_pt = last_center.Add(
                direction.Negate().Multiply(
                    last_dim / 2
                    + previous_top_extension
                )
            )

            top_end_pt = last_center.Add(
                direction.Multiply(
                    last_dim / 2
                    - 0.05 / 0.3048
                )
            )

            top_add_curve = Line.CreateBound(
                top_start_pt,
                top_end_pt
            )

            top_additional_curves.append(
                top_add_curve
            )

        k += 1

    # =====================================================
    # RETURN
    # =====================================================

    return {

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

        "beam_width": w,
        "beam_height": h,
        
        "normal_vector": normal,

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

        "dir_flag": dir_flag,
        
        "column_dimensions": column_dimensions,

        "segment_data": segment_data,

        "additional_curves": {
            "top": top_additional_curves,
            "bottom": btm_additional_curves
        }
    }

# splitting top/bottom curve for each beam to not exceed 12.00m of length for longitudinal rebar
# and generating depending on the case "L-shaped" rebar, "U-shaped" rebar or "straight shaped" rebar 
def split_curve(curve, bar_type, hook_type):
    # the main curve length
    total_length = curve.Length
    # bar diameter
    d = bar_type.BarModelDiameter
    # standard hook Bend Diameter
    D = bar_type.StandardHookBendDiameter
    # hook extension length
    l = hook_type.GetHookExtensionLength(bar_type)
    # hook total length
    b = l + D / 2 + d
    # referance length to generate a staright bar without hooks.  
    unit_length = 12 / 0.3048
    # referance length to generate an L-shapped rebar.  
    unit_length1 = (12 / 0.3048) - b - ((D + d) * math.pi / 4) + D + 2 * d
    print(unit_length1 * 0.3048)
    # referance length to generate an U-shapped rebar.
    unit_length2 = (12 / 0.3048) - 2 * b - ((D + d) * math.pi / 2) + 2 * D + 4 * d
    # overlap distance
    lr = 50 * d
    # tolerence distance needed to forcing creation of an L-shapped rebar with 12.00m long
    tol_near_full = 0.15 / 0.3048
    # referance length to exactely generate two overlaped L-shapped rebar with 12.00m long
    ref_length = 2 * unit_length1 - lr
    direction = curve.Direction
    curves = []

    start = curve.GetEndPoint(0)
    end = curve.GetEndPoint(1)
    k = 0
    n = int((total_length-unit_length1-lr) // unit_length)
    
    # Case 1: Single U-shaped rebar
    if round(total_length, 3) == round(unit_length2, 3):
        curves.append(curve)

    # Case 2: Two overlapping L-shaped rebars
    elif round(unit_length2, 3) < round(total_length, 3) <= round(ref_length, 3):
        if round(total_length, 3) <= round(unit_length1, 3):
            end1 = start + direction.Multiply(total_length - tol_near_full)
            start2 = end1 - direction.Multiply(lr)  
            new_line1 = Line.CreateBound(start, end1)
            new_line2 = Line.CreateBound(start2, end)
            curves.append(new_line1)
            curves.append(new_line2)
            
        else:  
            if round(ref_length, 3) == round(total_length, 3):
                end1 = start + direction.Multiply(unit_length1)
                start2 = end1 - direction.Multiply(lr)
                new_line1 = Line.CreateBound(start, end1)
                new_line2 = Line.CreateBound(start2, end)
                curves.append(new_line1)
                curves.append(new_line2)
                
            elif round(ref_length - total_length, 3) <= round(tol_near_full, 3):
                end1 = start + direction.Multiply(unit_length1)
                start2 = end - direction.Multiply(unit_length1)
                new_line1 = Line.CreateBound(start, end1)
                new_line2 = Line.CreateBound(start2, end)
                curves.append(new_line1)
                curves.append(new_line2)
                
            else:
                end1 = start + direction.Multiply(unit_length1)
                start2 = end1 - direction.Multiply(lr)
                new_line1 = Line.CreateBound(start, end1)
                new_line2 = Line.CreateBound(start2, end)
                curves.append(new_line1)
                curves.append(new_line2)
    
    # Case 3: Multiple overlapping rebars
    else:
        end1 = start.Add(direction.Multiply(unit_length1))
        new_line1 = Line.CreateBound(start, end1)
        start2 = start.Add(direction.Multiply(unit_length1 - lr))
        curves.append(new_line1)
        cumulative_length = unit_length1
        
        while k < n+1:
            end2 = start2.Add(direction.Multiply(unit_length))
            is_full_length = False
            remaining_length = curve.GetEndPoint(1).DistanceTo(end2)
            cumulative_length += (unit_length - lr)
            
            if round(cumulative_length - total_length, 3 ) >= 0:
                is_full_length = True
                excess = cumulative_length - total_length + d + D/2
                end2 = start2.Add(direction.Multiply(unit_length - excess))
            else:
                end2 = end2
            new_line2 = Line.CreateBound(start2, end2)
            curves.append(new_line2)
            k += 1
            next_start = start2.Add(direction.Multiply(unit_length - lr))
            
            if remaining_length <= unit_length1-lr:
                if is_full_length == True:
                    next_start = next_start.Add(direction.Negate().Multiply(excess))
                    remaining_curve = Line.CreateBound(next_start, curve.GetEndPoint(1))
                    curves.append(remaining_curve)
                else:
                    remaining_curve = Line.CreateBound(next_start, curve.GetEndPoint(1))
                    curves.append(remaining_curve)
                break
            start2 = next_start

    return curves
        


# transform function to create top and bottom rebar curve for each beam

def curve_multiply_offset(curves_list, beam_data, stirup_diam, 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"]

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

    top_transform = Transform.CreateTranslation(

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

            top_cover

        ).Add(

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

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

    btm_transform = Transform.CreateTranslation(

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

            h - (
                btm_cover
            )

        ).Add(

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

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

    transform = (
        top_transform
        if is_top
        else btm_transform
    )

    transformed_curves = []

    for c in curves_list:

        transformed_curves.append(

            c.CreateTransformed(transform)

        )

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

    direction = beam_data["top_curve"].Direction

    segment_data = beam_data["segment_data"]

    base_cover = base_cover / 0.3048
    
    unit_length = unit_length / 0.3048
    
    spacing1 = spacing1 / 0.3048
    
    spacing2 = spacing2 / 0.3048

    # -----------------------------------------------------
    # Base loop
    # -----------------------------------------------------
    
    base_loop = CurveLoop.CreateViaTransform(
        stirrup_curveloop,
        Transform.CreateTranslation(
            direction.Multiply(base_cover)
        )
    )
    
    stirrup_rebars = []
    
    # -----------------------------------------------------
    # One rebar distribution per beam segment
    # -----------------------------------------------------
    
    for start_distance, clear_length in segment_data:
        
        # ---------------------------------------------
        # Rebar distribution length
        # ---------------------------------------------       
        length1 = clear_length - unit_length
        
        length2 = clear_length - 2 * unit_length
        
        transform1 = Transform.CreateTranslation(
            direction.Multiply(start_distance)
        )
        
        transform2 = Transform.CreateTranslation(
            direction.Multiply(unit_length)
        )

        transform3 = Transform.CreateTranslation(
            direction.Multiply(clear_length - 2 * base_cover)
        )
        
        segment_loop1 = CurveLoop.CreateViaTransform(
            base_loop,
            transform1
        )
        
        segment_loop2 = CurveLoop.CreateViaTransform(
            segment_loop1,
            transform2
        )
        
        segment_loop3 = CurveLoop.CreateViaTransform(
            segment_loop1,
            transform3
        )
        
        # ---------------------------------------------
        # Curves
        # ---------------------------------------------
        
        stirup_curves1 = List[Curve](
            [c for c in segment_loop1]
        )
        
        stirup_curves2 = List[Curve](
            [c for c in segment_loop2]
        )
        
        stirup_curves3 = List[Curve](
            [c for c in segment_loop3]
        )
        
        # ---------------------------------------------
        # Create rebar
        # ---------------------------------------------
        
        rebar1 = Rebar.CreateFromCurves(
            doc,
            RebarStyle.StirrupTie,
            stirrup_type,
            hook_type,
            hook_type,
            beam,
            direction,
            stirup_curves1,
            RebarHookOrientation.Right,
            RebarHookOrientation.Right,
            True,
            True
        )
        
        rebar1.GetShapeDrivenAccessor().SetLayoutAsMaximumSpacing(
            spacing1,
            unit_length,
            True,
            True,
            True
        )
        
        stirrup_rebars.append(rebar1)
        
        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,
            clear_length - base_cover - 2 * unit_length,
            True,
            False,
            False
        )
        
        stirrup_rebars.append(rebar2)
               
        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_rebars_from_segments(
        beam,
        curvlst,
        beam_data,
        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_curves = curvlst[0]
    btm_curves = curvlst[1]

    top_rebars = []
    btm_rebars = []

    l = len(top_curves)

    for idx, (top, btm) in enumerate(zip(top_curves, btm_curves)):

        # -------------------------------------------------
        # Determine hooks
        # -------------------------------------------------

        if l <= 2:

            if l == 1:
                start_hook = hook_type
                end_hook = hook_type

            else:
                if idx == 0:
                    start_hook = hook_type
                    end_hook = None

                else:
                    start_hook = None
                    end_hook = hook_type

        else:

            if idx == 0:
                start_hook = hook_type
                end_hook = None

            elif idx == l - 1:
                start_hook = None
                end_hook = hook_type

            else:
                start_hook = None
                end_hook = None

        # -------------------------------------------------
        # TOP REBAR
        # -------------------------------------------------

        top_curv = List[Curve]()
        top_curv.Add(top)

        top_rebar = Rebar.CreateFromCurves(
            doc,
            RebarStyle.Standard,
            top_bar_type,
            start_hook,
            end_hook,
            beam,
            normal.Negate(),
            top_curv,
            RebarHookOrientation.Right,
            RebarHookOrientation.Right,
            True,
            True
        )

        top_rebar.GetShapeDrivenAccessor().SetLayoutAsFixedNumber(
            n,
            w - 2 * side_cover,
            True,
            True,
            True
        )

        top_rebars.append(top_rebar)

        # -------------------------------------------------
        # BOTTOM REBAR
        # -------------------------------------------------

        btm_curv = List[Curve]()
        btm_curv.Add(btm)

        btm_rebar = Rebar.CreateFromCurves(
            doc,
            RebarStyle.Standard,
            btm_bar_type,
            start_hook,
            end_hook,
            beam,
            normal.Negate(),
            btm_curv,
            RebarHookOrientation.Right,
            RebarHookOrientation.Right,
            True,
            True
        )

        btm_rebar.GetShapeDrivenAccessor().SetLayoutAsFixedNumber(
            n,
            w - 2 * side_cover,
            True,
            True,
            True
        )

        btm_rebars.append(btm_rebar)

    return top_rebars, btm_rebars


# setting vertical constraints to stack rebars layers
def set_vertical_cover_constraint(rebar, beam, stirrup_type, bar_type, dir_flag):
    manager = rebar.GetRebarConstraintsManager()
    multiplier = 0.5 if dir_flag else 1.5
    axis_from_cover = stirrup_type.BarNominalDiameter + multiplier * bar_type.BarNominalDiameter
    clear_offset = axis_from_cover - 0.5 * bar_type.BarNominalDiameter

    for handle in manager.GetAllHandles():
        print(handle.GetHandleType())
        if str(handle.GetHandleType()) != "Edge":
            continue
        candidates = [c for c in manager.GetConstraintCandidatesForHandle(handle, beam.Id)
                      if c.IsToCover()]
        
        if not candidates:
            raise Exception("No ToCover candidate for longitudinal rebar")

        candidate = min(candidates, key=lambda c: abs(c.GetDistanceToTargetCover()))
        sign = 1.0 if candidate.GetDistanceToTargetCover() >= 0 else -1.0
        candidate.SetDistanceToTargetCover(sign * clear_offset)
        manager.SetPreferredConstraint(candidate)


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

# Collect columns
columns = FilteredElementCollector(doc)\
    .OfCategory(BuiltInCategory.OST_StructuralColumns)\
    .WhereElementIsNotElementType()\
    .ToList()

curves = []
top_add_curves = []
btm_add_curves = []
stirrup_curveloops = []

datas = []

for beam in beams:

    set_beam_covers(
        beam,
        5,
        7,
        3
    )
for beam in beams:
    beam_data = get_intersecting_end_beams_data(
        beam,
        beams,
        columns,
        top_ext_factor=1.0/5.0,
        btm_ext_factor=1.0/10.0)
    segment_data = beam_data["segment_data"]

    datas.append(beam_data)
    
    stirrup_curveloop, center = get_beam_stirrup_curveloop(beam, beam_data, beams)
    
    stirrup_curveloops.append(stirrup_curveloop)

    top_curve = beam_data["top_curve"]

    btm_curve = beam_data["btm_curve"]
    
    top_curves = split_curve(top_curve, top_bar_type, hook_90)

    btm_curves = split_curve(btm_curve, btm_bar_type, hook_90)

    top_curves = curve_multiply_offset(
    top_curves,
    beam_data,
    stirrup_type,
    True)
 
    btm_curves = curve_multiply_offset(
    btm_curves,
    beam_data,
    stirrup_type,
    False)
    curvlst = (top_curves, btm_curves)
    
    curves.append(curvlst)

rebars = []
add_rebars = []
longitudinal = []

with Transaction(doc, "Create Beam longitudinal Rebars") as t:
    t.Start()
    # -------------------------------------------------
    # Create stirrups rebars
    # -------------------------------------------------    
    for beam, stirrup_curveloop, beam_data in zip(beams, stirrup_curveloops, datas):
        stirrup_rebars = create_stirrups_rebars(beam, stirrup_curveloop, beam_data, stirrup_type, hook_135, 0.05, 1, 0.10, 0.15)    

    # -------------------------------------------------
    # Create longitudinal rebars
    # -------------------------------------------------
    for beam, curvlst, beam_data in zip(beams, curves, datas):

        dir_flag = beam_data["dir_flag"]


        top_rebars, btm_rebars = create_rebars_from_segments(
            beam,
            curvlst,
            beam_data,
            top_bar_type,
            btm_bar_type,
            hook_90,
            3
        )
        for top_rebar in top_rebars:
        
            longitudinal.append(
                (
                    top_rebar,
                    beam,
                    top_bar_type,
                    dir_flag
                )
            )
        
        for btm_rebar in btm_rebars:
        
            longitudinal.append(
                (
                    btm_rebar,
                    beam,
                    btm_bar_type,
                    dir_flag
                )
            )


    doc.Regenerate()

    # -----------------------------------------------------
    # Apply explicit vertical stacking constraints
    # -----------------------------------------------------
   
    for rebar, beam, bar_type, dir_flag in longitudinal:
        print("beam {} direction flag is {}".format(beam.Id, dir_flag) )
    
        set_vertical_cover_constraint(
            rebar,
            beam,
            stirrup_type,
            bar_type,
            dir_flag
        )
    t.Commit()

OUT = [[[c.ToProtoType() for c in toplst],[c.ToProtoType() for c in btmlst]]for toplst, btmlst in curves], [[c.ToProtoType() for c in toplst] for toplst in top_add_curves]

 

Please check my used model

 

Thanks.

0 Likes