Files
IfcOpenShell/src/ifcopenshell-python/ifcopenshell/api/alignment/map_alignment_vertical_segment.py
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

226 lines
8.7 KiB
Python
Raw Normal View History

2025-03-14 07:31:50 -07:00
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from ifcopenshell import ifcopenshell_wrapper
from ifcopenshell import entity_instance
from typing import Sequence
import math
def _polynomial_length(A: float, B: float, C: float, L: float) -> float:
# closed form solultion for length of parabolic curve.
# see https://www.integral-table.com, equation #37
# Parabolic curve equation: y = A + Bx + Cx^2
# y' = B + 2Cx
# Length of a curve = Integral[0,L]( (y')^2 + 1) dx)
# y'^2 = 4C^2x^2 + 4BCx + B^2
# Substituting, Length of a curve = Integral[0,L]( (4C^2)x^2 + (4BC)x + (B^2 + 1)) dx)
# for eq. #37 cited above, a = 4C^2, b = 4BC, c = B^2 + 1
a = 4.0 * C * C
b = 4.0 * B * C
c = B * B + 1
v1 = lambda a, b, c, x: (b + 2.0 * a * x) / (4.0 * a)
v2 = lambda a, b, c, x: math.sqrt(a * x * x + b * x + c)
v3 = lambda a, b, c, x: (4.0 * a * c - b * b) / (8.0 * math.pow(a, 1.5))
v4 = lambda a, b, c, x: math.log(math.fabs(2.0 * a * x + b + 2.0 * math.sqrt(a * (a * x * x + b * x + c))))
fn = lambda a, b, c, x: v1(a, b, c, x) * v2(a, b, c, x) + v3(a, b, c, x) * v4(a, b, c, x)
curve_length = fn(a, b, c, L) - fn(
a, b, c, 0
) # remember when evaluating an integral, it must be evaluated at end points (L and 0)
return curve_length
def _map_constant_gradient(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
start_distance_along = design_parameters.StartDistAlong
horizontal_length = design_parameters.HorizontalLength
start_height = design_parameters.StartHeight
start_gradient = design_parameters.StartGradient
end_gradient = design_parameters.EndGradient
radius_of_curvature = design_parameters.RadiusOfCurvature
transition = "DISCONTINUOUS"
parent_curve = file.create_entity(
type="IfcLine",
Pnt=file.create_entity(
type="IfcCartesianPoint",
Coordinates=(0.0, 0.0),
),
Dir=file.create_entity(
type="IfcVector",
Orientation=file.create_entity(
type="IfcDirection",
DirectionRatios=(1.0, 0.0),
),
Magnitude=1.0,
),
)
dx = math.cos(math.atan(start_gradient))
dy = math.sin(math.atan(start_gradient))
curve_segment_length = horizontal_length / dx
curve_segment = file.create_entity(
type="IfcCurveSegment",
Transition=transition,
Placement=file.create_entity(
type="IfcAxis2Placement2D",
Location=file.create_entity(type="IfcCartesianPoint", Coordinates=(start_distance_along, start_height)),
RefDirection=file.createIfcDirection((dx, dy)),
),
SegmentStart=file.createIfcLengthMeasure(0.0),
SegmentLength=file.createIfcLengthMeasure(curve_segment_length),
ParentCurve=parent_curve,
)
return (curve_segment, None)
def _map_parabolic_arc(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
start_distance_along = design_parameters.StartDistAlong
horizontal_length = design_parameters.HorizontalLength
start_height = design_parameters.StartHeight
start_gradient = design_parameters.StartGradient
end_gradient = design_parameters.EndGradient
radius_of_curvature = design_parameters.RadiusOfCurvature
transition = "DISCONTINUOUS"
A = start_height
B = start_gradient
C = (end_gradient - start_gradient) / (2.0 * horizontal_length)
parent_curve = file.create_entity(
type="IfcPolynomialCurve",
Position=file.create_entity(
type="IfcAxis2Placement2D",
Location=file.create_entity(type="IfcCartesianPoint", Coordinates=(0.0, 0.0)),
RefDirection=file.createIfcDirection(
(1.0, 0.0),
),
),
CoefficientsX=(0.0, 1.0),
CoefficientsY=(A, B, C),
)
dx = math.cos(math.atan(start_gradient))
dy = math.sin(math.atan(start_gradient))
curve_segment_length = _polynomial_length(A, B, C, horizontal_length)
curve_segment = file.create_entity(
type="IfcCurveSegment",
Transition=transition,
Placement=file.create_entity(
type="IfcAxis2Placement2D",
Location=file.create_entity(type="IfcCartesianPoint", Coordinates=(start_distance_along, start_height)),
RefDirection=file.createIfcDirection((dx, dy)),
),
SegmentStart=file.createIfcLengthMeasure(0.0),
SegmentLength=file.createIfcLengthMeasure(curve_segment_length),
ParentCurve=parent_curve,
)
return (curve_segment, None)
def _map_circular_arc(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
start_distance_along = design_parameters.StartDistAlong
horizontal_length = design_parameters.HorizontalLength
start_height = design_parameters.StartHeight
start_gradient = design_parameters.StartGradient
end_gradient = design_parameters.EndGradient
radius_of_curvature = design_parameters.RadiusOfCurvature
transition = "DISCONTINUOUS"
start_angle = math.atan(start_gradient)
end_angle = math.atan(end_gradient)
dx = math.cos(start_angle)
dy = math.sin(start_angle)
if start_angle < end_angle:
radius = horizontal_length / (math.sin(end_angle) - math.sin(start_angle))
x = -radius * math.sin(start_angle)
y = radius * math.cos(start_angle)
start_angle += 3.0 * math.pi / 2.0
end_angle += 3.0 * math.pi / 2.0
else:
radius = horizontal_length / (math.sin(start_angle) - math.sin(end_angle))
x = radius * math.sin(start_angle)
y = -radius * math.cos(start_angle)
start_angle += math.pi / 2.0
end_angle += math.pi / 2.0
parent_curve = file.createIfcCircle(
Position=file.createIfcAxis2Placement2D(
Location=file.createIfcCartesianPoint((x, y)),
RefDirection=file.createIfcDirection((1.0, 0.0)),
),
Radius=radius,
)
segment_curve_length = radius * math.fabs(end_angle - start_angle)
curve_segment = file.create_entity(
type="IfcCurveSegment",
Transition=transition,
Placement=file.createIfcAxis2Placement2D(
Location=file.createIfcCartesianPoint((start_distance_along, start_height)),
RefDirection=file.createIfcDirection(
(dx, dy),
),
),
SegmentStart=file.createIfcLengthMeasure(radius * start_angle),
SegmentLength=file.createIfcLengthMeasure(radius * (end_angle - start_angle)),
ParentCurve=parent_curve,
)
return (curve_segment, None)
def _map_clothoid(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
raise NotImplementedError("mapping for IfcVerticalSegment.CLOTHOID not implemented")
def map_alignment_vertical_segment(
file: ifcopenshell.file, design_parameters: entity_instance
) -> Sequence[entity_instance]:
"""
Creates IfcCurveSegment entities for the represention of the supplied IfcAlignmentVerticalSegment business logic entity instance.
A pair of entities is returned for consistency with map_alignment_horizontal_segment and map_alignment_cant_segment.
"""
expected_type = "IfcAlignmentVerticalSegment"
if not design_parameters.is_a(expected_type):
raise TypeError(f"Expected to see type '{expected_type}', instead received '{design_parameters.is_a()}'.")
match design_parameters.PredefinedType:
case "CONSTANTGRADIENT":
result = _map_constant_gradient(file, design_parameters)
case "PARABOLICARC":
result = _map_parabolic_arc(file, design_parameters)
case "CIRCULARARC":
result = _map_circular_arc(file, design_parameters)
case "CLOTHOID":
result = _map_clothoid(file, design_parameters)
case _:
raise TypeError("Unexpected predefined type")
return result