mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
226 lines
8.7 KiB
Python
226 lines
8.7 KiB
Python
|
|
# 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
|