Setting rebars hooks orientation for beams network

Hi all, @c.poupin , @Mike.Buttery , @christian.stan

To create rebars for an orthogonal beam network where each beam spans from end to end, I need to extract the max_edge from each beam by retrieving the full, uncut solid. Then, I use the FaceNormal of the face where the max_edge lies to set the rebar distribution layout. I was able to achieve this in my previous question here.

However, and more importantly, ever since I started working with rebars in the API, I’ve consistently faced issues correctly setting the RebarHookOrientation for rebar ends. I’m still confused and don’t fully understand Revit’s rules for determining these orientations.

To illustrate my issue, consider the beam network example in the image below (where the first beams are parallel to the X axis and oriented toward +X, and the last four beams are parallel to the Y axis and oriented toward +Y).

When I run the code below, I get the expected hook orientations at the rebar ends for both top_rebar and btm_rebar on all beams except for beam4 and beam5, where their FaceNormal is on the opposite face compared to the corresponding beams in the same direction, I tried all possible possible orientatons, and I’m struggling to find a solution to this issue?

Here my code:

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

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

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

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

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

doc = DocumentManager.Instance.CurrentDBDocument

# Inputs from Dynamo
top_bar_type = UnwrapElement(IN[0])
btm_bar_type = UnwrapElement(IN[1])
hook_type = UnwrapElement(IN[2])

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

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

def curve_multiply_offset(curve, vector, distance, cover):
    curve = curve.CreateTransformed(Transform.CreateTranslation(XYZ(0, 0, -1).Multiply(cover)))
    vertical_offset = distance - 2 * cover

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

def get_intersecting_end_beams(single_beam, all_beams, tolerance=0.001):
    loc_curve = single_beam.Location.Curve
    w = single_beam.Symbol.LookupParameter("b").AsDouble()
    h = single_beam.Symbol.LookupParameter("h").AsDouble()
    start = loc_curve.GetEndPoint(0)
    end = loc_curve.GetEndPoint(1)

    w_start = None
    w_end = None

    for beam in all_beams:
        if beam.Id == single_beam.Id:
            continue
        other_curve = beam.Location.Curve
        b = beam.Symbol.LookupParameter("b").AsDouble()

        projected_start = other_curve.Project(start)
        if projected_start and projected_start.Distance < tolerance and w_start is None:
            w_start = b / 2

        projected_end = other_curve.Project(end)
        if projected_end and projected_end.Distance < tolerance and w_end is None:
            w_end = b / 2

    return {
        "beam_width": w,
        "beam_height": h,
        "width_start": w_start,
        "width_end": w_end
    }

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

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

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

    max_edge = max(solid.Edges, key=lambda x: x.ApproximateLength)
    max_edge_curve = max_edge.AsCurve()
    max_edge_direction = max_edge_curve.Direction
    max_edge_length = max_edge_curve.Length
    face_normal = get_side_face_normal(solid, max_edge)
    if face_normal is None:
        return None
    
    intersection_data = get_intersecting_end_beams(beam, all_beams)
    beam_width = intersection_data["beam_width"]
    beam_height = intersection_data["beam_height"]
    cover = 0.05 / 0.3048
    w_start = intersection_data["width_start"] or 0
    w_end = intersection_data["width_end"] or 0
    
    if max_edge_length > beam_length:
        if not max_edge_direction.IsAlmostEqualTo(direction):
            max_edge_curve = Line.CreateBound(max_edge_curve.GetEndPoint(1), max_edge_curve.GetEndPoint(0))
        new_start = max_edge_curve.Evaluate(cover / max_edge_length, True)
        new_end = max_edge_curve.Evaluate(1 - (cover / max_edge_length), True)
        new_line = Line.CreateBound(new_start, new_end)
        
        
    else:
        total_length = beam_length + w_start + w_end - 2 * cover
        project_point = start + face_normal.Multiply(beam_width / 2)
        new_start = project_point - direction.Multiply(w_start - cover)
        new_end = new_start + direction.Multiply(total_length)
        new_line = Line.CreateBound(new_start, new_end)
        

    top_curve, btm_curve = curve_multiply_offset(new_line, face_normal, beam_height, cover)
    print(direction)
    print(top_curve.Direction)
    print(btm_curve.Direction)    
    return btm_curve, top_curve, face_normal

