mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 01:41:57 +00:00
Import alignment from csv (#6234)
* Start of the official alignment API * Import alignment into bonsai model using CSV file
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user