Calculating cant alignments in IFC
Posted on 2024-06-14 in how-to • 2 min read
Rule ALA003 of the buildingSMART validation service looks at individual alignment segments to confirm that the same geometry type is used in the business logic and in the geometric representation.
During development, there was a question raised regarding the proper entity type to be used in the representation segment that corresponds to a linear transition of cant in the business logic. The ensuing discussion in the implementer’s forum in IFC4.x-IF#145 lead to considerable confusion on my part.
Therefore I decided to develop this notebook to do some calculations for the model in question.
import os
import numpy as np
import ifcopenshell
import ifcopenshell.geom as geom
s = geom.settings()
IN_PATH = os.path.join("..", "assets", "models", "alignment_validation")
FILE_NAME = "ACCA_sleepers-linear-placement-cant-implicit.ifc"
IN_FILE = os.path.join(IN_PATH, FILE_NAME)
model = ifcopenshell.open(IN_FILE)
# per inspection, this model contains only 1 alignment
align = model.by_type("IfcAlignment")[0]
align
prod_rep = align.Representation
prod_rep
shape_reps = prod_rep.Representations
shape_reps
seg_ref_curve = shape_reps[0].Items[0]
seg_ref_curve
grad_curve = seg_ref_curve.BaseCurve
grad_curve
comp_curve = grad_curve.BaseCurve
comp_curve
# traverse the alignment cant layout
for _ in align.IsNestedBy[0].RelatedObjects:
if _.is_a() == "IfcAlignmentCant":
cant_layout = _
cant_layout
for seg in cant_layout.IsNestedBy[0].RelatedObjects:
dp = seg.DesignParameters
print(dp)
From inspection, we see that the initial transition in the cant layout, #2557, is a linear transition from 0.0 to 1.0 m of cant from distance 400.0 to 450.0.
Therefore, create a function to calculate the cant values at 5 m intervals along this transition.
We’re helped by the fact that there is no change in the vertical alignment along this portion of the alignment,
meaning that the calculated elevations are 0.0.
Therefore, the z
coordinate of the segmented reference curve corresponds to 1/2 the total cant.
The low rail is the axis of rotation and the total cant is the distance the high rail is elevated above its normal position.
Therefore at the centerline of rail, where the alignment is located, the z
coordinate is 1/2 the total cant amount.
Said another way:
segmented_ref_curve_z = gradient_curve_z + 0.5 * total_cant
if gradient_curve_z == 0:
segmented_ref_curve_z = 0.5 * total_cant
def evaluate_u(curve: ifcopenshell.entity_instance, dist_along: float) -> np.ndarray:
pwf = ifcopenshell.ifcopenshell_wrapper.map_shape(s, curve.wrapped_data)
t = pwf.evaluate(dist_along)
ar = np.array(t)
x = ar[0][3]
y = ar[1][3]
z = ar[2][3]
return np.array([dist_along, x, y, z], dtype=np.float64)
# set up the distances to be evaluated
distances = np.linspace(start=400.0, stop=450.0, num=11, endpoint=True, dtype=np.float64)
coords = np.array([evaluate_u(seg_ref_curve, d) for d in distances])
This is obviously far from a correctly pythonic use of numpy, but it gets the job done.
# slice a 2d array of [distance_along, total cant]
cant_coords = np.array([coords[:, 1], 2 * coords[:, 3]], dtype=np.float64).T
cant_coords
Conclusion¶
It still may not make sense to me, but the IfcOpenShell implementation clearly calculates the correct values of cant for this example model. Therefore I’m happy to stand corrected, with thanks to RickBrice and peterrdf for their patience and explanation.