# Collect beams
all_beams = FilteredElementCollector(doc)\
    .OfCategory(BuiltInCategory.OST_StructuralFraming)\
    .WhereElementIsNotElementType()\
    .ToElements()

beams = [
    b for b in all_beams
    if b.LookupParameter("Utilisation structurelle") and
       b.LookupParameter("Utilisation structurelle").AsValueString() == "Longeron"
]

with Transaction(doc, "create rebars") as t:
    t.Start()
    curves = []
    rebars = []
    cover = 0.05 / 0.3048
    for beam in beams:
        direction = beam.Location.Curve.Direction
        width = beam.Symbol.LookupParameter("b").AsDouble()
        result = get_corrected_beam_length(beam, beams)
        if not result:
            continue
        top_curve, btm_curve, normal_vect = result
        
        if normal_vect.CrossProduct(direction).IsAlmostEqualTo(XYZ(0,0,-1)):
	        normT = top_curve.Direction.CrossProduct(XYZ.BasisZ).Negate()
	        normB = top_curve.Direction.CrossProduct(XYZ.BasisZ).Negate()
	        
        else:
	        normT = top_curve.Direction.CrossProduct(XYZ.BasisZ)
	        normB = top_curve.Direction.CrossProduct(XYZ.BasisZ)            
        top_curve_list = List[Curve]()
        btm_curve_list = List[Curve]()
        top_curve_list.Add(top_curve)
        btm_curve_list.Add(btm_curve)
        
        top_rebar = Rebar.CreateFromCurves(
            doc, RebarStyle.Standard,
            top_bar_type, hook_type, hook_type,
            beam, normT, top_curve_list,
            RebarHookOrientation.Left, RebarHookOrientation.Left,
            True, True
        )
        top_rebar.GetShapeDrivenAccessor().SetLayoutAsFixedNumber(3, width-2*cover, True, True, True)
        btm_rebar = Rebar.CreateFromCurves(
            doc, RebarStyle.Standard,
            btm_bar_type, hook_type, hook_type,
            beam, normB, btm_curve_list,
            RebarHookOrientation.Left, RebarHookOrientation.Left,
            True, True
        )
        btm_rebar.GetShapeDrivenAccessor().SetLayoutAsFixedNumber(3, width-2*cover, True, True, True)


        rebars.append([top_rebar, btm_rebar])
        curves.append([top_curve.ToProtoType(), btm_curve.ToProtoType()])
    t.Commit()

OUT = rebars

Any help would be apreciated

Thanks.

No one can help me?

Do beams 4 and 5 run a different direction than the others? i.e. do most beams run southwest to northeast, but these two run the opposite direction?

1 Like
  • beams parallel to X axis which are (beam1, beam2, beam3, beam4) goes from west to east (i.e their directions points to +X), picked FaceNormal from beam1, beam2 and beam3 points toward south (-Y), In opposit to other beams picked FaceNormal from beam4 points toward North (+Y)
  • beams parallel to Y axis which are (beam5, beam6, beam7, beam8) goes from south to north (i.e their directions points to +Y), picked FaceNormal from beam5 points toward west (-X), in opposit FaceNormal from other beams points toward east (+X)

Thanks.

So the two beams which have directions that are opposite are getting the hooks going the opposite way?

1 Like

Yes, that’s exactly what’s happening.

Try reversing the curves for those two beams before you make the rebar/hooks.

@jacob.small

here the outputed curves directions for each beam and I can’t see how can I reverse curves for beams having wrong hooks without reversing their FaceNormal, so in this case generated rebars are outside their host?

Hmmm… so the direction of the X beams and Y beams is consistent after all…

Can you provide a sample model and your current graph?

1 Like

@jacob.small

Please check my attached model and dyn file

beams_network.rvt (2.1 MB)
beams_network-rebars.dyn (16.1 KB)

Note: I’m using Revit 2023 and Structural Design package for rebars

Thanks.