Stacking rebars issue within intersected beams
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
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.
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.