mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 01:41:57 +00:00
Alignment api update (#6817)
* Some basic COGO survey points functions * Update alignment api Includes defining alignment segment by segment, automatic geometry definitions, and automatric stationing and referents * Fixes bonsai import alignment from csv * Adds DMS angle conversion functions to COGO api * Updated per @civilx64 review comments * Fixes problem with segment representations * Fixes problem with segment transition codes * Allows for compound vertical and horizontal curves * Implements callbacks for referent naming * Renames angle_from_bearing to bearing2dd for consistency with ifcopenshell.util.geolocation.dms2dd. Removes angle_from_dms because it duplicates dms2dd * Documents register_referent_name_callback * Fixes all sorts of problems with Cant/SegRefCurve implementation * refactor referent unit tests to use a fixture for test setup * lint with black --------- Co-authored-by: Scott Lecher <civilx64@gmail.com>
This commit is contained in:
@@ -17,7 +17,15 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Manages alignment layout (business logical) and alignment geometry (geometric representations).
|
||||
Manages alignment layout (semantic definition) and alignment geometry (geometric definition).
|
||||
|
||||
This API is defined in terms of the semantic definition of an alignment. The corresponding geometric definition
|
||||
is created and maintained automatically. The manditory zero length segment for the semantic and geometric definitions
|
||||
are automatically created and maintained.
|
||||
|
||||
Alignments are created with stationing referents. Each layout segment is assigned a position referent that informs about
|
||||
the start point of the segment. An example is the point of curvature of a horizontal circular curve. The referent is
|
||||
nested to the segment representing the circular arc and is named with a indicator of the position and the station, e.g. "P.C. (145+98.32)"
|
||||
|
||||
This API does not determine alignment parameters based on rules, such as minimum curve radius as a function of design speed or sight distance.
|
||||
|
||||
@@ -25,14 +33,13 @@ This API is under development and subject to code breaking changes in the future
|
||||
|
||||
Presently, this API supports:
|
||||
1. Creating alignments, both horizontal and vertical, using the PI method. Alignment definition can be read from a CSV file.
|
||||
2. Adding business logic and geometric segments to the end of an alignment
|
||||
3. Adding and removing the zero length segment at the end of alignments
|
||||
4. Creating geometric representations from a business logical definition
|
||||
5. Mapping individual business logical segments to geometric segments (complete for horizontal, missing clothoid for vertical, not implemented for cant)
|
||||
6. Using curve geometry to determine IfcCurveSegment.Transition transition code.
|
||||
7. Utility functions for printing business logical and geometric representations, as well as minimal geometry evaluations
|
||||
2. Creating alignments segment by segment.
|
||||
3. Automatic creation of geometric definitions (IfcCompositeCurve, IfcGradientCurve, IfcSegmentedReferenceCurve)
|
||||
4. Automatic definition of stationing
|
||||
5. Automatic definition of alignment transition point referents
|
||||
6. Utility functions for printing business logical and geometric representations, as well as minimal geometry evaluations
|
||||
|
||||
Future versions of this API will support:
|
||||
Future versions of this API may support:
|
||||
1. Defining alignments using the PI method, including transition spirals
|
||||
2. Updating horizontal curve definitions by revising transition spiral parameters and circular curve radii
|
||||
3. Updating vertical curve definitions by revising horizontal length of curves
|
||||
@@ -40,61 +47,59 @@ Future versions of this API will support:
|
||||
5. Adding a segment at any location along a curve
|
||||
"""
|
||||
|
||||
from .add_segment_to_curve import add_segment_to_curve
|
||||
from .add_segment_to_layout import add_segment_to_layout
|
||||
from .add_stationing_to_alignment import add_stationing_to_alignment
|
||||
from .add_vertical_alignment_by_pi_method import add_vertical_alignment_by_pi_method
|
||||
from .add_vertical_alignment import add_vertical_alignment
|
||||
from .add_zero_length_segment import add_zero_length_segment
|
||||
from .create_alignment_by_pi_method import create_alignment_by_pi_method
|
||||
from .create_alignment_from_csv import create_alignment_from_csv
|
||||
from .create_horizontal_alignment_by_pi_method import create_horizontal_alignment_by_pi_method
|
||||
from .create_geometric_representation import create_geometric_representation
|
||||
from .create_vertical_alignment_by_pi_method import create_vertical_alignment_by_pi_method
|
||||
from .add_stationing_referent import add_stationing_referent
|
||||
from .add_vertical_layout import add_vertical_layout
|
||||
from .create_layout_segment import create_layout_segment
|
||||
from .create_alignment import create
|
||||
from .create_by_pi_method import create_by_pi_method
|
||||
from .create_from_csv import create_from_csv
|
||||
from .create_segment_representations import create_segment_representations
|
||||
from .distance_along_from_station import distance_along_from_station
|
||||
from .get_alignment import get_alignment
|
||||
from .get_alignment_station import get_alignment_station
|
||||
from .get_layout_segments import get_layout_segments
|
||||
from .get_horizontal_layout import get_horizontal_layout
|
||||
from .get_vertical_layout import get_vertical_layout
|
||||
from .get_cant_layout import get_cant_layout
|
||||
from .get_alignment_layouts import get_alignment_layouts
|
||||
from .get_axis_subcontext import get_axis_subcontext
|
||||
from .get_basis_curve import get_basis_curve
|
||||
from .get_child_alignments import get_child_alignments
|
||||
from .get_curve import get_curve
|
||||
from .get_layout_curve import get_layout_curve
|
||||
from .get_parent_alignment import get_parent_alignment
|
||||
from .has_zero_length_segment import has_zero_length_segment
|
||||
from .map_alignment_segments import map_alignment_segments
|
||||
from .map_alignment_horizontal_segment import map_alignment_horizontal_segment
|
||||
from .map_alignment_vertical_segment import map_alignment_vertical_segment
|
||||
from .map_alignment_cant_segment import map_alignment_cant_segment
|
||||
from .layout_horizontal_alignment_by_pi_method import layout_horizontal_alignment_by_pi_method
|
||||
from .layout_vertical_alignment_by_pi_method import layout_vertical_alignment_by_pi_method
|
||||
from .name_segments import name_segments
|
||||
from .remove_last_segment import remove_last_segment
|
||||
from .remove_zero_length_segment import remove_zero_length_segment
|
||||
from .update_curve_segment_transition_code import update_curve_segment_transition_code
|
||||
from .util import *
|
||||
|
||||
from ._get_segment_start_point_label import register_referent_name_callback
|
||||
|
||||
__all__ = [
|
||||
"add_segment_to_curve",
|
||||
"add_segment_to_layout",
|
||||
"add_stationing_to_alignment",
|
||||
"add_vertical_alignment_by_pi_method",
|
||||
"add_vertical_alignment",
|
||||
"add_zero_length_segment",
|
||||
"create_alignment_by_pi_method",
|
||||
"create_alignment_from_csv",
|
||||
"create_horizontal_alignment_by_pi_method",
|
||||
"create_geometric_representation",
|
||||
"create_vertical_alignment_by_pi_method",
|
||||
"add_stationing_referent",
|
||||
"add_vertical_layout",
|
||||
"create_layout_segment",
|
||||
"create",
|
||||
"create_by_pi_method",
|
||||
"create_from_csv",
|
||||
"distance_along_from_station",
|
||||
"get_alignment",
|
||||
"get_alignment_station",
|
||||
"get_layout_segments",
|
||||
"get_horizontal_layout",
|
||||
"get_vertical_layout",
|
||||
"get_cant_layout",
|
||||
"get_alignment_layouts",
|
||||
"get_axis_subcontext",
|
||||
"get_basis_curve",
|
||||
"get_child_alignments",
|
||||
"get_curve",
|
||||
"get_layout_curve",
|
||||
"get_parent_alignment",
|
||||
"has_zero_length_segment",
|
||||
"map_alignment_segments",
|
||||
"map_alignment_horizontal_segment",
|
||||
"map_alignment_vertical_segment",
|
||||
"map_alignment_cant_segment",
|
||||
"layout_horizontal_alignment_by_pi_method",
|
||||
"layout_vertical_alignment_by_pi_method",
|
||||
"name_segments",
|
||||
"remove_last_segment",
|
||||
"remove_zero_length_segment",
|
||||
"update_curve_segment_transition_code",
|
||||
"register_referent_name_callback",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
# 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
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
from ifcopenshell.api.alignment._update_curve_segment_transition_code import _update_curve_segment_transition_code
|
||||
from ifcopenshell.api.alignment._map_alignment_horizontal_segment import __map_alignment_horizontal_segment
|
||||
from ifcopenshell.api.alignment._map_alignment_vertical_segment import _map_alignment_vertical_segment
|
||||
from ifcopenshell.api.alignment._map_alignment_cant_segment import _map_alignment_cant_segment
|
||||
|
||||
|
||||
def _add_curve_segment_to_composite_curve(
|
||||
file: ifcopenshell.file, curve_segment: entity_instance, composite_curve: entity_instance
|
||||
):
|
||||
if 0 < len(curve_segment.UsingCurves):
|
||||
raise TypeError("IfcCurveSegment cannot belong to other curves")
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
if composite_curve.Segments == None or 0 == len(composite_curve.Segments):
|
||||
# this is the first segment so just add it
|
||||
if composite_curve.Segments == None:
|
||||
composite_curve.Segments = []
|
||||
|
||||
# the last segment is always discontinuous
|
||||
curve_segment.Transition = "DISCONTINUOUS"
|
||||
|
||||
composite_curve.Segments += (curve_segment,)
|
||||
assert len(curve_segment.UsingCurves) == 1
|
||||
else:
|
||||
# get the segment before the zero length segment
|
||||
prev_segment = (
|
||||
composite_curve.Segments[-2]
|
||||
if composite_curve.Segments != None and 1 < len(composite_curve.Segments)
|
||||
else None
|
||||
)
|
||||
|
||||
curve_segment.Transition = "CONTINUOUS"
|
||||
|
||||
# must add the new segment to the curve before updating the transition code
|
||||
# add the new segment before the zero length segment
|
||||
zero_length_segment = composite_curve.Segments[-1]
|
||||
|
||||
if prev_segment:
|
||||
segments = composite_curve.Segments[0:-1]
|
||||
segments += (
|
||||
curve_segment,
|
||||
zero_length_segment,
|
||||
)
|
||||
composite_curve.Segments = []
|
||||
composite_curve.Segments += segments
|
||||
_update_curve_segment_transition_code(prev_segment, curve_segment)
|
||||
else:
|
||||
composite_curve.Segments = (curve_segment, zero_length_segment)
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
segment_fn = ifcopenshell_wrapper.map_shape(settings, curve_segment.wrapped_data)
|
||||
segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, segment_fn)
|
||||
e = segment_evaluator.evaluate(segment_fn.end())
|
||||
end = np.array(e)
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
|
||||
x = float(end[0, 3]) / unit_scale
|
||||
y = float(end[1, 3]) / unit_scale
|
||||
dx = float(end[0, 0])
|
||||
dy = float(end[1, 0])
|
||||
|
||||
# assume IfcAxis2Placement2D
|
||||
zero_length_segment.Placement.Location.Coordinates = (x, y)
|
||||
zero_length_segment.Placement.RefDirection.DirectionRatios = (dx, dy)
|
||||
|
||||
_update_curve_segment_transition_code(curve_segment, zero_length_segment)
|
||||
|
||||
|
||||
def _add_segment_to_curve(file: ifcopenshell.file, segment: entity_instance, curve: entity_instance) -> None:
|
||||
"""
|
||||
Creates an IfcCurveSegment from the IfcAlignmentSegment and adds it to the representation curve. The IfcCurveSegment is added
|
||||
at the end of the curve, but before the manditory zero length segment. The IfcCurveSegment.Transition for the segment
|
||||
that preceeds the new segment is updated.
|
||||
|
||||
:param segment: The segment to be added to the curve
|
||||
:param curve: The representation curve receiving the segment
|
||||
:return: None
|
||||
"""
|
||||
expected_types = ["IfcAlignmentSegment"]
|
||||
if not segment.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{segment.is_a()}"
|
||||
)
|
||||
|
||||
if segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment") and not curve.is_a("IfcCompositeCurve"):
|
||||
raise TypeError(f"Expected to see IfcCompositeCurve, instead received '{curve.is_a()}'.")
|
||||
elif segment.DesignParameters.is_a("IfcAlignmentVerticalSegment") and not curve.is_a("IfcGradientCurve"):
|
||||
raise TypeError(f"Expected to see IfcGradientCurve, instead received '{curve.is_a()}'.")
|
||||
elif segment.DesignParameters.is_a("IfcAlignmentCantSegment") and not curve.is_a("IfcSegmentedReferenceCurve"):
|
||||
raise TypeError(f"Expected to see IfcSegmentedReferenceCurve, instead received '{curve.is_a()}'.")
|
||||
|
||||
expected_type = "IfcCompositeCurve"
|
||||
if not curve.is_a(expected_type):
|
||||
raise TypeError(f"Expected to see {expected_type}, instead received {curve.is_a()}.")
|
||||
|
||||
# map the IfcAlignmentSegment to an IfcCurveSegment (or two in the case of helmert curves)
|
||||
if segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
|
||||
mapped_segments = __map_alignment_horizontal_segment(file, segment)
|
||||
elif segment.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
|
||||
mapped_segments = _map_alignment_vertical_segment(file, segment)
|
||||
elif segment.DesignParameters.is_a("IfcAlignmentCantSegment"):
|
||||
cant_layout = segment.Nests[0].RelatingObject
|
||||
mapped_segments = _map_alignment_cant_segment(file, segment, cant_layout.RailHeadDistance)
|
||||
else:
|
||||
assert False
|
||||
|
||||
for mapped_segment in mapped_segments:
|
||||
if mapped_segment:
|
||||
_add_curve_segment_to_composite_curve(file, mapped_segment, curve)
|
||||
@@ -0,0 +1,198 @@
|
||||
# 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
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.nest
|
||||
from ifcopenshell import ifcopenshell_wrapper
|
||||
import numpy as np
|
||||
import math
|
||||
from ifcopenshell import entity_instance
|
||||
from typing import Sequence
|
||||
|
||||
from ifcopenshell.api.alignment._add_segment_to_curve import _add_segment_to_curve
|
||||
from ifcopenshell.api.alignment._get_mapped_segments import _get_mapped_segments
|
||||
from ifcopenshell.api.alignment._get_segment_start_point_label import _get_segment_start_point_label
|
||||
|
||||
|
||||
def _add_segment_to_layout(file: ifcopenshell.file, layout: entity_instance, segment: entity_instance) -> None:
|
||||
"""
|
||||
Adds an IfcAlignmentSegment to a layout alignment (IfcAlignmentHorizontal/Vertical/Cant). This segment is added at the end
|
||||
of the layout, before the manditory zero length segment. An IfcCurveSegment is created for the corresponding geometric representation.
|
||||
|
||||
:param layout: The layout alignment
|
||||
:param segment: The segment to be appended
|
||||
:return: None
|
||||
"""
|
||||
|
||||
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
|
||||
if not layout.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received {layout.is_a()}"
|
||||
)
|
||||
|
||||
if not (segment.is_a("IfcAlignmentSegment")):
|
||||
raise TypeError(f"Expected to see IfcAlignmentSegment, instead received {segment.is_a()}.")
|
||||
|
||||
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
|
||||
|
||||
# add the new segment to the layout
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=[segment], relating_object=layout)
|
||||
|
||||
# segment is attached at the end, but this is after the zero length segment
|
||||
# swap the last two segments
|
||||
ifcopenshell.api.nest.reorder_nesting(file, segment, -1, -1)
|
||||
|
||||
# add the new segment to the geometric representation curve
|
||||
_add_segment_to_curve(file, segment, curve)
|
||||
|
||||
# gather information to:
|
||||
# (1) add a referent at the start of this segment
|
||||
# (2) update the name of the zero length segment's referent
|
||||
|
||||
# get the distance along the alignment to the start of the new segment
|
||||
dist_along = 0.0
|
||||
if layout.is_a("IfcAlignmentHorizontal"):
|
||||
for nest in layout.IsNestedBy:
|
||||
for seg in nest.RelatedObjects:
|
||||
if seg.is_a("IfcAlignmentSegment"):
|
||||
dist_along += seg.DesignParameters.SegmentLength
|
||||
|
||||
# the length of the current segment is in dist_along, so subtract it out
|
||||
dist_along -= segment.DesignParameters.SegmentLength
|
||||
else:
|
||||
dist_along = segment.DesignParameters.StartDistAlong
|
||||
|
||||
# get the station of the start of the segment
|
||||
alignment = ifcopenshell.api.alignment.get_alignment(layout)
|
||||
start_station = ifcopenshell.api.alignment.get_alignment_station(file, alignment)
|
||||
station = start_station + dist_along
|
||||
|
||||
# update the zero length layout segment
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
|
||||
|
||||
zero_length_segment = layout.IsNestedBy[0].RelatedObjects[-1]
|
||||
# DesignParameters.StartPoint for IfcAlignmentHorizontalSegment is automatically updated when the
|
||||
# geometric representation is updated because the semantic and geometric data use the same IfcPoint.
|
||||
# This is not the case of IfcAlignmentVerticalSegment and IfcAlignmentCantSegment. For these
|
||||
# segment types, the design parameters of the zero length segment must be updated explicitly.
|
||||
if zero_length_segment.DesignParameters.is_a(
|
||||
"IfcAlignmentVerticalSegment"
|
||||
) or zero_length_segment.DesignParameters.is_a("IfcAlignmentCantSegment"):
|
||||
# get the geometric representation for the new segment
|
||||
mapped_segments = _get_mapped_segments(file, segment)
|
||||
mapped_segment = mapped_segments[0] if mapped_segments[1] == None else mapped_segments[1]
|
||||
|
||||
# compute the end point matrix
|
||||
settings = ifcopenshell.geom.settings()
|
||||
segment_fn = ifcopenshell_wrapper.map_shape(settings, mapped_segment.wrapped_data)
|
||||
segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, segment_fn)
|
||||
e = segment_evaluator.evaluate(segment_fn.end())
|
||||
end = np.array(e)
|
||||
|
||||
# update the zero length segment semantic representation parameters
|
||||
if zero_length_segment.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
|
||||
y = float(end[1, 3]) / unit_scale
|
||||
zero_length_segment.DesignParameters.StartHeight = y
|
||||
dx = float(end[0, 0])
|
||||
dy = float(end[1, 0])
|
||||
zero_length_segment.DesignParameters.StartGradient = dy / dx
|
||||
zero_length_segment.DesignParameters.EndGradient = zero_length_segment.DesignParameters.StartGradient
|
||||
else:
|
||||
z = float(end[2, 3]) / unit_scale
|
||||
dx = float(end[0, 1])
|
||||
dy = float(end[1, 1])
|
||||
dz = float(end[2, 1])
|
||||
ds = math.sqrt(dx * dx + dy * dy)
|
||||
slope = dz / ds
|
||||
railhead = layout.RailHeadDistance
|
||||
|
||||
zero_length_segment.DesignParameters.StartCantLeft = z + slope * railhead / 2.0
|
||||
zero_length_segment.DesignParameters.StartCantRight = z - slope * railhead / 2.0
|
||||
|
||||
# updated the referent's name because the referent is now at a new station
|
||||
start_dist_along = 0.0
|
||||
if segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
|
||||
start_dist_along = dist_along + segment.DesignParameters.SegmentLength
|
||||
else:
|
||||
start_dist_along = segment.DesignParameters.StartDistAlong + segment.DesignParameters.HorizontalLength
|
||||
zero_length_segment.DesignParameters.StartDistAlong = start_dist_along
|
||||
|
||||
end_referent = zero_length_segment.IsNestedBy[0].RelatedObjects[0]
|
||||
end_referent.Name = f"{_get_segment_start_point_label(zero_length_segment,None)} ({ifcopenshell.util.stationing.station_as_string(file,start_station+start_dist_along)})"
|
||||
|
||||
# update the referent's geometric representation's location
|
||||
end_referent.ObjectPlacement.RelativePlacement.Location.DistanceAlong.wrappedValue = start_dist_along
|
||||
settings = ifcopenshell.geom.settings()
|
||||
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
curve_fn = ifcopenshell_wrapper.map_shape(settings, basis_curve.wrapped_data)
|
||||
curve_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, curve_fn)
|
||||
p = curve_evaluator.evaluate(start_dist_along * unit_scale)
|
||||
p = np.array(p)
|
||||
|
||||
x = float(p[0, 3]) / unit_scale
|
||||
y = float(p[1, 3]) / unit_scale
|
||||
z = float(p[2, 3]) / unit_scale
|
||||
|
||||
rx = float(p[0, 0])
|
||||
ry = float(p[1, 0])
|
||||
rz = float(p[2, 0])
|
||||
|
||||
ax = float(p[0, 2])
|
||||
ay = float(p[1, 2])
|
||||
az = float(p[2, 2])
|
||||
|
||||
end_referent.ObjectPlacement.CartesianPosition.Location.Coordinates = (x, y, z)
|
||||
end_referent.ObjectPlacement.CartesianPosition.Axis.DirectionRatios = (ax, ay, az)
|
||||
end_referent.ObjectPlacement.CartesianPosition.RefDirection.DirectionRatios = (rx, ry, rz)
|
||||
|
||||
# create the start of segment referent
|
||||
|
||||
# get the previous segment. Working from the end of the basis curve, -1 is zero length segment
|
||||
# -2 is the newly added segment, so -3 is the segment occuring just before the newly added segment
|
||||
prev_segment = layout.IsNestedBy[0].RelatedObjects[-3] if 2 < len(layout.IsNestedBy[0].RelatedObjects) else None
|
||||
name = f"{_get_segment_start_point_label(prev_segment,segment)} ({ifcopenshell.util.stationing.station_as_string(file,station)})"
|
||||
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||
file, segment, basis_curve=basis_curve, distance_along=dist_along, station=station, name=name
|
||||
)
|
||||
|
||||
if len(curve.Segments) == 2 and layout.is_a("IfcAlignmentHorizontal"):
|
||||
# this is the first real segment in the horizontal alignment
|
||||
# update the location of the alignment's stationing referent
|
||||
alignment = ifcopenshell.api.alignment.get_alignment(layout)
|
||||
stationing_referent = alignment.IsNestedBy[0].RelatedObjects[0]
|
||||
p = curve_evaluator.evaluate(
|
||||
stationing_referent.ObjectPlacement.RelativePlacement.Location.DistanceAlong.wrappedValue
|
||||
)
|
||||
p = np.array(p)
|
||||
|
||||
x = float(p[0, 3]) / unit_scale
|
||||
y = float(p[1, 3]) / unit_scale
|
||||
z = float(p[2, 3]) / unit_scale
|
||||
|
||||
rx = float(p[0, 0])
|
||||
ry = float(p[1, 0])
|
||||
rz = float(p[2, 0])
|
||||
|
||||
ax = float(p[0, 2])
|
||||
ay = float(p[1, 2])
|
||||
az = float(p[2, 2])
|
||||
|
||||
stationing_referent.ObjectPlacement.CartesianPosition.Location.Coordinates = (x, y, z)
|
||||
stationing_referent.ObjectPlacement.CartesianPosition.Axis.DirectionRatios = (ax, ay, az)
|
||||
stationing_referent.ObjectPlacement.CartesianPosition.RefDirection.DirectionRatios = (rx, ry, rz)
|
||||
@@ -0,0 +1,144 @@
|
||||
# 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
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.nest
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
from ifcopenshell.api.alignment._get_segment_start_point_label import _get_segment_start_point_label
|
||||
|
||||
|
||||
def _add_zero_length_segment(file: ifcopenshell.file, layout: entity_instance) -> None:
|
||||
"""
|
||||
Adds a zero length segment to the end of a layout. Also adds a zero length segment to the end of the corresponding geometric curve.
|
||||
|
||||
If the layout already has a zero length segment, nothing is changed
|
||||
|
||||
:param layout: An IfcAlignmentHorizontal, IfcAlignmentVertical, or IfcAlignmentCant
|
||||
:return: None
|
||||
"""
|
||||
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
|
||||
if not layout.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected layout type to be one of {[_ for _ in expected_types]}, instead received {layout.is_a()}"
|
||||
)
|
||||
|
||||
if ifcopenshell.api.alignment.has_zero_length_segment(layout):
|
||||
return
|
||||
|
||||
segment = None
|
||||
if layout.is_a("IfcAlignmentHorizontal"):
|
||||
design_parameters = file.createIfcAlignmentHorizontalSegment(
|
||||
StartPoint=file.createIfcCartesianPoint(
|
||||
(0.0, 0.0)
|
||||
), # this is a little problematic. need to know the end point and tangent
|
||||
StartDirection=0.0, # of the previous segment, which requires geometry mapping
|
||||
StartRadiusOfCurvature=0.0,
|
||||
EndRadiusOfCurvature=0.0,
|
||||
SegmentLength=0.0,
|
||||
PredefinedType="LINE",
|
||||
)
|
||||
segment = file.createIfcAlignmentSegment(GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters)
|
||||
elif layout.is_a("IfcAlignmentVertical"):
|
||||
last_segment_dist_along = 0.0
|
||||
last_segment_end_gradient = 0.0
|
||||
for rel in layout.IsNestedBy:
|
||||
if 0 < len(rel.RelatedObjects):
|
||||
last_segment = rel.RelatedObjects[1]
|
||||
last_segment_dist_along = (
|
||||
last_segment.DesignParameters.StartDistAlong + last_segment.DesignParameters.HorizontalLength
|
||||
)
|
||||
last_segment_end_gradient = last_segment.DesignParameters.EndGradient
|
||||
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=last_segment_dist_along,
|
||||
HorizontalLength=0.0,
|
||||
StartHeight=0.0,
|
||||
StartGradient=last_segment_end_gradient,
|
||||
EndGradient=last_segment_end_gradient,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
segment = file.createIfcAlignmentSegment(GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters)
|
||||
elif layout.is_a("IfcAlignmentCant"):
|
||||
last_segment_dist_along = 0.0
|
||||
last_segment_cant_left = 0.0
|
||||
last_segment_cant_right = 0.0
|
||||
for rel in layout.IsNestedBy:
|
||||
if 0 < len(rel.RelatedObjects):
|
||||
last_segment = rel.RelatedObjects[1]
|
||||
last_segment_dist_along = (
|
||||
last_segment.DesignParameters.StartDistAlong + last_segment.DesignParameters.HorizontalLength
|
||||
)
|
||||
last_segment_cant_left = (
|
||||
last_segment.DesignParameters.EndCantLeft
|
||||
if last_segment.DesignParameters.EndCantLeft != None
|
||||
else last_segment.DesignParameters.StartCantLeft
|
||||
)
|
||||
last_segment_cant_right = (
|
||||
last_segment.DesignParameters.EndCantRight
|
||||
if last_segment.DesignParameters.EndCantRight != None
|
||||
else last_segment.DesignParameters.StartCantRight
|
||||
)
|
||||
|
||||
design_parameters = file.createIfcAlignmentCantSegment(
|
||||
StartDistAlong=last_segment_dist_along,
|
||||
HorizontalLength=0.0,
|
||||
StartCantLeft=last_segment_cant_left,
|
||||
StartCantRight=last_segment_cant_right,
|
||||
PredefinedType="CONSTANTCANT",
|
||||
)
|
||||
segment = file.createIfcAlignmentSegment(GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters)
|
||||
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=[segment], relating_object=layout)
|
||||
|
||||
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
|
||||
|
||||
has_zero_length_segment = False
|
||||
if curve.Segments != None and 0 < len(curve.Segments):
|
||||
last_segment = curve.Segments[-1]
|
||||
has_zero_length_segment = (
|
||||
True
|
||||
if last_segment.Transition == "DISCONTINUOUS" and last_segment.SegmentLength.wrappedValue == 0.0
|
||||
else False
|
||||
)
|
||||
|
||||
if not has_zero_length_segment:
|
||||
parent_curve = file.createIfcLine(
|
||||
Pnt=file.createIfcCartesianPoint(Coordinates=((0.0, 0.0))),
|
||||
Dir=file.createIfcVector(
|
||||
Orientation=file.createIfcDirection(DirectionRatios=((1.0, 0.0))),
|
||||
Magnitude=1.0,
|
||||
),
|
||||
)
|
||||
curve_segment = file.createIfcCurveSegment(
|
||||
Transition="DISCONTINUOUS",
|
||||
Placement=file.createIfcAxis2Placement2D(
|
||||
Location=file.createIfcCartesianPoint((0.0, 0.0)),
|
||||
RefDirection=file.createIfcDirection((1.0, 0.0)),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(0.0),
|
||||
ParentCurve=parent_curve,
|
||||
)
|
||||
curve.Segments = [
|
||||
curve_segment,
|
||||
]
|
||||
|
||||
name = f"{_get_segment_start_point_label(segment,None)} {ifcopenshell.util.stationing.station_as_string(file,0.0)}"
|
||||
ifcopenshell.api.alignment.add_stationing_referent(file, segment, curve, 0.0, 0.0, name=name)
|
||||
+30
-53
@@ -25,17 +25,17 @@ import math
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
def create_geometric_representation(file: ifcopenshell.file, alignment: entity_instance) -> None:
|
||||
def _create_geometric_representation(file: ifcopenshell.file, alignment: entity_instance) -> None:
|
||||
"""
|
||||
Create geometric representation for the alignment.
|
||||
Create geometric representation for the alignment and its nested layouts.
|
||||
|
||||
There are 5 different cases:
|
||||
There are 5 different cases (the IfcCurve created is indicated):
|
||||
|
||||
1) Horizontal only
|
||||
2) Horizontal + Vertical
|
||||
3) Horizontal + Vertical + Cant
|
||||
4) Vertical only (this occurs when horizontal is reused from a parent alignment)
|
||||
5) Vertical + Cant (this occurs when horizontal is reused from a parent alignment)
|
||||
1) Horizontal only -> IfcCompositeCurve
|
||||
2) Horizontal + Vertical -> IfcCompositeCurve and IfcGradientCurve
|
||||
3) Horizontal + Vertical + Cant -> IfcCompositeCurve and IfcSegmentedReferentCurve
|
||||
4) Vertical only (this occurs when horizontal is reused from a parent alignment) -> IfcGradientCurve
|
||||
5) Vertical + Cant (this occurs when horizontal is reused from a parent alignment) -> IfcSegmentedReferenceCurve
|
||||
|
||||
:param alignment: The alignment for which the representation is being created
|
||||
:return: None
|
||||
@@ -43,7 +43,7 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
|
||||
|
||||
expected_type = "IfcAlignment"
|
||||
if not alignment.is_a(expected_type):
|
||||
raise TypeError("Expected '{expected_type}' but got '{alignment.is_a()}'")
|
||||
raise TypeError(f"Expected {expected_type} but got {alignment.is_a()}")
|
||||
|
||||
placement = file.createIfcLocalPlacement(
|
||||
PlacementRelTo=None,
|
||||
@@ -60,11 +60,8 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
|
||||
if len(layouts) == 1 and len(children) == 0:
|
||||
assert layouts[0].is_a("IfcAlignmentHorizontal")
|
||||
# Horizontal only - IFC CT 4.1.7.1.1.1
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(file, layouts[0])
|
||||
composite_curve = file.createIfcCompositeCurve()
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[0], composite_curve)
|
||||
representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
composite_curve = file.createIfcCompositeCurve(Segments=[], SelfIntersect=False)
|
||||
representation = file.createIfcShapeRepresentation(
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="Axis",
|
||||
RepresentationType="Curve2D",
|
||||
@@ -75,12 +72,8 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
|
||||
# Horizontal and Vertical - IFC CT 4.1.7.1.1.1
|
||||
assert layouts[0].is_a("IfcAlignmentHorizontal")
|
||||
assert layouts[1].is_a("IfcAlignmentVertical")
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(file, layouts[0])
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(file, layouts[1])
|
||||
composite_curve = file.createIfcCompositeCurve()
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[0], composite_curve)
|
||||
representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
composite_curve = file.createIfcCompositeCurve(Segments=[], SelfIntersect=False)
|
||||
representation = file.createIfcShapeRepresentation(
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="FootPrint",
|
||||
RepresentationType="Curve2D",
|
||||
@@ -88,10 +81,8 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
|
||||
)
|
||||
ifcopenshell.api.geometry.assign_representation(file, alignment, representation)
|
||||
|
||||
gradient_curve = file.createIfcGradientCurve(BaseCurve=composite_curve)
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[1], gradient_curve)
|
||||
representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
gradient_curve = file.createIfcGradientCurve(Segments=[], BaseCurve=composite_curve, SelfIntersect=False)
|
||||
representation = file.createIfcShapeRepresentation(
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="Axis",
|
||||
RepresentationType="Curve3D",
|
||||
@@ -103,13 +94,8 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
|
||||
assert layouts[0].is_a("IfcAlignmentHorizontal")
|
||||
assert layouts[1].is_a("IfcAlignmentVertical")
|
||||
assert layouts[2].is_a("IfcAlignmentCant")
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(file, layouts[0])
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(file, layouts[1])
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(file, layouts[2])
|
||||
composite_curve = file.createIfcCompositeCurve()
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[0], composite_curve)
|
||||
representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
composite_curve = file.createIfcCompositeCurve(Segments=[], SelfIntersect=False)
|
||||
representation = file.createIfcShapeRepresentation(
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="FootPrint",
|
||||
RepresentationType="Curve2D",
|
||||
@@ -117,10 +103,10 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
|
||||
)
|
||||
ifcopenshell.api.geometry.assign_representation(file, alignment, representation)
|
||||
|
||||
gradient_curve = file.createIfcGradientCurve(BaseCurve=composite_curve)
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[1], gradient_curve)
|
||||
segmented_reference_curve = file.createIfcSegmentedReferenceCurve(BaseCurve=gradient_curve)
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[2], segmented_reference_curve)
|
||||
gradient_curve = file.createIfcGradientCurve(Segments=[], BaseCurve=composite_curve, SelfIntersect=False)
|
||||
segmented_reference_curve = file.createIfcSegmentedReferenceCurve(
|
||||
Segments=[], BaseCurve=gradient_curve, SelfIntersect=False
|
||||
)
|
||||
representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
@@ -132,11 +118,8 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
|
||||
else:
|
||||
# Reusing Horizontal - CT 4.1.4.4.1.2
|
||||
# Create a representation on the parent alignment
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(file, layouts[0])
|
||||
composite_curve = file.createIfcCompositeCurve()
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[0], composite_curve)
|
||||
representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
composite_curve = file.createIfcCompositeCurve(Segments=[], SelfIntersect=False)
|
||||
representation = file.createIfcShapeRepresentation(
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="FootPrint",
|
||||
RepresentationType="Curve2D",
|
||||
@@ -149,12 +132,9 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
|
||||
child_layouts = ifcopenshell.api.alignment.get_alignment_layouts(child_alignment)
|
||||
if len(child_layouts) == 1:
|
||||
assert child_layouts[0].is_a("IfcAlignmentVertical")
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(file, child_layouts[0])
|
||||
base_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
gradient_curve = file.createIfcGradientCurve(BaseCurve=base_curve)
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, child_layouts[0], gradient_curve)
|
||||
representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
gradient_curve = file.createIfcGradientCurve(Segments=[], BaseCurve=base_curve, SelfIntersect=False)
|
||||
representation = file.createIfcShapeRepresentation(
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="Axis",
|
||||
RepresentationType="Curve3D",
|
||||
@@ -164,15 +144,12 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
|
||||
elif len(child_layouts) == 2:
|
||||
assert child_layouts[0].is_a("IfcAlignmentVertical")
|
||||
assert child_layouts[1].is_a("IfcAlignmentCant")
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(file, child_layouts[0])
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(file, child_layouts[1])
|
||||
base_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
gradient_curve = file.createIfcGradientCurve(BaseCurve=base_curve)
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, child_layouts[0], gradient_curve)
|
||||
segmented_reference_curve = file.createIfcSegmentedReferenceCurve(BaseCurve=gradient_curve)
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, child_layouts[1], segmented_reference_curve)
|
||||
representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
gradient_curve = file.createIfcGradientCurve(Segments=[], BaseCurve=base_curve, SelfIntersect=False)
|
||||
segmented_reference_curve = file.createIfcSegmentedReferenceCurve(
|
||||
Segments=[], BaseCurve=gradient_curve, SelfIntersect=False
|
||||
)
|
||||
representation = file.creatIfcShapeRepresentation(
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="Axis",
|
||||
RepresentationType="Curve3D",
|
||||
@@ -0,0 +1,60 @@
|
||||
# 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
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell import entity_instance
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def __get_curve_segment_count(segment: entity_instance) -> int:
|
||||
"""
|
||||
returns the number of IfcCurveSegment that an IfcAlignmentSegment maps to.
|
||||
generally this is a 1 to 1 mapping, with helmert curve being the exception
|
||||
"""
|
||||
if segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
|
||||
return 2 if segment.DesignParameters.PredefinedType == "HELMERTCURVE" else 1
|
||||
elif segment.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
|
||||
return 1
|
||||
elif segment.DesignParameters.is_a("IfcAlignmentCantSegment"):
|
||||
return 2 if segment.DesignParameters.PredefinedType == "HELMERTCURVE" else 1
|
||||
|
||||
|
||||
def _get_mapped_segments(file: ifcopenshell.file, layout_segment: entity_instance) -> Sequence[entity_instance]:
|
||||
"""
|
||||
Finds the IfcCurveSegment related to segment
|
||||
"""
|
||||
expected_type = "IfcAlignmentSegment"
|
||||
if not layout_segment.is_a(expected_type):
|
||||
raise TypeError(f"Expected to see type '{expected_type}', instead received '{layout_segment.is_a()}'.")
|
||||
|
||||
layout = layout_segment.Nests[0].RelatingObject
|
||||
alignment = ifcopenshell.api.alignment.get_alignment(layout)
|
||||
curve = ifcopenshell.api.alignment.get_curve(alignment)
|
||||
|
||||
index = 0
|
||||
for seg in layout.IsNestedBy[0].RelatedObjects:
|
||||
index += __get_curve_segment_count(seg)
|
||||
if seg == layout_segment:
|
||||
break
|
||||
|
||||
segment_count = __get_curve_segment_count(layout_segment)
|
||||
if segment_count == 1:
|
||||
return (curve.Segments[index - segment_count], None)
|
||||
else:
|
||||
return (curve.Segments[index - segment_count], curve.Segment[index])
|
||||
@@ -0,0 +1,314 @@
|
||||
# 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 entity_instance
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
_horizontal_callback = None
|
||||
_vertical_callback = None
|
||||
_cant_callback = None
|
||||
|
||||
|
||||
def register_referent_name_callback(horizontal=None, vertical=None, cant=None):
|
||||
"""
|
||||
Referents are automatically created at the start of each horizontal, vertical, and cant segment.
|
||||
The referents represent key points in the alignment layout such as Point of Curvature, Point of Tangent, and others.
|
||||
Different juristicions use different naming systems for these key points.
|
||||
|
||||
The referent name callback functions provide a customizable method for naming these referents. If a callback is registered,
|
||||
it is called when creating the referent name, otherwise the default naming is used.
|
||||
|
||||
The callback function signature is
|
||||
|
||||
def mycallback(prev_segment : entity_instance, segment : entity_instance) -> str:
|
||||
|
||||
The callback function returns a string that is used in the referent name for the referent at the start of `segment`.
|
||||
The callback must accomodate the following cases:
|
||||
* prev_segment = None and segment != None - this indicates the last segment so the "End of Alignment" name is returned
|
||||
* prev_segment != None and segment == None - this indicates the first segment so the "Beginning of Alignment" name is returned
|
||||
* prev_segment != None and segment != None - this indicates an intermediate segment so a name representitive of the transition is returned
|
||||
|
||||
Setting any or all of the callbacks to None causes the default naming to be used.
|
||||
"""
|
||||
global _horizontal_callback
|
||||
_horizontal_callback = horizontal
|
||||
|
||||
global _vertical_callback
|
||||
_vertical_callback = vertical
|
||||
|
||||
global _cant_callback
|
||||
_cant_callback = cant
|
||||
|
||||
|
||||
def _horizontal_label(prev_segment: entity_instance, segment: entity_instance) -> str:
|
||||
if prev_segment == None and segment != None:
|
||||
label = "P.O.B."
|
||||
elif prev_segment != None and segment == None:
|
||||
label = "P.O.E."
|
||||
else:
|
||||
lookup_table = {
|
||||
"BLOSSCURVE": {
|
||||
"BLOSSCURVE": "xx",
|
||||
"CIRCULARARC": "S.C.",
|
||||
"CLOTHOID": "xx",
|
||||
"COSINECURVE": "xx",
|
||||
"CUBIC": "xx",
|
||||
"HELMERTCURVE": "xx",
|
||||
"LINE": "S.T.",
|
||||
"SINECURVE": "xx",
|
||||
"VIENNESEBEND": "xx",
|
||||
},
|
||||
"CIRCULARARC": {
|
||||
"BLOSSCURVE": "C.S.",
|
||||
"CIRCULARARC": "P.C.C.",
|
||||
"CLOTHOID": "C.S.",
|
||||
"COSINECURVE": "C.S.",
|
||||
"CUBIC": "C.S.",
|
||||
"HELMERTCURVE": "C.S.",
|
||||
"LINE": "P.T.",
|
||||
"SINECURVE": "C.S.",
|
||||
"VIENNESEBEND": "C.S.",
|
||||
},
|
||||
"CLOTHOID": {
|
||||
"BLOSSCURVE": "xx",
|
||||
"CIRCULARARC": "S.C.",
|
||||
"CLOTHOID": "xx",
|
||||
"COSINECURVE": "xx",
|
||||
"CUBIC": "xx",
|
||||
"HELMERTCURVE": "xx",
|
||||
"LINE": "S.T.",
|
||||
"SINECURVE": "xx",
|
||||
"VIENNESEBEND": "xx",
|
||||
},
|
||||
"COSINECURVE": {
|
||||
"BLOSSCURVE": "xx",
|
||||
"CIRCULARARC": "S.C.",
|
||||
"CLOTHOID": "xx",
|
||||
"COSINECURVE": "xx",
|
||||
"CUBIC": "xx",
|
||||
"HELMERTCURVE": "xx",
|
||||
"LINE": "S.T.",
|
||||
"SINECURVE": "xx",
|
||||
"VIENNESEBEND": "xx",
|
||||
},
|
||||
"CUBIC": {
|
||||
"BLOSSCURVE": "xx",
|
||||
"CIRCULARARC": "S.C.",
|
||||
"CLOTHOID": "xx",
|
||||
"COSINECURVE": "xx",
|
||||
"CUBIC": "xx",
|
||||
"HELMERTCURVE": "xx",
|
||||
"LINE": "S.T.",
|
||||
"SINECURVE": "xx",
|
||||
"VIENNESEBEND": "xx",
|
||||
},
|
||||
"HELMERTCURVE": {
|
||||
"BLOSSCURVE": "xx",
|
||||
"CIRCULARARC": "S.C.",
|
||||
"CLOTHOID": "xx",
|
||||
"COSINECURVE": "xx",
|
||||
"CUBIC": "xx",
|
||||
"HELMERTCURVE": "xx",
|
||||
"LINE": "S.T.",
|
||||
"SINECURVE": "xx",
|
||||
"VIENNESEBEND": "xx",
|
||||
},
|
||||
"LINE": {
|
||||
"BLOSSCURVE": "T.S.",
|
||||
"CIRCULARARC": "P.C.",
|
||||
"CLOTHOID": "T.S.",
|
||||
"COSINECURVE": "T.S.",
|
||||
"CUBIC": "T.S.",
|
||||
"HELMERTCURVE": "T.S.",
|
||||
"LINE": "P.I.",
|
||||
"SINECURVE": "T.S.",
|
||||
"VIENNESEBEND": "T.S.",
|
||||
},
|
||||
"SINECURVE": {
|
||||
"BLOSSCURVE": "xx",
|
||||
"CIRCULARARC": "S.C.",
|
||||
"CLOTHOID": "xx",
|
||||
"COSINECURVE": "xx",
|
||||
"CUBIC": "xx",
|
||||
"HELMERTCURVE": "xx",
|
||||
"LINE": "S.T.",
|
||||
"SINECURVE": "xx",
|
||||
"VIENNESEBEND": "xx",
|
||||
},
|
||||
"VIENNESEBEND": {
|
||||
"BLOSSCURVE": "xx",
|
||||
"CIRCULARARC": "S.C.",
|
||||
"CLOTHOID": "xx",
|
||||
"COSINECURVE": "xx",
|
||||
"CUBIC": "xx",
|
||||
"HELMERTCURVE": "xx",
|
||||
"LINE": "S.T.",
|
||||
"SINECURVE": "xx",
|
||||
"VIENNESEBEND": "xx",
|
||||
},
|
||||
}
|
||||
label = lookup_table[prev_segment.DesignParameters.PredefinedType][segment.DesignParameters.PredefinedType]
|
||||
|
||||
return label
|
||||
|
||||
|
||||
def _vertical_label(prev_segment: entity_instance, segment: entity_instance) -> str:
|
||||
if prev_segment == None and segment != None:
|
||||
label = "V.P.O.B."
|
||||
elif prev_segment != None and segment == None:
|
||||
label = "V.P.O.E."
|
||||
else:
|
||||
lookup_table = {
|
||||
"CIRCULARARC": {"CIRCULARARC": "xx", "CLOTHOID": "xx", "CONSTANTGRADIENT": "xx", "PARABOLICARC": "xx"},
|
||||
"CLOTHOID": {"CIRCULARARC": "xx", "CLOTHOID": "xx", "CONSTANTGRADIENT": "xx", "PARABOLICARC": "xx"},
|
||||
"CONSTANTGRADIENT": {
|
||||
"CIRCULARARC": "xx",
|
||||
"CLOTHOID": "xx",
|
||||
"CONSTANTGRADIENT": "P.V.I",
|
||||
"PARABOLICARC": "P.V.C.",
|
||||
},
|
||||
"PARABOLICARC": {
|
||||
"CIRCULARARC": "xx",
|
||||
"CLOTHOID": "xx",
|
||||
"CONSTANTGRADIENT": "P.V.T.",
|
||||
"PARABOLICARC": "V.C.C.",
|
||||
},
|
||||
}
|
||||
label = lookup_table[prev_segment.DesignParameters.PredefinedType][segment.DesignParameters.PredefinedType]
|
||||
|
||||
return label
|
||||
|
||||
|
||||
def _cant_label(prev_segment: entity_instance, segment: entity_instance) -> str:
|
||||
if prev_segment == None and segment != None:
|
||||
label = "C.P.O.B."
|
||||
elif prev_segment != None and segment == None:
|
||||
label = "C.P.O.E."
|
||||
else:
|
||||
lookup_table = {
|
||||
"BLOSSCURVE": {
|
||||
"BLOSSCURVE": "xx",
|
||||
"CONSTANTCANT": "xx",
|
||||
"COSINECURVE": "xx",
|
||||
"HELMERTCURVE": "xx",
|
||||
"LINEARTRANSITION": "xx",
|
||||
"SINECURVE": "xx",
|
||||
"VIENNESEBEND": "xx",
|
||||
},
|
||||
"CONSTANTCANT": {
|
||||
"BLOSSCURVE": "xx",
|
||||
"CONSTANTCANT": "xx",
|
||||
"COSINECURVE": "xx",
|
||||
"HELMERTCURVE": "xx",
|
||||
"LINEARTRANSITION": "xx",
|
||||
"SINECURVE": "xx",
|
||||
"VIENNESEBEND": "xx",
|
||||
},
|
||||
"COSINECURVE": {
|
||||
"BLOSSCURVE": "xx",
|
||||
"CONSTANTCANT": "xx",
|
||||
"COSINECURVE": "xx",
|
||||
"HELMERTCURVE": "xx",
|
||||
"LINEARTRANSITION": "xx",
|
||||
"SINECURVE": "xx",
|
||||
"VIENNESEBEND": "xx",
|
||||
},
|
||||
"HELMERTCURVE": {
|
||||
"BLOSSCURVE": "xx",
|
||||
"CONSTANTCANT": "xx",
|
||||
"COSINECURVE": "xx",
|
||||
"HELMERTCURVE": "xx",
|
||||
"LINEARTRANSITION": "xx",
|
||||
"SINECURVE": "xx",
|
||||
"VIENNESEBEND": "xx",
|
||||
},
|
||||
"LINEARTRANSITION": {
|
||||
"BLOSSCURVE": "xx",
|
||||
"CONSTANTCANT": "xx",
|
||||
"COSINECURVE": "xx",
|
||||
"HELMERTCURVE": "xx",
|
||||
"LINEARTRANSITION": "xx",
|
||||
"SINECURVE": "xx",
|
||||
"VIENNESEBEND": "xx",
|
||||
},
|
||||
"SINECURVE": {
|
||||
"BLOSSCURVE": "xx",
|
||||
"CONSTANTCANT": "xx",
|
||||
"COSINECURVE": "xx",
|
||||
"HELMERTCURVE": "xx",
|
||||
"LINEARTRANSITION": "xx",
|
||||
"SINECURVE": "xx",
|
||||
"VIENNESEBEND": "xx",
|
||||
},
|
||||
"VIENNESEBEND": {
|
||||
"BLOSSCURVE": "xx",
|
||||
"CONSTANTCANT": "xx",
|
||||
"COSINECURVE": "xx",
|
||||
"HELMERTCURVE": "xx",
|
||||
"LINEARTRANSITION": "xx",
|
||||
"SINECURVE": "xx",
|
||||
"VIENNESEBEND": "xx",
|
||||
},
|
||||
}
|
||||
label = lookup_table[prev_segment.DesignParameters.PredefinedType][segment.DesignParameters.PredefinedType]
|
||||
|
||||
return label
|
||||
|
||||
|
||||
def _get_segment_start_point_label(prev_segment: entity_instance, segment: entity_instance) -> str:
|
||||
"""
|
||||
Returns the label for the start point of a segment. Typically used in the name of an IfcReferent
|
||||
"""
|
||||
if prev_segment != None and segment != None and prev_segment.is_a() != segment.is_a():
|
||||
raise TypeError(
|
||||
f"Expected entity type to be the same type, instead received {prev_segment.is_a()} and {segment.is_a()}"
|
||||
)
|
||||
|
||||
expected_types = ["IfcAlignmentHorizontalSegment", "IfcAlignmentVerticalSegment", "IfcAlignmentCantSegment"]
|
||||
if prev_segment != None and not prev_segment.DesignParameters.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected prev_segment.DesignParameters type to be one of {[_ for _ in expected_types]}, instead received {prev_segment.DesignParameters.is_a()}"
|
||||
)
|
||||
if segment != None and not segment.DesignParameters.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected segment.DesignParameters type to be one of {[_ for _ in expected_types]}, instead received {segment.DesignParameters.is_a()}"
|
||||
)
|
||||
|
||||
s = segment if segment != None else prev_segment
|
||||
if s.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
|
||||
global _horizontal_callback
|
||||
if _horizontal_callback:
|
||||
label = _horizontal_callback(prev_segment, segment)
|
||||
else:
|
||||
label = _horizontal_label(prev_segment, segment)
|
||||
elif s.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
|
||||
global _vertical_callback
|
||||
if _vertical_callback:
|
||||
label = _vertical_callback(prev_segment, segment)
|
||||
else:
|
||||
label = _vertical_label(prev_segment, segment)
|
||||
elif s.DesignParameters.is_a("IfcAlignmentCantSegment"):
|
||||
global _cant_callback
|
||||
if _cant_callback:
|
||||
label = _cant_callback(prev_segment, segment)
|
||||
else:
|
||||
label = _cant_label(prev_segment, segment)
|
||||
|
||||
return label
|
||||
+1
-1
@@ -380,7 +380,7 @@ def _map_viennese_bend(
|
||||
raise NotImplementedError("VIENNESEBEND not implemented")
|
||||
|
||||
|
||||
def map_alignment_cant_segment(
|
||||
def _map_alignment_cant_segment(
|
||||
file: ifcopenshell.file, segment: entity_instance, rail_head_distance: float
|
||||
) -> Sequence[entity_instance]:
|
||||
"""
|
||||
+1
-1
@@ -401,7 +401,7 @@ def _map_viennese_bend(file: ifcopenshell.file, design_parameters: entity_instan
|
||||
raise NotImplementedError("VIENNESEBEND not implemented")
|
||||
|
||||
|
||||
def map_alignment_horizontal_segment(file: ifcopenshell.file, segment: entity_instance) -> Sequence[entity_instance]:
|
||||
def __map_alignment_horizontal_segment(file: ifcopenshell.file, segment: entity_instance) -> Sequence[entity_instance]:
|
||||
"""
|
||||
Creates IfcCurveSegment entities for the represention of the supplied IfcAlignmentHorizontalSegment business logic entity instance.
|
||||
A pair of entities is returned because a single business logic segment of type HELMERTCURVE maps to two representaiton entities.
|
||||
+1
-1
@@ -189,7 +189,7 @@ def _map_clothoid(file: ifcopenshell.file, design_parameters: entity_instance) -
|
||||
raise NotImplementedError("mapping for IfcVerticalSegment.CLOTHOID not implemented")
|
||||
|
||||
|
||||
def map_alignment_vertical_segment(file: ifcopenshell.file, segment: entity_instance) -> Sequence[entity_instance]:
|
||||
def _map_alignment_vertical_segment(file: ifcopenshell.file, segment: 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.
|
||||
+4
-4
@@ -25,7 +25,7 @@ import numpy as np
|
||||
import math
|
||||
|
||||
|
||||
def update_curve_segment_transition_code(prev_segment: entity_instance, segment: entity_instance) -> None:
|
||||
def _update_curve_segment_transition_code(prev_segment: entity_instance, segment: entity_instance) -> None:
|
||||
"""
|
||||
Updates IfcCurveSegment.Transition of prev_segment based on a comparison of
|
||||
the position, ref. direction, and curvature at the end of the prev_segment and the start of segment.
|
||||
@@ -62,9 +62,9 @@ def update_curve_segment_transition_code(prev_segment: entity_instance, segment:
|
||||
s = segment_evaluator.evaluate(segment_fn.start())
|
||||
start = np.array(s)
|
||||
|
||||
same_position = True if np.allclose(end[:3], start[:3]) else False
|
||||
same_gradient = True if np.allclose(end[:0], start[:0]) else False
|
||||
same_curvature = True if np.allclose(end[3:], start[3:]) else False
|
||||
same_position = True if np.allclose(end[:3, 3], start[:3, 3]) else False
|
||||
same_gradient = True if np.allclose(end[:3, 0], start[:3, 0]) else False
|
||||
same_curvature = True if np.allclose(end[3:, :3], start[3:, :3]) else False
|
||||
|
||||
if same_position:
|
||||
prev_segment.Transition = "CONTINUOUS"
|
||||
@@ -1,76 +0,0 @@
|
||||
# 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
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.geom
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def add_segment_to_curve(file: ifcopenshell.file, segment: entity_instance, composite_curve: entity_instance) -> None:
|
||||
"""
|
||||
Adds a segment to a composite curve. The segment must not belong to another composite curve (len(segment.UsingCurves) == 0).
|
||||
If the composite curve does not have any segments, the segment is simply appended to the curve.
|
||||
If the composite curve has segments, the position, ref. direction, and curvature at the end of the last segment is
|
||||
compared to the position, ref. direction and curvature at the start of the new segment. The IfcCurveSegment.Transition of the last curve segment is updated.
|
||||
|
||||
:param segment: The segment to be added to the curve
|
||||
:param composite_curve: The curve receiving the segment
|
||||
:return: None
|
||||
"""
|
||||
expected_type = "IfcCurveSegment"
|
||||
if not segment.is_a(expected_type):
|
||||
raise TypeError(f"Expected to see '{expected_type}', instead received '{segment.is_a()}'.")
|
||||
|
||||
if 0 < len(segment.UsingCurves):
|
||||
raise TypeError("IfcCurveSegment cannot belong to other curves")
|
||||
|
||||
expected_type = "IfcCompositeCurve"
|
||||
if not composite_curve.is_a(expected_type):
|
||||
raise TypeError(f"Expected to see '{expected_type}', instead received '{composite_curve.is_a()}'.")
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
if composite_curve.Segments == None or 0 == len(composite_curve.Segments):
|
||||
# this is the first segment so just add it
|
||||
if composite_curve.Segments == None:
|
||||
composite_curve.Segments = []
|
||||
|
||||
# the last segment is always discontinuous
|
||||
segment.Transition = "DISCONTINUOUS"
|
||||
|
||||
composite_curve.Segments += (segment,)
|
||||
assert len(segment.UsingCurves) == 1
|
||||
else:
|
||||
zero_length_segment = (
|
||||
ifcopenshell.api.alignment.remove_zero_length_segment(file, composite_curve)
|
||||
if ifcopenshell.api.alignment.has_zero_length_segment(composite_curve)
|
||||
else None
|
||||
)
|
||||
|
||||
prev_segment = composite_curve.Segments[-1]
|
||||
|
||||
# the last segment is always discontinuous
|
||||
segment.Transition = "DISCONTINUOUS"
|
||||
|
||||
# must add the new segment to the curve before updating the transition code
|
||||
composite_curve.Segments += (segment,)
|
||||
|
||||
ifcopenshell.api.alignment.update_curve_segment_transition_code(prev_segment, segment)
|
||||
|
||||
if zero_length_segment:
|
||||
ifcopenshell.api.alignment.add_segment_to_curve(zero_length_segment, composite_curve)
|
||||
@@ -1,51 +0,0 @@
|
||||
# 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
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.nest
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def add_segment_to_layout(file: ifcopenshell.file, alignment: entity_instance, segment: entity_instance) -> None:
|
||||
"""
|
||||
Adds a segment to a layout alignment (horizontal, vertical, or cant)
|
||||
|
||||
:param alignment: The alignment
|
||||
:param segment: The segment to be appended
|
||||
:return: None
|
||||
"""
|
||||
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
|
||||
if not alignment.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{alignment.is_a()}"
|
||||
)
|
||||
|
||||
if not (segment.is_a("IfcAlignmentSegment")):
|
||||
raise TypeError(f"Expected to see IfcAlignmentSegment, instead received '{segment.is_a()}.")
|
||||
|
||||
zero_length_segment = (
|
||||
ifcopenshell.api.alignment.remove_zero_length_segment(file, alignment)
|
||||
if ifcopenshell.api.alignment.has_zero_length_segment(alignment)
|
||||
else None
|
||||
)
|
||||
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=[segment], relating_object=alignment)
|
||||
|
||||
if zero_length_segment:
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=[zero_length_segment], relating_object=alignment)
|
||||
@@ -0,0 +1,137 @@
|
||||
# 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
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.util.stationing
|
||||
import ifcopenshell.guid
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell import ifcopenshell_wrapper
|
||||
import numpy as np
|
||||
|
||||
|
||||
def add_stationing_referent(
|
||||
file: ifcopenshell.file,
|
||||
element: entity_instance,
|
||||
basis_curve: entity_instance,
|
||||
distance_along: float,
|
||||
station: float,
|
||||
name: str,
|
||||
) -> entity_instance:
|
||||
"""
|
||||
Adds an IfcReferent to the element with the Pset_Stationing property set.
|
||||
If element is an IfcAlignment, IfcReferent.PredefinedType is set to "STATION", otherwise "POSITION"
|
||||
|
||||
:param element: the element to receive the referent, expected to be an IfcAlignment or IfcAlignmentSegment
|
||||
:param basis_curve: the basis curve for positining
|
||||
:param distance_along: distance along the basis curve
|
||||
:param station: station value
|
||||
:param name: name to assign to IfcReferent.Name, typically a stringized version of the station value
|
||||
:return: referent
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
alignment = model.by_type("IfcAlignment")[0]
|
||||
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
ifcopenshell.api.alignment.add_stationing_referent(model,entity=alignment,basis_curve=basis_curve,distance_along=0.0,station=100.0)
|
||||
"""
|
||||
object_placement = None
|
||||
representation = None
|
||||
if basis_curve:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
fn = ifcopenshell_wrapper.map_shape(settings, basis_curve.wrapped_data)
|
||||
evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, fn)
|
||||
p = evaluator.evaluate(distance_along * unit_scale)
|
||||
p = np.array(p)
|
||||
|
||||
x = float(p[0, 3]) / unit_scale
|
||||
y = float(p[1, 3]) / unit_scale
|
||||
z = float(p[2, 3]) / unit_scale
|
||||
|
||||
rx = float(p[0, 0])
|
||||
ry = float(p[1, 0])
|
||||
rz = float(p[2, 0])
|
||||
|
||||
ax = float(p[0, 2])
|
||||
ay = float(p[1, 2])
|
||||
az = float(p[2, 2])
|
||||
|
||||
object_placement = file.createIfcLinearPlacement(
|
||||
RelativePlacement=file.createIfcAxis2PlacementLinear(
|
||||
Location=file.createIfcPointByDistanceExpression(
|
||||
DistanceAlong=file.createIfcLengthMeasure(distance_along),
|
||||
OffsetLateral=None,
|
||||
OffsetVertical=None,
|
||||
OffsetLongitudinal=None,
|
||||
BasisCurve=basis_curve,
|
||||
)
|
||||
),
|
||||
CartesianPosition=file.createIfcAxis2Placement3D(
|
||||
Location=file.createIfcCartesianPoint(((x, y, z))),
|
||||
Axis=file.createIfcDirection((ax, ay, az)),
|
||||
RefDirection=file.createIfcDirection((rx, ry, rz)),
|
||||
),
|
||||
)
|
||||
# this commented out code is what you would do to add a geometric representation of the referent
|
||||
# the example is a circle. a better way would be to pass a representation into the function
|
||||
# representation = file.create_entity(
|
||||
# name="IfcCircle",
|
||||
# position=file.createIfcAxis2Placement2D(Location=file.createIfcCartesianPoint(Coordinates=(0.0, 0.0)),
|
||||
# radius=1.0)
|
||||
# )
|
||||
|
||||
# create referent for the station
|
||||
predefined_type = "STATION" if element.is_a("IfcAlignment") else "POSITION"
|
||||
referent = file.createIfcReferent(
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=name,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=object_placement,
|
||||
Representation=representation,
|
||||
PredefinedType=predefined_type,
|
||||
)
|
||||
pset_stationing = ifcopenshell.api.pset.add_pset(file, product=referent, name="Pset_Stationing")
|
||||
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties={"Station": station})
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=[referent], relating_object=element)
|
||||
|
||||
alignment = element
|
||||
|
||||
if element.is_a("IfcAlignmentSegment"):
|
||||
layout = element.Nests[0].RelatingObject
|
||||
alignment = ifcopenshell.api.alignment.get_alignment(layout)
|
||||
|
||||
if len(alignment.Positions) == 0:
|
||||
rel_positions = file.createIfcRelPositions(
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
RelatingPositioningElement=alignment,
|
||||
RelatedProducts=[
|
||||
referent,
|
||||
],
|
||||
)
|
||||
else:
|
||||
alignment.Positions[0].RelatedProducts += (referent,)
|
||||
|
||||
return referent
|
||||
@@ -1,83 +0,0 @@
|
||||
# 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
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.util.stationing
|
||||
import ifcopenshell.guid
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def add_stationing_to_alignment(
|
||||
file: ifcopenshell.file, alignment: entity_instance, start_station: float, plus_seperator=3, accuracy=3
|
||||
) -> None:
|
||||
"""
|
||||
Adds stationing to an alignment by creating an IfcReferent with the Pset_Stationing property set to establish the stationing at the start of the alignment.
|
||||
Note - this function assumes the stationing has not been previously defined
|
||||
|
||||
:param alignment: the alignment to be stationed
|
||||
:param start_station: station value at the start of the alignment
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
alignment = model.by_type("IfcAlignment")[0]
|
||||
ifcopenshell.api.alignment.add_stationing_to_alignment(model,alignment=alignment,start_station=100.0)
|
||||
"""
|
||||
# this commented out code is what you would do to add a geometric representation of the referent
|
||||
# the example is a circle. a better way would be to pass a representation into the function
|
||||
object_placement = None
|
||||
representation = None
|
||||
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
if basis_curve:
|
||||
object_placement = file.createIfcLinearPlacement(
|
||||
RelativePlacement=file.createIfcAxis2PlacementLinear(
|
||||
Location=file.createIfcPointByDistanceExpression(
|
||||
DistanceAlong=file.createIfcLengthMeasure(0.0),
|
||||
OffsetLateral=None,
|
||||
OffsetVertical=None,
|
||||
OffsetLongitudinal=None,
|
||||
BasisCurve=basis_curve,
|
||||
)
|
||||
),
|
||||
CartesianPosition=None,
|
||||
)
|
||||
# representation = file.create_entity(
|
||||
# name="IfcCircle",
|
||||
# position=file.createIfcAxis2Placement2D(Location=file.createIfcCartesianPoint(Coordinates=(0.0, 0.0)),
|
||||
# radius=1.0)
|
||||
# )
|
||||
|
||||
# create referent for start station
|
||||
start_referent = file.createIfcReferent(
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=ifcopenshell.util.stationing.station_as_string(start_station, plus_seperator, accuracy),
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=object_placement,
|
||||
Representation=representation,
|
||||
PredefinedType="STATION",
|
||||
)
|
||||
pset_stationing = ifcopenshell.api.pset.add_pset(file, product=start_referent, name="Pset_Stationing")
|
||||
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties={"Station": start_station})
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=[start_referent], relating_object=alignment)
|
||||
@@ -1,199 +0,0 @@
|
||||
# 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
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.api.root
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.stationing
|
||||
import ifcopenshell.api
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def _move_vertical_to_child_alignment(
|
||||
file: ifcopenshell.file, parent_alignment: entity_instance, vertical_alignment: entity_instance
|
||||
):
|
||||
"""
|
||||
Creates a new child alignment and aggregates it to the parent alignment. Moves the vertical alignment from the parent
|
||||
alignment to the child alignment. Also moves the "Axis/Curve3D" representation to the child alignment, if present.
|
||||
This function supports the transition of vertical alignment between CT 4.1.4.4.1.1 and 4.1.4.4.1.2 because a subsequent
|
||||
vertical alignment is being added and the Alignment Layout - Reusing Horizontal Layout concept applies.
|
||||
"""
|
||||
# unhook the vertical alignment from the parent alignment
|
||||
ifcopenshell.api.nest.unassign_object(file, related_objects=[vertical_alignment])
|
||||
|
||||
# create the child alignment
|
||||
child_alignment = ifcopenshell.api.root.create_entity(
|
||||
file, ifc_class="IfcAlignment", name=f"Child of {parent_alignment.Name}"
|
||||
)
|
||||
|
||||
# nest the vertical alignment onto the child alignment
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=[vertical_alignment], relating_object=child_alignment)
|
||||
|
||||
# aggreage the child alignment to the parent alignment
|
||||
ifcopenshell.api.aggregate.assign_object(file, products=[child_alignment], relating_object=parent_alignment)
|
||||
|
||||
# if the parent alignment has a representation, move the Axis/Curve3D represention to the child alignment
|
||||
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
|
||||
if base_curve:
|
||||
representations = ifcopenshell.util.representation.get_representations_iter(parent_alignment)
|
||||
for representation in representations:
|
||||
if representation.RepresentationIdentifier == "Axis" and representation.RepresentationType == "Curve3D":
|
||||
ifcopenshell.api.geometry.unassign_representation(file, parent_alignment, representation)
|
||||
ifcopenshell.api.geometry.assign_representation(file, child_alignment, representation)
|
||||
break
|
||||
|
||||
|
||||
def add_vertical_alignment(
|
||||
file: ifcopenshell.file, parent_alignment: entity_instance, vertical_alignment: entity_instance
|
||||
) -> None:
|
||||
"""
|
||||
Adds a vertical alignment to a previously created alignment.
|
||||
|
||||
If this is the first vertical alignment assigned to the parent_alignment the IFC CT 4.1.4.4.1.1 Alignment Layout - Horizontal, Vertical and Cant
|
||||
is followed. If this is the second or subsequent vertical alignment assigned to the parent_alignment the
|
||||
IFC CT 4.1.4.4.1.2 Alignment Layout - Reusing Horizontal Layout is followed.
|
||||
|
||||
When the second vertical alignment is added, the structure of the IFC model must transition from one concept template to the other.
|
||||
Specifically, the following occurs:
|
||||
|
||||
1) The first child IfcAlignment is created and is IfcRelAggregates with the parent alignment.
|
||||
2) The first vertical alignment is unassigned from the IfcRelNests of the parent alignment and assigned to the new child alignment IfcRelNests
|
||||
3) A second child IfcAlignment is created ant is is IfcRelAggregates with the parent alignment.
|
||||
4) The vertical_alignment is assigned to the second child alignment
|
||||
|
||||
For the third and subsequent vertical alignments, a new child alignment is created and aggregated to the parent alignment and an IfcAlignmentVertical is created
|
||||
from vpoints and lengths and assigned to the new child alignment.
|
||||
|
||||
If the parent_alignment has a geometric representation, a geometric representation will be created for the vertical alignment.
|
||||
|
||||
:param parent_alignment: The parent alignment
|
||||
:param vertical_alignment: The vertical alignment to be added
|
||||
:return: None
|
||||
"""
|
||||
|
||||
# get all the child alignments under alignment
|
||||
child_alignments = [
|
||||
c for c in ifcopenshell.util.element.get_decomposition(parent_alignment) if c.is_a("IfcAlignment")
|
||||
]
|
||||
|
||||
# Get all the IfcAlignmentVertical that are nesting alignment (there should be 0 or 1)
|
||||
# if 0, alignment is just horizontal and we are adding the first vertical so it will nest to the alignment,
|
||||
# or there are multiple vertical and they nest to the aggregated child alignments
|
||||
# if 1, there is one vertical alignments. Move it to a child alignment
|
||||
vertical_alignments_nesting_alignment = [
|
||||
c for c in ifcopenshell.util.element.get_components(parent_alignment) if c.is_a("IfcAlignmentVertical")
|
||||
]
|
||||
|
||||
# move the vertical alignment to a child alignment because there is going to be more than one vertical
|
||||
assert len(vertical_alignments_nesting_alignment) == 0 or len(vertical_alignments_nesting_alignment) == 1
|
||||
for vertical_alignment_nesting_alignment in vertical_alignments_nesting_alignment:
|
||||
_move_vertical_to_child_alignment(file, parent_alignment, vertical_alignment_nesting_alignment)
|
||||
|
||||
if len(child_alignments) == 0 and len(vertical_alignments_nesting_alignment) == 0:
|
||||
# this is the first vertical alignment so nest it into the parent alignment (IFC CT 4.1.4.4.1.1)
|
||||
ifcopenshell.api.nest.assign_object(
|
||||
file, related_objects=[vertical_alignment], relating_object=parent_alignment
|
||||
)
|
||||
|
||||
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
|
||||
if base_curve:
|
||||
# the parent alignment has a Representation so create a representation for the vertical
|
||||
gradient_curve = file.create_entity(
|
||||
type="IfcGradientCurve", Segments=[], SelfIntersect=False, BaseCurve=base_curve, EndPoint=None
|
||||
)
|
||||
|
||||
# using the business logic definition of vertical_alignment, create the curve segments and assign to gradient_curve
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, vertical_alignment, gradient_curve)
|
||||
|
||||
# Per IFC CT 4.1.7.1.1.1, the shape representation for Horizontal geometry only is
|
||||
# RepresentationIdentifier="Axis" and RepresentationType="Curve2D".
|
||||
# However, per IFC CT 4.1.7.1.1.2 and 3 the shape represenation with Horizontal, Vertical and Cant
|
||||
# is RepresentationIdentifier="FootPrint" and RepresentationType="Curve2D" for the horizontal and
|
||||
# RepresentationIdentifier="Axis" and RepresentationType="Curve3D" for the 2.5D curve.
|
||||
# Since the alignment is transitioning from horizontal only to horizontal+vertical, the
|
||||
# RepresentationIdentifier must change from "Axis" to "FootPrint"
|
||||
representations = ifcopenshell.util.representation.get_representations_iter(parent_alignment)
|
||||
for representation in representations:
|
||||
if representation.RepresentationIdentifier == "Axis" and representation.RepresentationType == "Curve2D":
|
||||
representation.RepresentationIdentifier = "FootPrint"
|
||||
break
|
||||
|
||||
# create the Axis,Curve3D representation
|
||||
axis_geom_subcontext = ifcopenshell.api.alignment.get_axis_subcontext(file)
|
||||
axis3d_shape_representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="Axis",
|
||||
RepresentationType="Curve3D",
|
||||
Items=(gradient_curve,),
|
||||
)
|
||||
|
||||
ifcopenshell.api.geometry.assign_representation(file, parent_alignment, axis3d_shape_representation)
|
||||
else:
|
||||
# there are multiple vertical reusing the horizontal (IFC CT 4.1.4.4.1.2)
|
||||
# this is the second or subsequent vertical reusing the horizontal
|
||||
|
||||
# create a new child alignment for the new vertical
|
||||
child_alignment = file.create_entity(
|
||||
type="IfcAlignment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=f"Child of {parent_alignment.Name}",
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
PredefinedType=None,
|
||||
)
|
||||
|
||||
# Aggregate the child alignment to the parent alignment
|
||||
ifcopenshell.api.aggregate.assign_object(file, (child_alignment,), parent_alignment)
|
||||
|
||||
# nest the vertical under the child alignment
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=[vertical_alignment], relating_object=child_alignment)
|
||||
|
||||
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
|
||||
if base_curve:
|
||||
child_alignment.ObjectPlacement = parent_alignment.ObjectPlacement
|
||||
|
||||
# the parent alignment has a Representation so create a representation for the vertical
|
||||
gradient_curve = file.create_entity(
|
||||
type="IfcGradientCurve", Segments=[], SelfIntersect=False, BaseCurve=base_curve, EndPoint=None
|
||||
)
|
||||
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, vertical_alignment, gradient_curve)
|
||||
|
||||
axis_geom_subcontext = ifcopenshell.api.alignment.get_axis_subcontext(file)
|
||||
|
||||
# create the Curve3D representation
|
||||
axis3d_shape_representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="Axis",
|
||||
RepresentationType="Curve3D",
|
||||
Items=(gradient_curve,),
|
||||
)
|
||||
|
||||
# add the representation to the child alignment
|
||||
ifcopenshell.api.geometry.assign_representation(file, child_alignment, axis3d_shape_representation)
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
# 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
|
||||
import ifcopenshell.api.alignment.add_vertical_alignment
|
||||
from ifcopenshell import entity_instance
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
def add_vertical_alignment_by_pi_method(
|
||||
file: ifcopenshell.file,
|
||||
parent_alignment: entity_instance,
|
||||
vpoints: Sequence[Sequence[float]],
|
||||
lengths: Sequence[float],
|
||||
) -> None:
|
||||
"""
|
||||
Adds a vertical alignment to a previously created alignment using the PI method.
|
||||
|
||||
If this is the first vertical alignment assigned to the parent_alignment the IFC CT 4.1.4.4.1.1 Alignment Layout - Horizontal, Vertical and Cant
|
||||
is followed. If this is the second or subsequent vertical alignment assigned to the parent_alignment the
|
||||
IFC CT 4.1.4.4.1.2 Alignment Layout - Reusing Horizontal Layout is followed.
|
||||
|
||||
When the second vertical alignment is added, the structure of the IFC model must transition from one concept template to the other.
|
||||
Specifically, the following occurs:
|
||||
|
||||
1) The first child IfcAlignment is created and is IfcRelAggregates with the parent alignment.
|
||||
2) The first vertical alignment is unassigned from the IfcRelNests of the parent alignment and assigned to the new child alignment IfcRelNests
|
||||
3) A second child IfcAlignment is created and it is IfcRelAggregates with the parent alignment.
|
||||
4) An IfcAlignmentVertical is created from vpoints and lengths and it is assigned to the second child alignment
|
||||
|
||||
For the third and subsequent vertical alignments, a new child alignment is created and aggregated to the parent alignment and an IfcAlignmentVertical is created
|
||||
from vpoints and lengths and assigned to the new child alignment.
|
||||
|
||||
If the parent_alignment has a geometric representation, a geometric representation will be created for the vertical alignment.
|
||||
|
||||
:param parent_alignment: The parent alignment
|
||||
:param vpoints: A sequence of (D,Z) points where D is distance along horizontal and Z is elevation
|
||||
:param: lengths: Lengths of parabolic vertical curves occuring at each VPI
|
||||
:return: None
|
||||
"""
|
||||
vertical_alignment = ifcopenshell.api.alignment.create_vertical_alignment_by_pi_method(
|
||||
file, parent_alignment.Name, vpoints, lengths
|
||||
)
|
||||
ifcopenshell.api.alignment.add_vertical_alignment(file, parent_alignment, vertical_alignment)
|
||||
@@ -0,0 +1,192 @@
|
||||
# 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
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.api
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
from ifcopenshell.api.alignment._add_zero_length_segment import _add_zero_length_segment
|
||||
|
||||
|
||||
def _move_vertical_layout_to_child_alignment(
|
||||
file: ifcopenshell.file, parent_alignment: entity_instance, vertical_layout: entity_instance
|
||||
):
|
||||
"""
|
||||
Creates a new child alignment and aggregates it to the parent alignment. Moves the vertical alignment from the parent
|
||||
alignment to the child alignment. Also moves the "Axis/Curve3D" representation to the child alignment, if present.
|
||||
This function supports the transition of vertical alignment between CT 4.1.4.4.1.1 and 4.1.4.4.1.2 because a subsequent
|
||||
vertical alignment is being added and the Alignment Layout - Reusing Horizontal Layout concept applies.
|
||||
"""
|
||||
# unhook the vertical layout from the parent alignment
|
||||
ifcopenshell.api.nest.unassign_object(file, related_objects=[vertical_layout])
|
||||
|
||||
# create the child alignment
|
||||
child_alignment = file.createIfcAlignment(
|
||||
GlobalId=ifcopenshell.guid.new(), Name=f"Child of {parent_alignment.Name}"
|
||||
)
|
||||
|
||||
# nest the vertical layout onto the child alignment
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=[vertical_layout], relating_object=child_alignment)
|
||||
|
||||
# aggreage the child alignment to the parent alignment
|
||||
ifcopenshell.api.aggregate.assign_object(file, products=[child_alignment], relating_object=parent_alignment)
|
||||
|
||||
# if the parent alignment has a representation, move the Axis/Curve3D represention to the child alignment
|
||||
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
|
||||
if base_curve:
|
||||
representations = ifcopenshell.util.representation.get_representations_iter(parent_alignment)
|
||||
for representation in representations:
|
||||
if representation.RepresentationIdentifier == "Axis" and representation.RepresentationType == "Curve3D":
|
||||
ifcopenshell.api.geometry.unassign_representation(file, parent_alignment, representation)
|
||||
ifcopenshell.api.geometry.assign_representation(file, child_alignment, representation)
|
||||
break
|
||||
|
||||
|
||||
def add_vertical_layout(file: ifcopenshell.file, parent_alignment: entity_instance) -> entity_instance:
|
||||
"""
|
||||
Adds a vertical layout to a previously created alignment.
|
||||
|
||||
If this is the first vertical layout assigned to the parent_alignment the IFC CT 4.1.4.4.1.1 Alignment Layout - Horizontal, Vertical and Cant
|
||||
is followed. If this is the second or subsequent vertical layout assigned to the parent_alignment the
|
||||
IFC CT 4.1.4.4.1.2 Alignment Layout - Reusing Horizontal Layout is followed.
|
||||
|
||||
When the second vertical layout is added, the structure of the IFC model must transition from one concept template to the other.
|
||||
Specifically, the following occurs:
|
||||
|
||||
1) The first child IfcAlignment is created and is IfcRelAggregates with the parent alignment.
|
||||
2) The first vertical layout is unassigned from the IfcRelNests of the parent alignment and is IfcRelNests to the new child alignment.
|
||||
3) A second child IfcAlignment is created and it is IfcRelAggregates with the parent alignment.
|
||||
4) The vertical layout is IfcRelNests to the second child alignment
|
||||
|
||||
For the third and subsequent vertical layouts, a new child alignment is created and aggregated to the parent alignment.
|
||||
|
||||
A zero segment length terminated IfcGradientCurve is created for the new vertical layout
|
||||
|
||||
:param parent_alignment: The parent alignment
|
||||
:return: The new vertical layout, including the manditory zero length segment
|
||||
"""
|
||||
|
||||
vertical_layout = file.createIfcAlignmentVertical(GlobalId=ifcopenshell.guid.new())
|
||||
|
||||
# get all the child alignments under alignment
|
||||
child_alignments = [
|
||||
c for c in ifcopenshell.util.element.get_decomposition(parent_alignment) if c.is_a("IfcAlignment")
|
||||
]
|
||||
|
||||
# Get all the IfcAlignmentVertical that are nesting alignment (there should be 0 or 1)
|
||||
# if 0, alignment is just horizontal and we are adding the first vertical so it will nest to the alignment,
|
||||
# or there are multiple vertical and they nest to the aggregated child alignments
|
||||
# if 1, there is one vertical alignments. Move it to a child alignment
|
||||
vertical_layouts_nesting_alignment = [
|
||||
c for c in ifcopenshell.util.element.get_components(parent_alignment) if c.is_a("IfcAlignmentVertical")
|
||||
]
|
||||
|
||||
# move the vertical layout to a child alignment because there is going to be more than one vertical
|
||||
assert len(vertical_layouts_nesting_alignment) == 0 or len(vertical_layouts_nesting_alignment) == 1
|
||||
for vertical_layout_nesting_alignment in vertical_layouts_nesting_alignment:
|
||||
_move_vertical_layout_to_child_alignment(file, parent_alignment, vertical_layout_nesting_alignment)
|
||||
|
||||
if len(child_alignments) == 0 and len(vertical_layouts_nesting_alignment) == 0:
|
||||
# this is the first vertical layout so nest it into the parent alignment (IFC CT 4.1.4.4.1.1)
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=[vertical_layout], relating_object=parent_alignment)
|
||||
|
||||
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
|
||||
|
||||
# the parent alignment has a Representation so create a representation for the vertical
|
||||
gradient_curve = file.createIfcGradientCurve(
|
||||
Segments=[], SelfIntersect=False, BaseCurve=base_curve, EndPoint=None
|
||||
)
|
||||
|
||||
# Per IFC CT 4.1.7.1.1.1, the shape representation for Horizontal geometry only is
|
||||
# RepresentationIdentifier="Axis" and RepresentationType="Curve2D".
|
||||
# However, per IFC CT 4.1.7.1.1.2 and 3 the shape represenation with Horizontal, Vertical and Cant
|
||||
# is RepresentationIdentifier="FootPrint" and RepresentationType="Curve2D" for the horizontal and
|
||||
# RepresentationIdentifier="Axis" and RepresentationType="Curve3D" for the 2.5D curve.
|
||||
# Since the alignment is transitioning from horizontal only to horizontal+vertical, the
|
||||
# RepresentationIdentifier must change from "Axis" to "FootPrint"
|
||||
representations = ifcopenshell.util.representation.get_representations_iter(parent_alignment)
|
||||
for representation in representations:
|
||||
if representation.RepresentationIdentifier == "Axis" and representation.RepresentationType == "Curve2D":
|
||||
representation.RepresentationIdentifier = "FootPrint"
|
||||
break
|
||||
|
||||
# create the Axis,Curve3D representation
|
||||
axis_geom_subcontext = ifcopenshell.api.alignment.get_axis_subcontext(file)
|
||||
axis3d_shape_representation = file.createIfcShapeRepresentation(
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="Axis",
|
||||
RepresentationType="Curve3D",
|
||||
Items=(gradient_curve,),
|
||||
)
|
||||
|
||||
ifcopenshell.api.geometry.assign_representation(file, parent_alignment, axis3d_shape_representation)
|
||||
else:
|
||||
# there are multiple vertical reusing the horizontal (IFC CT 4.1.4.4.1.2)
|
||||
# this is the second or subsequent vertical reusing the horizontal
|
||||
|
||||
# create a new child alignment for the new vertical
|
||||
child_alignment = file.createIfcAlignment(
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=f"Child of {parent_alignment.Name}",
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
PredefinedType=None,
|
||||
)
|
||||
|
||||
# Aggregate the child alignment to the parent alignment
|
||||
ifcopenshell.api.aggregate.assign_object(file, (child_alignment,), parent_alignment)
|
||||
|
||||
# nest the vertical under the child alignment
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=[vertical_layout], relating_object=child_alignment)
|
||||
|
||||
child_alignment.ObjectPlacement = parent_alignment.ObjectPlacement
|
||||
|
||||
# the parent alignment has a Representation so create a representation for the vertical
|
||||
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
|
||||
gradient_curve = file.createIfcGradientCurve(
|
||||
Segments=[], SelfIntersect=False, BaseCurve=base_curve, EndPoint=None
|
||||
)
|
||||
|
||||
axis_geom_subcontext = ifcopenshell.api.alignment.get_axis_subcontext(file)
|
||||
|
||||
# create the Curve3D representation
|
||||
axis3d_shape_representation = file.createIfcShapeRepresentation(
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="Axis",
|
||||
RepresentationType="Curve3D",
|
||||
Items=(gradient_curve,),
|
||||
)
|
||||
|
||||
# add the representation to the child alignment
|
||||
ifcopenshell.api.geometry.assign_representation(file, child_alignment, axis3d_shape_representation)
|
||||
|
||||
# All alignment layouts must end with a zero length segment. Their geometric representations must also end with a zero length segment.
|
||||
# Now that all the geometry is setup, add the zero length segment to the layout, which also adds a zero length segment to the representation
|
||||
_add_zero_length_segment(file, vertical_layout)
|
||||
|
||||
return vertical_layout
|
||||
@@ -1,154 +0,0 @@
|
||||
# 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
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def add_zero_length_segment(file: ifcopenshell.file, entity: entity_instance) -> None:
|
||||
"""
|
||||
Adds a zero length segment to the end of entity.
|
||||
|
||||
:param entity: An IfcAlignmentHorizontal, IfcAlignmentVertical, IfcAlignmentCant or IfcCompositeCurve (or subtype)
|
||||
:return: None
|
||||
"""
|
||||
expected_types = [
|
||||
"IfcAlignmentHorizontal",
|
||||
"IfcAlignmentVertical",
|
||||
"IfcAlignmentCant",
|
||||
"IfcCompositeCurve",
|
||||
"IfcGradientCurve",
|
||||
"IfcSegmentedReferenceCurve",
|
||||
]
|
||||
if not entity.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{entity.is_a()}"
|
||||
)
|
||||
|
||||
if ifcopenshell.api.alignment.has_zero_length_segment(entity):
|
||||
return # do nothing if the entity already has a zero length segment
|
||||
|
||||
if entity.is_a("IfcCompositeCurve"):
|
||||
last_segment = entity.Segments[-1]
|
||||
settings = ifcopenshell.geom.settings()
|
||||
segment_fn = ifcopenshell_wrapper.map_shape(settings, last_segment.wrapped_data)
|
||||
segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, segment_fn)
|
||||
e = segment_evaluator.evaluate(segment_fn.end())
|
||||
end = np.array(e)
|
||||
x = float(end[0, 3])
|
||||
y = float(end[1, 3])
|
||||
dx = float(end[0, 0])
|
||||
dy = float(end[1, 0])
|
||||
|
||||
parent_curve = file.createIfcLine(
|
||||
Pnt=file.createIfcCartesianPoint(Coordinates=((0.0, 0.0))),
|
||||
Dir=file.createIfcVector(
|
||||
Orientation=file.createIfcDirection(DirectionRatios=((1.0, 0.0))),
|
||||
Magnitude=1.0,
|
||||
),
|
||||
)
|
||||
curve_segment = file.createIfcCurveSegment(
|
||||
Transition="DISCONTINUOUS",
|
||||
Placement=file.createIfcAxis2Placement2D(
|
||||
Location=file.createIfcCartesianPoint((x, y)),
|
||||
RefDirection=file.createIfcDirection((dx, dy)),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(0.0),
|
||||
ParentCurve=parent_curve,
|
||||
)
|
||||
ifcopenshell.api.alignment.add_segment_to_curve(file, curve_segment, entity)
|
||||
else:
|
||||
for rel in entity.IsNestedBy:
|
||||
if 0 < len(rel.RelatedObjects):
|
||||
last_segment = rel.RelatedObjects[-1]
|
||||
if last_segment.is_a("IfcAlignmentSegment"):
|
||||
if entity.is_a("IfcAlignmentHorizontal"):
|
||||
design_parameters = file.createIfcAlignmentHorizontalSegment(
|
||||
StartPoint=file.createIfcCartesianPoint(
|
||||
(0.0, 0.0)
|
||||
), # this is a little problematic. need to know the end point and tangent
|
||||
StartDirection=0.0, # of the previous segment, which requires geometry mapping
|
||||
SegmentLength=0.0,
|
||||
PredefinedType="LINE",
|
||||
)
|
||||
segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
ifcopenshell.api.nest.assign_object(
|
||||
file,
|
||||
related_objects=[
|
||||
segment,
|
||||
],
|
||||
relating_object=entity,
|
||||
)
|
||||
break
|
||||
elif entity.is_a("IfcAlignmentVertical"):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=last_segment.DesignParameters.StartDistAlong
|
||||
+ last_segment.DesignParameters.HorizontalLength,
|
||||
HorizontalLength=0.0,
|
||||
StartHeight=0.0,
|
||||
StartGradient=last_segment.DesignParameters.EndGradient,
|
||||
EndGradient=last_segment.DesignParameters.EndGradient,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
ifcopenshell.api.nest.assign_object(
|
||||
file,
|
||||
related_objects=[
|
||||
segment,
|
||||
],
|
||||
relating_object=entity,
|
||||
)
|
||||
break
|
||||
elif entity.is_a("IfcAlignmentCant"):
|
||||
design_parameters = file.createIfcAlignmentCantSegment(
|
||||
StartDistAlong=last_segment.DesignParameters.StartDistAlong
|
||||
+ last_segment.DesignParameters.HorizontalLength,
|
||||
HorizontalLength=0.0,
|
||||
StartCantLeft=(
|
||||
last_segment.DesignParameters.EndCantLeft
|
||||
if last_segment.DesignParameters.EndCantLeft != None
|
||||
else last_segment.DesignParameters.StartCantLeft
|
||||
),
|
||||
StartCantRight=(
|
||||
last_segment.DesignParameters.EndCantRight
|
||||
if last_segment.DesignParameters.EndCantRight != None
|
||||
else last_segment.DesignParameters.StartCantRight
|
||||
),
|
||||
PredefinedType="CONSTANTCANT",
|
||||
)
|
||||
segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
ifcopenshell.api.nest.assign_object(
|
||||
file,
|
||||
related_objects=[
|
||||
segment,
|
||||
],
|
||||
relating_object=entity,
|
||||
)
|
||||
break
|
||||
@@ -0,0 +1,87 @@
|
||||
# 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
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
from ifcopenshell.api.alignment._create_geometric_representation import _create_geometric_representation
|
||||
from ifcopenshell.api.alignment._add_zero_length_segment import _add_zero_length_segment
|
||||
|
||||
|
||||
def create(
|
||||
file: ifcopenshell.file,
|
||||
name: str,
|
||||
include_vertical: bool = False,
|
||||
include_cant: bool = False,
|
||||
start_station: float = 0.0,
|
||||
) -> entity_instance:
|
||||
"""
|
||||
Creates a new IfcAlignment with an IfcRelNests nesting an IfcReferent (for stationing) and IfcAlignmentHorizontal. The nest relationship can optionally
|
||||
include IfcAlignmentVertical and IfcAlignmentCant. Geometric representations for the alignment layouts (IfcCompositeCurve,
|
||||
IfcGradientCurve, IfcSegmentedReferenceCurve) are created as well.
|
||||
|
||||
Zero length segments are added at the end.
|
||||
|
||||
The IfcAlignment is aggreated to IfcProject
|
||||
|
||||
Use get_horizontal_layout(alignment) to get the IfcAlignmentHorizontal layout.
|
||||
|
||||
:param file:
|
||||
:param name: name assigned to IfcAlignment.Name
|
||||
:param include_vertical: If True, IfcAlignmentVertical and IfcGradientCurve are created
|
||||
:param include_cant: If True, IfcAlignmentCant and IfcSegmentedReferenceCurve are created
|
||||
:param start_station: station value at the start of the alignment
|
||||
:return: Returns an IfcAlignment
|
||||
"""
|
||||
alignment = file.createIfcAlignment(
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
Name=name,
|
||||
)
|
||||
|
||||
alignment_layouts = []
|
||||
|
||||
alignment_layouts.append(file.createIfcAlignmentHorizontal(GlobalId=ifcopenshell.guid.new()))
|
||||
|
||||
if include_vertical:
|
||||
alignment_layouts.append(file.createIfcAlignmentVertical(GlobalId=ifcopenshell.guid.new()))
|
||||
|
||||
if include_cant:
|
||||
alignment_layouts.append(file.createIfcAlignmentCant(GlobalId=ifcopenshell.guid.new(), RailHeadDistance=1.0))
|
||||
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=alignment_layouts, relating_object=alignment)
|
||||
|
||||
_create_geometric_representation(file, alignment)
|
||||
|
||||
for layout in alignment_layouts:
|
||||
_add_zero_length_segment(file, layout)
|
||||
|
||||
# define stationing
|
||||
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
name = ifcopenshell.util.stationing.station_as_string(file, start_station)
|
||||
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||
file, alignment, basis_curve, 0.0, start_station, name
|
||||
)
|
||||
ifcopenshell.api.nest.reorder_nesting(file, referent, -1, 0)
|
||||
|
||||
# IFC 4.1.4.1.1 Alignment Aggregation To Project
|
||||
project = file.by_type("IfcProject")[0]
|
||||
if project:
|
||||
ifcopenshell.api.aggregate.assign_object(file, products=[alignment], relating_object=project)
|
||||
|
||||
return alignment
|
||||
+13
-38
@@ -24,59 +24,34 @@ from ifcopenshell import entity_instance
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
def create_alignment_by_pi_method(
|
||||
def create_by_pi_method(
|
||||
file: ifcopenshell.file,
|
||||
alignment_name: str,
|
||||
name: str,
|
||||
hpoints: Sequence[Sequence[float]],
|
||||
radii: Sequence[float],
|
||||
vpoints: Sequence[Sequence[float]] = None,
|
||||
lengths: Sequence[float] = None,
|
||||
alignment_description: str = None,
|
||||
start_station: float = 0.0,
|
||||
) -> entity_instance:
|
||||
"""
|
||||
Create an alignment using the PI layout method for both horizontal and vertical alignments.
|
||||
If vpoints and lengths are omitted, only a horizontal alignment is created. Only the business logic
|
||||
entities are creaed. Use create_geometric_representation() to create the geometric entities.
|
||||
If vpoints and lengths are omitted, only a horizontal alignment is created.
|
||||
|
||||
:param alignment_name: value for Name attribute
|
||||
:param name: value for Name attribute
|
||||
:param points: (X,Y) pairs denoting the location of the horizontal PIs, including start and end
|
||||
:param radii: radii values to use for transition
|
||||
:param vpoints: (distance_along, Z_height) pairs denoting the location of the vertical PIs, including start and end.
|
||||
:param lengths: parabolic vertical curve horizontal length values to use for transition
|
||||
:param alignment_description: value for Description attribute
|
||||
:return: Returns an IfcAlignment
|
||||
"""
|
||||
alignments = []
|
||||
|
||||
horizontal_alignment = ifcopenshell.api.alignment.create_horizontal_alignment_by_pi_method(
|
||||
file, alignment_name, hpoints, radii
|
||||
include_vertical = True if vpoints and lengths else False
|
||||
alignment = ifcopenshell.api.alignment.create(
|
||||
file, name, include_vertical=include_vertical, start_station=start_station
|
||||
)
|
||||
alignments.append(horizontal_alignment)
|
||||
|
||||
if vpoints and lengths:
|
||||
vertical_alignment = ifcopenshell.api.alignment.create_vertical_alignment_by_pi_method(
|
||||
file, alignment_name, vpoints, lengths
|
||||
)
|
||||
alignments.append(vertical_alignment)
|
||||
|
||||
# create the alignment
|
||||
alignment = file.create_entity(
|
||||
type="IfcAlignment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=alignment_name,
|
||||
Description=alignment_description,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
PredefinedType=None,
|
||||
)
|
||||
|
||||
# nest the horizontal and vertical under the alignment
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=alignments, relating_object=alignment)
|
||||
|
||||
# IFC 4.1.4.1.1 Alignment Aggregation To Project
|
||||
project = file.by_type("IfcProject")[0]
|
||||
ifcopenshell.api.aggregate.assign_object(file, products=[alignment], relating_object=project)
|
||||
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
||||
ifcopenshell.api.alignment.layout_horizontal_alignment_by_pi_method(file, horizontal_layout, hpoints, radii)
|
||||
if include_vertical:
|
||||
vertical_layout = ifcopenshell.api.alignment.get_vertical_layout(alignment)
|
||||
ifcopenshell.api.alignment.layout_vertical_alignment_by_pi_method(file, vertical_layout, vpoints, lengths)
|
||||
|
||||
return alignment
|
||||
+11
-25
@@ -17,27 +17,19 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.stationing
|
||||
import ifcopenshell.api
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell.api.alignment import get_axis_subcontext
|
||||
|
||||
import math
|
||||
from typing import Sequence
|
||||
|
||||
import csv
|
||||
|
||||
|
||||
def create_alignment_from_csv(file: ifcopenshell.file, filepath: str) -> entity_instance:
|
||||
def create_from_csv(file: ifcopenshell.file, filepath: str) -> entity_instance:
|
||||
"""
|
||||
Creates an alignment from PI data stored in a CSV file. Only the business logic
|
||||
entities are creaed. Use create_geometric_representation() to create the geometric entities.
|
||||
Creates an alignment from PI data stored in a CSV file.
|
||||
|
||||
The format of the file is:
|
||||
|
||||
@@ -94,22 +86,16 @@ def create_alignment_from_csv(file: ifcopenshell.file, filepath: str) -> entity_
|
||||
radii = radii[1:-1] # The first radius value is a placeholder, remove it
|
||||
|
||||
if row_count == 1:
|
||||
# create the alignment
|
||||
alignment = file.createIfcAlignment(GlobalId=ifcopenshell.guid.new())
|
||||
# create the horizontal alignment
|
||||
horizontal_alignment = ifcopenshell.api.alignment.create_horizontal_alignment_by_pi_method(
|
||||
file, "Alignment_from_CSV", coordinates, radii
|
||||
)
|
||||
# nest them together
|
||||
ifcopenshell.api.nest.assign_object(
|
||||
file, related_objects=(horizontal_alignment,), relating_object=alignment
|
||||
alignment = ifcopenshell.api.alignment.create(file, "Alignment_from_CSV")
|
||||
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
||||
ifcopenshell.api.alignment.layout_horizontal_alignment_by_pi_method(
|
||||
file, horizontal_layout, coordinates, radii
|
||||
)
|
||||
else:
|
||||
# add all subsequent vertical alignments
|
||||
ifcopenshell.api.alignment.add_vertical_alignment_by_pi_method(file, alignment, coordinates, radii)
|
||||
|
||||
# IFC 4.1.4.1.1 Alignment Aggregation To Project
|
||||
project = file.by_type("IfcProject")[0]
|
||||
ifcopenshell.api.aggregate.assign_object(file, products=[alignment], relating_object=project)
|
||||
vertical_layout = ifcopenshell.api.alignment.add_vertical_layout(file, alignment)
|
||||
ifcopenshell.api.alignment.layout_vertical_alignment_by_pi_method(
|
||||
file, vertical_layout, coordinates, radii
|
||||
)
|
||||
|
||||
return alignment
|
||||
-216
@@ -1,216 +0,0 @@
|
||||
# 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
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
import math
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
def create_horizontal_alignment_by_pi_method(
|
||||
file: ifcopenshell.file, name: str, hpoints: Sequence[Sequence[float]], radii: Sequence[float]
|
||||
) -> entity_instance:
|
||||
"""
|
||||
Create a horizontal alignment using the PI layout method.
|
||||
|
||||
:param name: value for Name attribute
|
||||
:param hpoints: (X, Y) pairs denoting the location of the horizontal PIs, including start (POB) and end (POE).
|
||||
:param radii: radius values to use for transition
|
||||
:return: Returns a IfcAlignmentHorizontal
|
||||
"""
|
||||
if not (len(hpoints) - 2 == len(radii)):
|
||||
raise ValueError("radii should have two fewer elements that hpoints")
|
||||
|
||||
# Create the horizontal alignment (IfcAlignmentHorizontal) and nest alignment segments
|
||||
horizontal_alignment = file.create_entity(
|
||||
type="IfcAlignmentHorizontal",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=f"{name} - Horizontal",
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
)
|
||||
|
||||
xBT, yBT = hpoints[0]
|
||||
xPI, yPI = hpoints[1]
|
||||
|
||||
i = 1
|
||||
|
||||
for radius in radii:
|
||||
# back tangent
|
||||
dxBT = xPI - xBT
|
||||
dyBT = yPI - yBT
|
||||
angleBT = math.atan2(dyBT, dxBT)
|
||||
lengthBT = math.sqrt(dxBT * dxBT + dyBT * dyBT)
|
||||
|
||||
# forward tangent
|
||||
i += 1
|
||||
xFT, yFT = hpoints[i]
|
||||
dxFT = xFT - xPI
|
||||
dyFT = yFT - yPI
|
||||
angleFT = math.atan2(dyFT, dxFT)
|
||||
|
||||
delta = angleFT - angleBT
|
||||
|
||||
tangent = abs(radius * math.tan(delta / 2))
|
||||
|
||||
lc = abs(radius * delta)
|
||||
|
||||
radius *= delta / abs(delta)
|
||||
|
||||
xPC = xPI - tangent * math.cos(angleBT)
|
||||
yPC = yPI - tangent * math.sin(angleBT)
|
||||
|
||||
xPT = xPI + tangent * math.cos(angleFT)
|
||||
yPT = yPI + tangent * math.sin(angleFT)
|
||||
|
||||
tangent_run = lengthBT - tangent
|
||||
|
||||
# create back tangent run
|
||||
pt = file.create_entity(
|
||||
type="IfcCartesianPoint",
|
||||
Coordinates=(xBT, yBT),
|
||||
)
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentHorizontalSegment",
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartPoint=pt,
|
||||
StartDirection=angleBT,
|
||||
StartRadiusOfCurvature=0.0,
|
||||
EndRadiusOfCurvature=0.0,
|
||||
SegmentLength=tangent_run,
|
||||
GravityCenterLineHeight=None,
|
||||
PredefinedType="LINE",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, horizontal_alignment, alignment_segment)
|
||||
|
||||
# create circular curve
|
||||
if radius != 0.0:
|
||||
pc = file.create_entity(
|
||||
type="IfcCartesianPoint",
|
||||
Coordinates=(xPC, yPC),
|
||||
)
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentHorizontalSegment",
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartPoint=pc,
|
||||
StartDirection=angleBT,
|
||||
StartRadiusOfCurvature=float(radius),
|
||||
EndRadiusOfCurvature=float(radius),
|
||||
SegmentLength=lc,
|
||||
GravityCenterLineHeight=None,
|
||||
PredefinedType="CIRCULARARC",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, horizontal_alignment, alignment_segment)
|
||||
|
||||
xBT = xPT
|
||||
yBT = yPT
|
||||
xPI = xFT
|
||||
yPI = yFT
|
||||
|
||||
# done processing radii
|
||||
# create last tangent run
|
||||
dx = xPI - xBT
|
||||
dy = yPI - yBT
|
||||
angleBT = math.atan2(dy, dx)
|
||||
tangent_run = math.sqrt(dx * dx + dy * dy)
|
||||
pt = file.create_entity(type="IfcCartesianPoint", Coordinates=(xBT, yBT))
|
||||
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentHorizontalSegment",
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartPoint=pt,
|
||||
StartDirection=angleBT,
|
||||
StartRadiusOfCurvature=0.0,
|
||||
EndRadiusOfCurvature=0.0,
|
||||
SegmentLength=tangent_run,
|
||||
GravityCenterLineHeight=None,
|
||||
PredefinedType="LINE",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, horizontal_alignment, alignment_segment)
|
||||
|
||||
# create zero length terminator segment
|
||||
poe = file.create_entity(type="IfcCartesianPoint", Coordinates=(xPI, yPI))
|
||||
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentHorizontalSegment",
|
||||
StartTag="POE",
|
||||
EndTag="POE",
|
||||
StartPoint=poe,
|
||||
StartDirection=angleBT,
|
||||
StartRadiusOfCurvature=0.0,
|
||||
EndRadiusOfCurvature=0.0,
|
||||
SegmentLength=0.0,
|
||||
GravityCenterLineHeight=None,
|
||||
PredefinedType="LINE",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, horizontal_alignment, alignment_segment)
|
||||
|
||||
return horizontal_alignment
|
||||
@@ -0,0 +1,85 @@
|
||||
# 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
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.alignment.get_alignment
|
||||
import ifcopenshell.geom
|
||||
from ifcopenshell import entity_instance
|
||||
import math
|
||||
from ifcopenshell import ifcopenshell_wrapper
|
||||
import numpy as np
|
||||
|
||||
from ifcopenshell.api.alignment._add_segment_to_layout import _add_segment_to_layout
|
||||
|
||||
|
||||
def create_layout_segment(
|
||||
file: ifcopenshell.file, layout: entity_instance, design_parameters: entity_instance
|
||||
) -> np.array:
|
||||
"""
|
||||
Creates a new IfcAlignmentSegment using the IfcAlignmentParameterSegment design parameters.
|
||||
The new segment is appended to the layout alignment and the corresponding IfcCurveSegment is created in the geometric representation
|
||||
|
||||
:param layout: The layout to receive the new layout segment. This parameter is expected to be IfcAlignmentHorizontal, IfcAlignmentVertical or IfcAlignmentCant
|
||||
:param design_parameters: The parameters defining the segment. Expected to be the appropreate subclass of IfcAlignmentParameterSegment
|
||||
:return: 4x4 matrix at end of segment as np.array intended to be used as the start point geometry for the next segment.
|
||||
"""
|
||||
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
|
||||
if not layout.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received {layout.is_a()}"
|
||||
)
|
||||
|
||||
if layout.is_a("IfcAlignmentHorizontal") and not design_parameters.is_a("IfcAlignmentHorizontalSegment"):
|
||||
raise TypeError("Expected design_parameters to be IfcAlignmentHorizontalSegment")
|
||||
elif layout.is_a("IfcAlignmentVertical") and not design_parameters.is_a("IfcAlignmentVerticalSegment"):
|
||||
raise TypeError("Expected design_parameters to be IfcAlignmentVerticalSegment")
|
||||
elif layout.is_a("IfcAlignmentCant") and not design_parameters.is_a("IfcAlignmentCantSegment"):
|
||||
raise TypeError("Expected design_parameters to be IfcAlignmentCantSegment")
|
||||
|
||||
# create the segment and add it to the layout.
|
||||
segment = file.createIfcAlignmentSegment(GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters)
|
||||
_add_segment_to_layout(file, layout, segment) # adds to layout and geometric representation
|
||||
|
||||
# compute the 4x4 matrix at the end of the segment so this information can be
|
||||
# returned and used when defining the next segment
|
||||
alignment = ifcopenshell.api.alignment.get_alignment(layout)
|
||||
curve = ifcopenshell.api.alignment.get_curve(alignment)
|
||||
|
||||
if layout.is_a("IfcAlignmentHorizontal"):
|
||||
if curve.is_a("IfcGradientCurve"):
|
||||
curve = curve.BaseCurve
|
||||
elif curve.is_a("IfcSegmentedReferenceCurve"):
|
||||
curve = (
|
||||
curve.BaseCurve.BaseCurve
|
||||
) # layout is horizontal and curve is segmented ref ... we want the curve's base curve
|
||||
elif layout.is_a("IfcAlignmentVertical"):
|
||||
if curve.is_a("IfcSegmentedReferenceCurve"):
|
||||
curve = curve.BaseCurve
|
||||
|
||||
# the new segment is two from the end... the end segment is zero length
|
||||
curve_segment = curve.Segments[-2]
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
|
||||
segment_fn = ifcopenshell_wrapper.map_shape(settings, curve_segment.wrapped_data)
|
||||
segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, segment_fn)
|
||||
e = segment_evaluator.evaluate(segment_fn.end())
|
||||
end = np.array(e)
|
||||
|
||||
return end
|
||||
+1
-1
@@ -58,7 +58,7 @@ def create_segment_representations(
|
||||
)
|
||||
|
||||
curve_segments = curve.Segments
|
||||
segments = nested_alignment.IsNestedBy[0].RelatingObjects
|
||||
segments = nested_alignment.IsNestedBy[0].RelatedObjects
|
||||
|
||||
for curve_segment, alignment_segment in zip(curve_segments, segments):
|
||||
axis_representation = file.create_entity(
|
||||
|
||||
-194
@@ -1,194 +0,0 @@
|
||||
# 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
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
import math
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
def create_vertical_alignment_by_pi_method(
|
||||
file: ifcopenshell.file, name: str, vpoints: Sequence[Sequence[float]], lengths: Sequence[float]
|
||||
) -> entity_instance:
|
||||
"""
|
||||
Create a vertical alignment using the PI layout method.
|
||||
|
||||
:param name: value for Name attribute
|
||||
:param base_curve: base curve representing the 2D projection of the gradient curve
|
||||
:param vpoints: (distance_along, Z_height) pairs denoting the location of the vertical PIs, including start and end.
|
||||
:param lengths: horizontal length of parabolic vertical curves
|
||||
:return: IfcAlignmentHorizontal
|
||||
"""
|
||||
if not (len(vpoints) - 2 == len(lengths)):
|
||||
raise ValueError("lengths should have two fewer elements that vpoints")
|
||||
|
||||
# Create the vertical alignment (IfcAlignmentVertical) and nest alignment segments
|
||||
vertical_alignment = file.create_entity(
|
||||
type="IfcAlignmentVertical",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=f"{name} - Vertical",
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
)
|
||||
|
||||
xPBG, yPBG = vpoints[0]
|
||||
xPVI, yPVI = vpoints[1]
|
||||
i = 1
|
||||
for length in lengths:
|
||||
# back gradient
|
||||
dxBG = xPVI - xPBG
|
||||
dyBG = yPVI - yPBG
|
||||
start_slope = math.tan(math.atan2(dyBG, dxBG))
|
||||
|
||||
# forward gradient
|
||||
i += 1
|
||||
xPFG, yPFG = vpoints[i]
|
||||
dxFG = xPFG - xPVI
|
||||
dyFG = yPFG - yPVI
|
||||
end_slope = math.tan(math.atan2(dyFG, dxFG))
|
||||
|
||||
xEVC = xPVI + length / 2.0
|
||||
yEVC = yPVI + end_slope * length / 2.0
|
||||
|
||||
# create gradient
|
||||
gradient_length = dxBG - length / 2.0
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentVerticalSegment",
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartDistAlong=xPBG,
|
||||
HorizontalLength=gradient_length,
|
||||
StartHeight=yPBG,
|
||||
StartGradient=start_slope,
|
||||
EndGradient=start_slope,
|
||||
RadiusOfCurvature=None,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, vertical_alignment, alignment_segment)
|
||||
|
||||
# create vertical curve
|
||||
if 0.0 < length:
|
||||
k = (end_slope - start_slope) / length
|
||||
xBVC = xPVI - length / 2.0
|
||||
yBVC = yPVI - start_slope * length / 2.0
|
||||
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentVerticalSegment",
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartDistAlong=xBVC,
|
||||
HorizontalLength=length,
|
||||
StartHeight=yBVC,
|
||||
StartGradient=start_slope,
|
||||
EndGradient=end_slope,
|
||||
RadiusOfCurvature=1 / k,
|
||||
PredefinedType="PARABOLICARC",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, vertical_alignment, alignment_segment)
|
||||
|
||||
# start of next curve is end of this curve
|
||||
xPBG = xEVC
|
||||
yPBG = yEVC
|
||||
xPVI = xPFG
|
||||
yPVI = yPFG
|
||||
|
||||
# create last gradient run
|
||||
dx = xPVI - xPBG
|
||||
dy = yPVI - yPBG
|
||||
slope = math.tan(math.atan2(dy, dx))
|
||||
gradient_length = dx
|
||||
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentVerticalSegment",
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartDistAlong=xPBG,
|
||||
HorizontalLength=gradient_length,
|
||||
StartHeight=yPBG,
|
||||
StartGradient=slope,
|
||||
EndGradient=slope,
|
||||
RadiusOfCurvature=None,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, vertical_alignment, alignment_segment)
|
||||
|
||||
# create zero length terminator segment
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentVerticalSegment",
|
||||
StartTag="VPOE",
|
||||
EndTag="VPOE",
|
||||
StartDistAlong=xPVI,
|
||||
HorizontalLength=0.0,
|
||||
StartHeight=yPVI,
|
||||
StartGradient=slope,
|
||||
EndGradient=slope,
|
||||
RadiusOfCurvature=None,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, vertical_alignment, alignment_segment)
|
||||
|
||||
return vertical_alignment
|
||||
@@ -43,12 +43,6 @@ def distance_along_from_station(file: ifcopenshell.file, alignment: entity_insta
|
||||
print(dist_along) # 100.00
|
||||
"""
|
||||
|
||||
start_station = 0.0
|
||||
components = ifcopenshell.util.element.get_components(alignment)
|
||||
for c in components:
|
||||
if c.is_a("IfcReferent") and ifcopenshell.util.element.get_predefined_type(c) == "STATION":
|
||||
start_station = ifcopenshell.util.element.get_pset(c, name="Pset_Stationing", prop="Station")
|
||||
break
|
||||
|
||||
start_station = ifcopenshell.api.alignment.get_alignment_station(file, alignment)
|
||||
dist_along = station - start_station
|
||||
return dist_along
|
||||
|
||||
+7
-11
@@ -17,19 +17,15 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.util
|
||||
from ifcopenshell import entity_instance
|
||||
import ifcopenshell.api.alignment.remove_last_segment
|
||||
from typing import Sequence
|
||||
|
||||
import ifcopenshell.util.representation
|
||||
|
||||
|
||||
def remove_zero_length_segment(file: ifcopenshell.file, entity: entity_instance) -> entity_instance:
|
||||
def get_alignment(layout: entity_instance) -> entity_instance:
|
||||
"""
|
||||
Removes the zero length segment from the end of entity.
|
||||
|
||||
:param entity: An IfcAlignmentHorizontal, IfcAlignmentVertical, IfcAlignmentCant or IfcCompositeCurve
|
||||
:return: The zero length segment
|
||||
Returns the alignment that nests this layout
|
||||
"""
|
||||
if not ifcopenshell.api.alignment.has_zero_length_segment(entity):
|
||||
return None
|
||||
|
||||
return ifcopenshell.api.alignment.remove_last_segment(file, entity)
|
||||
return layout.Nests[0].RelatingObject if 0 < len(layout.Nests) else None
|
||||
@@ -0,0 +1,40 @@
|
||||
# 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 entity_instance
|
||||
|
||||
|
||||
def get_alignment_station(file: ifcopenshell.file, alignment: entity_instance) -> float:
|
||||
"""
|
||||
Returns the start station of the alignment. If the alignment is nested by an IfcReferent
|
||||
the referent is checked for PredefinedType of STATION and an occurance of Pset_Stationing.Station,
|
||||
otherwise start station is taken to be 0.0.
|
||||
"""
|
||||
|
||||
if not alignment.is_a("IfcAlignment"):
|
||||
raise TypeError(f"Expected entity type to be IfcAlignment, instead received {alignment.is_a()}")
|
||||
|
||||
start_station = 0.0
|
||||
components = ifcopenshell.util.element.get_components(alignment)
|
||||
for c in components:
|
||||
if c.is_a("IfcReferent") and ifcopenshell.util.element.get_predefined_type(c) == "STATION":
|
||||
start_station = ifcopenshell.util.element.get_pset(c, name="Pset_Stationing", prop="Station")
|
||||
break
|
||||
|
||||
return start_station
|
||||
@@ -45,6 +45,10 @@ def get_basis_curve(alignment: entity_instance) -> entity_instance:
|
||||
representation.RepresentationIdentifier == "FootPrint" and representation.RepresentationType == "Curve2D"
|
||||
):
|
||||
axis = representation
|
||||
break
|
||||
return None if axis.Items == None or len(axis.Items) == 0 else axis.Items[0]
|
||||
|
||||
return None if axis == None or axis.Items == None or len(axis.Items) == 0 else axis.Items[0]
|
||||
if axis == None and 0 < len(alignment.Decomposes):
|
||||
parent_alignment = alignment.Decomposes[0].RelatingObject
|
||||
return get_basis_curve(parent_alignment)
|
||||
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# 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/>.
|
||||
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def get_cant_layout(alignment: entity_instance) -> entity_instance:
|
||||
"""
|
||||
Returns the IfcAlignmentCant assocated with this alignment
|
||||
"""
|
||||
for rel in alignment.IsNestedBy:
|
||||
for layout in rel.RelatedObjects:
|
||||
if layout.is_a("IfcAlignmentCant"):
|
||||
return layout
|
||||
|
||||
return None
|
||||
@@ -26,7 +26,7 @@ import ifcopenshell.util.element
|
||||
|
||||
def get_child_alignments(alignment: entity_instance) -> Sequence[entity_instance]:
|
||||
"""
|
||||
Returns the aggregated child alignments to this alignment
|
||||
Returns the aggregated child alignments to this alignment per CT 4.1.4.4.1.2 Alignment Layout - Reusing Horizontal Layout
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# 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/>.
|
||||
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def get_horizontal_layout(alignment: entity_instance) -> entity_instance:
|
||||
"""
|
||||
Returns the IfcAlignmentHorizontal assocated with this alignment
|
||||
"""
|
||||
for rel in alignment.IsNestedBy:
|
||||
for layout in rel.RelatedObjects:
|
||||
if layout.is_a("IfcAlignmentHorizontal"):
|
||||
return layout
|
||||
|
||||
return None
|
||||
@@ -0,0 +1,55 @@
|
||||
# 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
|
||||
import ifcopenshell.util
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
import ifcopenshell.util.representation
|
||||
|
||||
|
||||
def get_layout_curve(layout: entity_instance) -> entity_instance:
|
||||
"""
|
||||
Returns the representation curve for the layout. This will be an IfcCompositeCurve, IfcGradientCurve, or IfcSegmentReferenceCurve
|
||||
for IfcAlignmentHorizontal, IfcAlignmentVertical, or IfcAlignmentCant, respectively.
|
||||
|
||||
:param layout: An alignment layout
|
||||
:return: The geometric representation curve
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
alignment = model.by_type("IfcAlignment")[0]
|
||||
layout = ifcopenshell.api.get_horizontal_layout(alignment)
|
||||
composite_curve = ifcopenshell.api.alignment.get_layout_curve(layout)
|
||||
"""
|
||||
alignment = ifcopenshell.api.alignment.get_alignment(layout)
|
||||
|
||||
curve = ifcopenshell.api.alignment.get_curve(alignment)
|
||||
if layout.is_a("IfcAlignmentHorizontal"):
|
||||
# Layout is horizontal so get the IfcCompositeCurve
|
||||
if curve.is_a("IfcGradientCurve"):
|
||||
curve = curve.BaseCurve
|
||||
elif curve.is_a("IfcSegmentedReferenceCurve"):
|
||||
curve = curve.BaseCurve.BaseCurve
|
||||
elif layout.is_a("IfcAlignmentVertical"):
|
||||
# Layout is vertical so get the IfcGradientCurve
|
||||
if curve.is_a("IfcSegmentedReferenceCurve"):
|
||||
curve = curve.BaseCurve
|
||||
|
||||
return curve
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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
|
||||
import ifcopenshell.util
|
||||
from ifcopenshell import entity_instance
|
||||
from typing import Sequence
|
||||
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def get_layout_segments(layout: entity_instance) -> Sequence[entity_instance]:
|
||||
"""
|
||||
Returns the IfcAlignmentSegment nested to this alignment layout
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
horizontal = model.by_type("IfcAlignmentHorizontal")[0]
|
||||
segments = ifcopenshell.api.alignment.get_layout_segments(horizontal)
|
||||
"""
|
||||
segments = []
|
||||
for rel in layout.IsNestedBy:
|
||||
for segment in rel.RelatedObjects:
|
||||
if segment.is_a("IfcAlignmentSegment"):
|
||||
segments.append(segment)
|
||||
|
||||
return segments
|
||||
@@ -0,0 +1,31 @@
|
||||
# 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/>.
|
||||
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def get_vertical_layout(alignment: entity_instance) -> entity_instance:
|
||||
"""
|
||||
Returns the IfcAlignmentVertical assocated with this alignment
|
||||
"""
|
||||
for rel in alignment.IsNestedBy:
|
||||
for layout in rel.RelatedObjects:
|
||||
if layout.is_a("IfcAlignmentVertical"):
|
||||
return layout
|
||||
|
||||
return None
|
||||
@@ -22,40 +22,29 @@ import ifcopenshell.util.element
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def has_zero_length_segment(entity: entity_instance) -> bool:
|
||||
def has_zero_length_segment(layout: entity_instance) -> bool:
|
||||
"""
|
||||
Returns true if the entity ends with a zero length segment. If the entity is an IfcCompositeCurve the IfcCurveSegment.Transition must be DISCONTINUOUS
|
||||
Returns true if the layout ends with a zero length segment.
|
||||
|
||||
:param entity: An IfcAlignmentHorizontal, IfcAlignmentVertical, IfcAlignmentCant or IfcCompositeCurve
|
||||
:param layout: An IfcAlignmentHorizontal, IfcAlignmentVertical, or IfcAlignmentCant
|
||||
:return: True if the zero length segment is present
|
||||
"""
|
||||
expected_types = [
|
||||
"IfcAlignmentHorizontal",
|
||||
"IfcAlignmentVertical",
|
||||
"IfcAlignmentCant",
|
||||
"IfcCompositeCurve",
|
||||
"IfcGradientCurve",
|
||||
"IfcSegmentedReferenceCurve",
|
||||
]
|
||||
if not entity.is_a() in expected_types:
|
||||
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
|
||||
if not layout.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{entity.is_a()}"
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{layout.is_a()}"
|
||||
)
|
||||
|
||||
if entity.is_a("IfcCompositeCurve"):
|
||||
last_segment = entity.Segments[-1]
|
||||
return last_segment.Transition == "DISCONTINUOUS" and last_segment.SegmentLength.wrappedValue == 0.0
|
||||
else:
|
||||
segments = ifcopenshell.util.element.get_components(entity)
|
||||
for rel in entity.IsNestedBy:
|
||||
if 0 < len(rel.RelatedObjects):
|
||||
last_segment = rel.RelatedObjects[-1]
|
||||
if last_segment.is_a("IfcAlignmentSegment"):
|
||||
if last_segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
|
||||
return last_segment.DesignParameters.SegmentLength == 0.0
|
||||
elif last_segment.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
|
||||
return last_segment.DesignParameters.HorizontalLength == 0.0
|
||||
elif last_segment.DesignParameters.is_a("IfcAlignmentCantSegment"):
|
||||
return last_segment.DesignParameters.HorizontalLength == 0.0
|
||||
result = False
|
||||
for rel in layout.IsNestedBy:
|
||||
if 0 < len(rel.RelatedObjects):
|
||||
last_segment = rel.RelatedObjects[-1]
|
||||
if last_segment.is_a("IfcAlignmentSegment"):
|
||||
if last_segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
|
||||
result = last_segment.DesignParameters.SegmentLength == 0.0
|
||||
elif last_segment.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
|
||||
result = last_segment.DesignParameters.HorizontalLength == 0.0
|
||||
elif last_segment.DesignParameters.is_a("IfcAlignmentCantSegment"):
|
||||
result = last_segment.DesignParameters.HorizontalLength == 0.0
|
||||
|
||||
return False
|
||||
return result
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
# 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 entity_instance
|
||||
|
||||
import math
|
||||
from collections.abc import Sequence
|
||||
|
||||
import ifcopenshell.api.alignment.create_layout_segment
|
||||
|
||||
|
||||
def layout_horizontal_alignment_by_pi_method(
|
||||
file: ifcopenshell.file, layout: entity_instance, hpoints: Sequence[Sequence[float]], radii: Sequence[float]
|
||||
) -> None:
|
||||
"""
|
||||
Appends IfcAlignmentHorizontalSegment to a previously defined IfcAlignmentHorizontal using the PI layout method.
|
||||
The zero length segment is updated.
|
||||
|
||||
:param file: file
|
||||
:param layout: An IfcAlignmentHorizontal layout
|
||||
:param hpoints: (X, Y) pairs denoting the location of the horizontal PIs, including start (POB) and end (POE).
|
||||
:param radii: radius values to use for transition
|
||||
:return: None
|
||||
"""
|
||||
if not (len(hpoints) - 2 == len(radii)):
|
||||
raise ValueError("radii should have two fewer elements that hpoints")
|
||||
|
||||
xBT, yBT = hpoints[0]
|
||||
xPI, yPI = hpoints[1]
|
||||
|
||||
i = 1
|
||||
|
||||
for radius in radii:
|
||||
# back tangent
|
||||
dxBT = xPI - xBT
|
||||
dyBT = yPI - yBT
|
||||
angleBT = math.atan2(dyBT, dxBT)
|
||||
lengthBT = math.sqrt(dxBT * dxBT + dyBT * dyBT)
|
||||
|
||||
# forward tangent
|
||||
i += 1
|
||||
xFT, yFT = hpoints[i]
|
||||
dxFT = xFT - xPI
|
||||
dyFT = yFT - yPI
|
||||
angleFT = math.atan2(dyFT, dxFT)
|
||||
|
||||
delta = angleFT - angleBT
|
||||
|
||||
tangent = abs(radius * math.tan(delta / 2))
|
||||
|
||||
lc = abs(radius * delta)
|
||||
|
||||
radius *= delta / abs(delta)
|
||||
|
||||
xPC = xPI - tangent * math.cos(angleBT)
|
||||
yPC = yPI - tangent * math.sin(angleBT)
|
||||
|
||||
xPT = xPI + tangent * math.cos(angleFT)
|
||||
yPT = yPI + tangent * math.sin(angleFT)
|
||||
|
||||
tangent_run = lengthBT - tangent
|
||||
|
||||
# create back tangent run
|
||||
if 1.0e-03 < tangent_run:
|
||||
pt = file.createIfcCartesianPoint(
|
||||
Coordinates=(xBT, yBT),
|
||||
)
|
||||
design_parameters = file.createIfcAlignmentHorizontalSegment(
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartPoint=pt,
|
||||
StartDirection=angleBT,
|
||||
StartRadiusOfCurvature=0.0,
|
||||
EndRadiusOfCurvature=0.0,
|
||||
SegmentLength=tangent_run,
|
||||
GravityCenterLineHeight=None,
|
||||
PredefinedType="LINE",
|
||||
)
|
||||
ifcopenshell.api.alignment.create_layout_segment(file, layout, design_parameters)
|
||||
|
||||
# create circular curve
|
||||
if radius != 0.0:
|
||||
pc = file.createIfcCartesianPoint(
|
||||
Coordinates=(xPC, yPC),
|
||||
)
|
||||
design_parameters = file.createIfcAlignmentHorizontalSegment(
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartPoint=pc,
|
||||
StartDirection=angleBT,
|
||||
StartRadiusOfCurvature=float(radius),
|
||||
EndRadiusOfCurvature=float(radius),
|
||||
SegmentLength=lc,
|
||||
GravityCenterLineHeight=None,
|
||||
PredefinedType="CIRCULARARC",
|
||||
)
|
||||
ifcopenshell.api.alignment.create_layout_segment(file, layout, design_parameters)
|
||||
|
||||
xBT = xPT
|
||||
yBT = yPT
|
||||
xPI = xFT
|
||||
yPI = yFT
|
||||
|
||||
# done processing radii
|
||||
# create last tangent run
|
||||
dx = xPI - xBT
|
||||
dy = yPI - yBT
|
||||
angleBT = math.atan2(dy, dx)
|
||||
tangent_run = math.sqrt(dx * dx + dy * dy)
|
||||
|
||||
if 1.0e-03 < tangent_run:
|
||||
pt = file.createIfcCartesianPoint(Coordinates=(xBT, yBT))
|
||||
|
||||
design_parameters = file.createIfcAlignmentHorizontalSegment(
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartPoint=pt,
|
||||
StartDirection=angleBT,
|
||||
StartRadiusOfCurvature=0.0,
|
||||
EndRadiusOfCurvature=0.0,
|
||||
SegmentLength=tangent_run,
|
||||
GravityCenterLineHeight=None,
|
||||
PredefinedType="LINE",
|
||||
)
|
||||
ifcopenshell.api.alignment.create_layout_segment(file, layout, design_parameters)
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
# 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
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
import math
|
||||
from collections.abc import Sequence
|
||||
|
||||
import ifcopenshell.api.alignment.create_layout_segment
|
||||
|
||||
|
||||
def layout_vertical_alignment_by_pi_method(
|
||||
file: ifcopenshell.file, layout: entity_instance, vpoints: Sequence[Sequence[float]], lengths: Sequence[float]
|
||||
) -> None:
|
||||
"""
|
||||
Appends IfcAlignmentVerticalSegment to a previously defined IfcAlignmentVertical using the PI layout method.
|
||||
The zero length segment is updated.
|
||||
|
||||
:param file: file
|
||||
:param layout: An IfcAlignmentVertical layout
|
||||
:param vpoints: (distance_along, Z_height) pairs denoting the location of the vertical PIs, including start and end.
|
||||
:param lengths: horizontal length of parabolic vertical curves
|
||||
:return: None
|
||||
"""
|
||||
if not (len(vpoints) - 2 == len(lengths)):
|
||||
raise ValueError("lengths should have two fewer elements that vpoints")
|
||||
|
||||
xPBG, yPBG = vpoints[0]
|
||||
xPVI, yPVI = vpoints[1]
|
||||
i = 1
|
||||
for length in lengths:
|
||||
# back gradient
|
||||
dxBG = xPVI - xPBG
|
||||
dyBG = yPVI - yPBG
|
||||
start_slope = math.tan(math.atan2(dyBG, dxBG))
|
||||
|
||||
# forward gradient
|
||||
i += 1
|
||||
xPFG, yPFG = vpoints[i]
|
||||
dxFG = xPFG - xPVI
|
||||
dyFG = yPFG - yPVI
|
||||
end_slope = math.tan(math.atan2(dyFG, dxFG))
|
||||
|
||||
xEVC = xPVI + length / 2.0
|
||||
yEVC = yPVI + end_slope * length / 2.0
|
||||
|
||||
# create gradient
|
||||
gradient_length = dxBG - length / 2.0
|
||||
if 1.0e-03 < gradient_length:
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartDistAlong=xPBG,
|
||||
HorizontalLength=gradient_length,
|
||||
StartHeight=yPBG,
|
||||
StartGradient=start_slope,
|
||||
EndGradient=start_slope,
|
||||
RadiusOfCurvature=None,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
ifcopenshell.api.alignment.create_layout_segment(file, layout, design_parameters)
|
||||
|
||||
# create vertical curve
|
||||
if 0.0 < length:
|
||||
k = (end_slope - start_slope) / length
|
||||
xBVC = xPVI - length / 2.0
|
||||
yBVC = yPVI - start_slope * length / 2.0
|
||||
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartDistAlong=xBVC,
|
||||
HorizontalLength=length,
|
||||
StartHeight=yBVC,
|
||||
StartGradient=start_slope,
|
||||
EndGradient=end_slope,
|
||||
RadiusOfCurvature=1 / k,
|
||||
PredefinedType="PARABOLICARC",
|
||||
)
|
||||
ifcopenshell.api.alignment.create_layout_segment(file, layout, design_parameters)
|
||||
|
||||
# start of next curve is end of this curve
|
||||
xPBG = xEVC
|
||||
yPBG = yEVC
|
||||
xPVI = xPFG
|
||||
yPVI = yPFG
|
||||
|
||||
# create last gradient run
|
||||
dx = xPVI - xPBG
|
||||
dy = yPVI - yPBG
|
||||
slope = math.tan(math.atan2(dy, dx))
|
||||
gradient_length = dx
|
||||
|
||||
if 1.0e-03 < gradient_length:
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartDistAlong=xPBG,
|
||||
HorizontalLength=gradient_length,
|
||||
StartHeight=yPBG,
|
||||
StartGradient=slope,
|
||||
EndGradient=slope,
|
||||
RadiusOfCurvature=None,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
ifcopenshell.api.alignment.create_layout_segment(file, layout, design_parameters)
|
||||
@@ -1,68 +0,0 @@
|
||||
# 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
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def map_alignment_segments(
|
||||
file: ifcopenshell.file, alignment: entity_instance, composite_curve: entity_instance
|
||||
) -> None:
|
||||
"""
|
||||
Creates IfcCurveSegment entities for the supplied alignment business logic entity instance and assigns them to the composite curve.
|
||||
End-Start points of adjacent segments are evaluated and the IfcCurveSegment.Transition is set.
|
||||
|
||||
This function does not create an IfcShapeRepresentation. Use create_geometric_representation to create all the representations
|
||||
for an alignment. This function only populates the composite curve with IfcCurveSegment entities.
|
||||
|
||||
:param alignment: The business logic alignment, expected to be IfcAlignmentHorizontal, IfcAlignmentVertical, or IfcAlignmentCant
|
||||
:param composite_curve: The IfcCompositeCurve (or subclass) which will receive the IfcCurveSegment
|
||||
:return: None
|
||||
"""
|
||||
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
|
||||
if not alignment.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{alignment.is_a()}"
|
||||
)
|
||||
|
||||
if alignment.is_a("IfcAlignmentHorizontal") and not composite_curve.is_a("IfcCompositeCurve"):
|
||||
raise TypeError(f"Expected to see IfcCompositeCurve, instead received '{composite_curve.is_a()}'.")
|
||||
elif alignment.is_a("IfcAlignmentVertical") and not composite_curve.is_a("IfcGradientCurve"):
|
||||
raise TypeError(f"Expected to see IfcGradientCurve, instead received '{composite_curve.is_a()}'.")
|
||||
elif alignment.is_a("IfcAlignmentCant") and not composite_curve.is_a("IfcSegmentedReferenceCurve"):
|
||||
raise TypeError(f"Expected to see IfcSegmentedReferenceCurve, instead received '{composite_curve.is_a()}'.")
|
||||
|
||||
composite_curve.SelfIntersect = False
|
||||
|
||||
for rel_nests in alignment.IsNestedBy:
|
||||
for layout in rel_nests.RelatedObjects:
|
||||
if layout.is_a("IfcLinearElement"):
|
||||
if alignment.is_a("IfcAlignmentHorizontal"):
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, layout)
|
||||
elif alignment.is_a("IfcAlignmentVertical"):
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, layout)
|
||||
elif alignment.is_a("IfcAlignmentCant"):
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(
|
||||
file, layout, alignment.RailHeadDistance
|
||||
)
|
||||
else:
|
||||
assert False
|
||||
for mapped_segment in mapped_segments:
|
||||
if mapped_segment:
|
||||
ifcopenshell.api.alignment.add_segment_to_curve(file, mapped_segment, composite_curve)
|
||||
@@ -20,21 +20,21 @@ import ifcopenshell
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def name_segments(prefix: str, alignment: entity_instance) -> None:
|
||||
def name_segments(prefix: str, layout: entity_instance) -> None:
|
||||
"""
|
||||
Sets the segment name like ("H1" for horizontal, "V1" for vertical, "C1" for cant)
|
||||
Sets the IfcAlignmentSegment.Name attribute using a prefix and sequence number (e.g. "H1" for horizontal, "V1" for vertical, "C1" for cant)
|
||||
|
||||
:param prefix: The naming prefix
|
||||
:param alignment: The alignment whose segments are to be named. This should be a IfcAlignmentHorizontal, IfcAlignmentVertical or IfcAlignmentCant
|
||||
:param layout: The layout alignment whose segments are to be named. This should be a IfcAlignmentHorizontal, IfcAlignmentVertical or IfcAlignmentCant
|
||||
"""
|
||||
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
|
||||
if not alignment.is_a() in expected_types:
|
||||
if not layout.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{v.is_a()}"
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{layout.is_a()}"
|
||||
)
|
||||
|
||||
i = 1
|
||||
for rel in alignment.IsNestedBy:
|
||||
for rel in layout.IsNestedBy:
|
||||
for segment in rel.RelatedObjects:
|
||||
if segment.is_a("IfcAlignmentSegment"):
|
||||
segment.Name = f"{prefix}{i}"
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
# 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
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
import ifcopenshell.util
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def remove_last_segment(file: ifcopenshell.file, entity: entity_instance) -> entity_instance:
|
||||
"""
|
||||
Removes the last segment from the end of entity.
|
||||
|
||||
:param entity: An IfcAlignmentHorizontal, IfcAlignmentVertical, IfcAlignmentCant or IfcCompositeCurve
|
||||
:return: The segment
|
||||
"""
|
||||
expected_types = [
|
||||
"IfcAlignmentHorizontal",
|
||||
"IfcAlignmentVertical",
|
||||
"IfcAlignmentCant",
|
||||
"IfcCompositeCurve",
|
||||
"IfcGradientCurve",
|
||||
"IfcSegmentedReferenceCurve",
|
||||
]
|
||||
if not entity.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{entity.is_a()}"
|
||||
)
|
||||
|
||||
if entity.is_a("IfcCompositeCurve"):
|
||||
last_segment = entity.Segments[-1]
|
||||
entity.Segments = tuple(set(entity.Segments) - {last_segment})
|
||||
entity.Segments[-1].Transition = "DISCONTINUOUS"
|
||||
return last_segment
|
||||
else:
|
||||
components = ifcopenshell.util.element.get_components(entity)
|
||||
last_segment = components[-1]
|
||||
ifcopenshell.api.nest.unassign_object(file, (last_segment,))
|
||||
return last_segment
|
||||
@@ -123,6 +123,23 @@ def print_alignment(alignment, indent=0):
|
||||
print_alignment(child, indent + 2)
|
||||
|
||||
|
||||
def print_alignment_deep(alignment, indent=0):
|
||||
"""
|
||||
Debugging function to print alignment decomposition, including layout segments
|
||||
"""
|
||||
print(" " * indent, alignment)
|
||||
|
||||
for rel in alignment.IsNestedBy:
|
||||
for child in rel.RelatedObjects:
|
||||
print_alignment_deep(child, indent + 2)
|
||||
if child.is_a("IfcAlignmentSegment"):
|
||||
print(" " * (indent + 4), child.DesignParameters)
|
||||
|
||||
for agg in alignment.IsDecomposedBy:
|
||||
for child in agg.RelatedObjects:
|
||||
print_alignmen_deep(child, indent + 2)
|
||||
|
||||
|
||||
def print_composite_curve(curve):
|
||||
"""
|
||||
Debugging function to print composite curve segments
|
||||
@@ -131,3 +148,17 @@ def print_composite_curve(curve):
|
||||
|
||||
for segment in curve.Segments:
|
||||
print(" " * 2, segment)
|
||||
|
||||
|
||||
def print_composite_curve_deep(curve):
|
||||
"""
|
||||
Debugging function to print composite curve segments, including curve segment details
|
||||
"""
|
||||
print(str(curve)[0:100])
|
||||
|
||||
for segment in curve.Segments:
|
||||
print(" " * 2, segment)
|
||||
print(" " * 4, segment.ParentCurve)
|
||||
print(" " * 4, segment.Placement)
|
||||
print(" " * 4, segment.Placement.Location)
|
||||
print(" " * 4, segment.Placement.RefDirection)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# 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/>.
|
||||
|
||||
"""
|
||||
Coordinate Geometry (cogo) functions primarily for survey points and control monument for layout, parcels, etc.
|
||||
"""
|
||||
|
||||
from .add_survey_point import add_survey_point
|
||||
from .assign_survey_point import assign_survey_point
|
||||
from .edit_survey_point import edit_survey_point
|
||||
from .bearing2dd import bearing2dd
|
||||
|
||||
__all__ = [
|
||||
"add_survey_point",
|
||||
"assign_survey_point",
|
||||
"edit_survey_point",
|
||||
"bearing2dd",
|
||||
]
|
||||
@@ -0,0 +1,47 @@
|
||||
# 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 entity_instance
|
||||
import typing
|
||||
|
||||
def add_survey_point(file: ifcopenshell.file, survey_point: entity_instance, site:entity_instance = None) -> entity_instance:
|
||||
"""
|
||||
Adds a single survey point to the model based on IFC Concept Template 4.1.7.1.2.5.
|
||||
Survey points are located relative to IfcRepresentationContext.WorldCoordinateSystem
|
||||
|
||||
:param survey_point: The survey point
|
||||
:return: an IfcAnnotation entity
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
annotation = ifcopenshell.api.cogo.add_survey_point(file,file.createIfcCartesianPoint(4000.0,3500.0)))
|
||||
"""
|
||||
context = ifcopenshell.util.representation.get_context(file,"Model","Annotation","MODEL_VIEW")
|
||||
shape_representation = file.createIfcShapeRepresentation(ContextOfItems=context,RepresentationIdentifier='Annotation',RepresentationType='Point',Items=[survey_point])
|
||||
representation = file.createIfcProductDefinitionShape(Representations=[shape_representation])
|
||||
annotation = file.createIfcAnnotation(ifcopenshell.guid.new(),ObjectPlacement=context.WorldCoordinateSystem,Representation=representation,PredefinedType="SURVEY")
|
||||
|
||||
if (site == None):
|
||||
site = file.by_type("IfcSite")[0]
|
||||
|
||||
ifcopenshell.api.spatial.assign_container(file,relating_structure=site,products=[annotation])
|
||||
|
||||
return annotation
|
||||
@@ -0,0 +1,38 @@
|
||||
# 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 entity_instance
|
||||
import typing
|
||||
|
||||
def assign_survey_point(annotation: entity_instance, survey_point: entity_instance):
|
||||
"""
|
||||
Assigns a coordinate point to a survey point annotation
|
||||
|
||||
:param annotaton: The survey point annotation
|
||||
:param survey_point: The survey point
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
annotation = ifcopenshell.api.cogo.add_survey_point(file,file.createIfcCartesianPoint(4000.0,3500.0)))
|
||||
ifcopenshell.api.cogo.assign_surve_point(annotation,file.createIfcCartesianPoint(4000.0,3500.0,100.0))
|
||||
"""
|
||||
annotation.Representation.Representations[0].Items = [survey_point]
|
||||
@@ -0,0 +1,106 @@
|
||||
# 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.util.geolocation
|
||||
|
||||
def bearing2dd(bearing: str)->float:
|
||||
"""
|
||||
Converts a quadrant bearing string to decimal degrees
|
||||
|
||||
The format of the string is "N|S dd (mm (ss.s)) E|W"
|
||||
where:
|
||||
N|S is N or S for North or South
|
||||
dd is degree (required)
|
||||
mm is minute (optional, but required if second is provided)
|
||||
ss.s is second (required)
|
||||
E|W is E or W for East or West
|
||||
|
||||
:param str: the bearing string
|
||||
:return: Angle in radian
|
||||
"""
|
||||
error_msg = "Invalid bearing string"
|
||||
|
||||
bearing = bearing.strip() # trim external white space
|
||||
bearing = ' '.join(bearing.split()) # make sure all parts separated by a single space
|
||||
parts = bearing.split()
|
||||
nParts = len(parts)
|
||||
if nParts < 3 or 5 < nParts:
|
||||
raise ValueError(error_msg)
|
||||
|
||||
cY = parts[0]
|
||||
cY = cY.upper()
|
||||
if cY != 'N' and cY != 'S':
|
||||
raise ValueError(error_msg)
|
||||
|
||||
cX = parts[-1]
|
||||
cX = cX.upper()
|
||||
if cX != 'E' and cX != 'W':
|
||||
raise ValueError(error_msg)
|
||||
|
||||
d = 0
|
||||
m = 0
|
||||
s = 0.
|
||||
ms = 0
|
||||
|
||||
if nParts == 3:
|
||||
d = int(parts[1])
|
||||
elif nParts == 4:
|
||||
d = int(parts[1])
|
||||
m = int(parts[2])
|
||||
elif nParts == 5:
|
||||
d = int(parts[1])
|
||||
m = int(parts[2])
|
||||
s = float(parts[3])
|
||||
|
||||
# s in a decimal number
|
||||
# need to break it into whole seconds and milliseconds
|
||||
ms = 100.*(s - int(s))
|
||||
s = int(s)
|
||||
|
||||
if d < 0 or (m < 0 or 60 <= m) or (s < 0 or 60 <= s) or ms < 0:
|
||||
raise ValueError(error_msg)
|
||||
|
||||
if cY == 'N' and cX == 'E':
|
||||
angle = 90.
|
||||
sign = -1.
|
||||
elif cY == 'N' and cX == 'W':
|
||||
angle = 90.
|
||||
sign = 1.
|
||||
elif cY == 'S' and cX == 'E':
|
||||
angle = 270.
|
||||
sign = 1.
|
||||
elif cY == 'S' and cX == 'W':
|
||||
angle = 270.
|
||||
sign = -1.
|
||||
|
||||
try:
|
||||
dms = ifcopenshell.util.geolocation.dms2dd(d,m,s,ms)
|
||||
except ValueError:
|
||||
raise ValueError(error_msg)
|
||||
|
||||
if dms < 0. or 90. < dms:
|
||||
raise ValueError(error_msg)
|
||||
|
||||
angle += sign*dms
|
||||
|
||||
# S 90 E will evaluate to 360
|
||||
if angle == 360.:
|
||||
angle = 0.
|
||||
|
||||
return angle
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# 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 entity_instance
|
||||
import typing
|
||||
|
||||
def edit_survey_point(annotation: entity_instance, x:float,y:float,z:float=0.0):
|
||||
"""
|
||||
Edits the location of a previously defined survey point
|
||||
|
||||
:param survey_point: The survey point
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
annotation = ifcopenshell.api.cogo.add_survey_point(file,file.createIfcCartesianPoint(4000.0,3500.0)))
|
||||
ifcopenshell.api.cogo.edit_surve_point(annotation,3500.0,2000.0)
|
||||
"""
|
||||
if annotation.Representation.Representations[0].Items[0].Dim == 2:
|
||||
annotation.Representation.Representations[0].Items[0].Coordinates = ((x,y))
|
||||
else:
|
||||
annotation.Representation.Representations[0].Items[0].Coordinates = ((x,y,z))
|
||||
Reference in New Issue
Block a user