Hi all, @Mike.Buttery,
I finally managed to solve my issue and found an alternative solution to @c.poupin’s approach that uses BoundingBox.ByMinimumVolume
.
The idea was to create a new_line
for cases where the concerned beam fails to return the expected max_edge
. This was done by projecting the start point start
of the beam’s beam_curve
location onto the face where max_edge
belongs. I used the face’s FaceNormal
and projected it with a distance equal to beam_width / 2
.
Then, using beam_curve.Direction
, I projected the endpoints of new_line
(new_start
, new_end
) to their final positions, with distances equal to half the width (width / 2) of the intersecting beams at the start
and end
points… which I refer to as w_start
and w_end
in my code.
The desired length in this case is:
total_length = beam_length + w_start + w_end
As you can see in the image below, I tested the code and it works for both orthogonal and skewed beams (with a slight difference for skewed beams).
here my final code:
beams_max_edge
import clr
import sys
import System
from System.Collections.Generic import List, Dictionary
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)
clr.AddReference('RevitServices')
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
import functools
doc = DocumentManager.Instance.CurrentDBDocument
def rollbackTransaction(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
TransactionManager.Instance.ForceCloseTransaction()
t = Transaction(doc, func.__name__)
t.Start()
try:
result = func(*args, **kwargs)
finally:
t.RollBack()
t.Dispose()
return result
return wrapper
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 get_beam_width(beam):
return beam.Symbol.LookupParameter("b").AsDouble()
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()
start = loc_curve.GetEndPoint(0)
end = loc_curve.GetEndPoint(1)
w_start = None
w_end = None
id_start = None
id_end = None
proj_pt_start = None
proj_pt_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()
# Project start point
projected_start = other_curve.Project(start)
if projected_start and projected_start.Distance < tolerance and w_start is None:
w_start = b/2
id_start = str(beam.Id)
proj_pt_start = projected_start.XYZPoint.ToPoint()
# Project end point
projected_end = other_curve.Project(end)
if projected_end and projected_end.Distance < tolerance and w_end is None:
w_end = b/2
id_end = str(beam.Id)
proj_pt_end = projected_end.XYZPoint.ToPoint()
return {
"beam_id": str(single_beam.Id),
"beam_width": w,
"width_start": w_start,
"width_end": w_end,
"start_id": id_start,
"end_id": id_end,
"projected_start_point": proj_pt_start,
"projected_end_point": proj_pt_end
}
@rollbackTransaction
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)
# Switch join order to ensure clean solid
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_length = max_edge_curve.Length
face_normal = get_side_face_normal(solid, max_edge)
# Get beam intersection data (needed in both cases)
intersection_data = get_intersecting_end_beams(beam, all_beams, tolerance=0.001)
beam_width = intersection_data["beam_width"]
if max_edge_length > beam_length:
new_line = max_edge_curve.ToProtoType()
w_start = intersection_data["width_start"] or 0
w_end = intersection_data["width_end"] or 0
else:
w_start = intersection_data["width_start"] or 0
w_end = intersection_data["width_end"] or 0
total_length = beam_length + w_start + w_end
project_point = start + face_normal.Multiply(beam_width / 2)
new_start = project_point - direction.Multiply(w_start)
new_end = new_start + direction.Multiply(total_length)
new_line = Line.CreateBound(new_start, new_end).ToProtoType()
return {
"beam_id": intersection_data["beam_id"],
"start_beam_id": intersection_data["start_id"],
"start_beam_width": round(w_start * 0.3048, 3),
"end_beam_id": intersection_data["end_id"],
"end_beam_width": round(w_end * 0.3048, 3),
"new_line": new_line,
"total_length": round(new_line.Length, 3)
}
# Collect beams
beams = FilteredElementCollector(doc)\
.OfCategory(BuiltInCategory.OST_StructuralFraming)\
.WhereElementIsNotElementType()\
.ToElements()
filtered = [res for res in (get_corrected_beam_length(beam, beams) for beam in beams) if res is not None]
OUT = ([res["new_line"] for res in filtered], filtered)
Thanks.