mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
Updates alignment api. Fixes bugs authoring semantic-only alignment
This commit is contained in:
@@ -70,8 +70,10 @@ from .get_basis_curve import get_basis_curve
|
|||||||
from .get_cant_layout import get_cant_layout
|
from .get_cant_layout import get_cant_layout
|
||||||
from .get_child_alignments import get_child_alignments
|
from .get_child_alignments import get_child_alignments
|
||||||
from .get_curve import get_curve
|
from .get_curve import get_curve
|
||||||
|
from .get_curve_segment import get_curve_segment
|
||||||
from .get_curve_segment_transition_code import get_curve_segment_transition_code
|
from .get_curve_segment_transition_code import get_curve_segment_transition_code
|
||||||
from .get_horizontal_layout import get_horizontal_layout
|
from .get_horizontal_layout import get_horizontal_layout
|
||||||
|
from .get_layout import get_layout
|
||||||
from .get_layout_curve import get_layout_curve
|
from .get_layout_curve import get_layout_curve
|
||||||
from .get_layout_segments import get_layout_segments
|
from .get_layout_segments import get_layout_segments
|
||||||
from .get_mapped_segments import get_mapped_segments
|
from .get_mapped_segments import get_mapped_segments
|
||||||
@@ -86,6 +88,7 @@ from .layout_vertical_alignment_by_pi_method import (
|
|||||||
layout_vertical_alignment_by_pi_method,
|
layout_vertical_alignment_by_pi_method,
|
||||||
)
|
)
|
||||||
from .name_segments import name_segments
|
from .name_segments import name_segments
|
||||||
|
from .update_end_point import update_end_point
|
||||||
from .update_fallback_position import update_fallback_position
|
from .update_fallback_position import update_fallback_position
|
||||||
from .util import *
|
from .util import *
|
||||||
|
|
||||||
@@ -112,8 +115,10 @@ __all__ = [
|
|||||||
"get_cant_layout",
|
"get_cant_layout",
|
||||||
"get_child_alignments",
|
"get_child_alignments",
|
||||||
"get_curve",
|
"get_curve",
|
||||||
|
"get_curve_segment",
|
||||||
"get_curve_segment_transition_code",
|
"get_curve_segment_transition_code",
|
||||||
"get_horizontal_layout",
|
"get_horizontal_layout",
|
||||||
|
"get_layout",
|
||||||
"get_layout_curve",
|
"get_layout_curve",
|
||||||
"get_layout_segments",
|
"get_layout_segments",
|
||||||
"get_parent_alignment",
|
"get_parent_alignment",
|
||||||
@@ -124,6 +129,7 @@ __all__ = [
|
|||||||
"layout_vertical_alignment_by_pi_method",
|
"layout_vertical_alignment_by_pi_method",
|
||||||
"name_segments",
|
"name_segments",
|
||||||
"register_referent_name_callback",
|
"register_referent_name_callback",
|
||||||
|
"update_end_point",
|
||||||
"update_fallback_position",
|
"update_fallback_position",
|
||||||
"get_mapped_segments",
|
"get_mapped_segments",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from typing import Union
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
@@ -24,6 +25,9 @@ import ifcopenshell.geom
|
|||||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||||
import ifcopenshell.util.unit
|
import ifcopenshell.util.unit
|
||||||
from ifcopenshell import entity_instance
|
from ifcopenshell import entity_instance
|
||||||
|
from ifcopenshell.api.alignment._get_segment_endpoint import _get_segment_endpoint
|
||||||
|
from ifcopenshell.api.alignment._update_zero_length_segment_placement import _update_zero_length_segment_placement
|
||||||
|
|
||||||
from ifcopenshell.api.alignment._map_alignment_cant_segment import (
|
from ifcopenshell.api.alignment._map_alignment_cant_segment import (
|
||||||
_map_alignment_cant_segment,
|
_map_alignment_cant_segment,
|
||||||
)
|
)
|
||||||
@@ -39,11 +43,26 @@ from ifcopenshell.api.alignment._update_curve_segment_transition_code import (
|
|||||||
|
|
||||||
|
|
||||||
def _add_curve_segment_to_composite_curve(
|
def _add_curve_segment_to_composite_curve(
|
||||||
file: ifcopenshell.file, curve_segment: entity_instance, composite_curve: entity_instance
|
file: ifcopenshell.file,
|
||||||
):
|
layout_segment: entity_instance,
|
||||||
|
curve_segment: entity_instance,
|
||||||
|
composite_curve: entity_instance,
|
||||||
|
) -> Union[np.array, None]:
|
||||||
|
"""
|
||||||
|
Adds a curve segment to a composite curve and returns the end point of the added segment.
|
||||||
|
|
||||||
|
:param file: The IFC file
|
||||||
|
:param layout_segment: The layout segment
|
||||||
|
:param curve_segment: The curve segment to be added
|
||||||
|
:param composite_curve: The composite curve to which the segment will be added
|
||||||
|
:return: The end point of the added segment or None if an error occurs
|
||||||
|
"""
|
||||||
if 0 < len(curve_segment.UsingCurves):
|
if 0 < len(curve_segment.UsingCurves):
|
||||||
raise TypeError("IfcCurveSegment cannot belong to other curves")
|
raise TypeError("IfcCurveSegment cannot belong to other curves")
|
||||||
|
|
||||||
|
prev_segment = None
|
||||||
|
zero_length_segment = None
|
||||||
|
|
||||||
settings = ifcopenshell.geom.settings()
|
settings = ifcopenshell.geom.settings()
|
||||||
if composite_curve.Segments == None or 0 == len(composite_curve.Segments):
|
if composite_curve.Segments == None or 0 == len(composite_curve.Segments):
|
||||||
# this is the first segment so just add it
|
# this is the first segment so just add it
|
||||||
@@ -56,22 +75,29 @@ def _add_curve_segment_to_composite_curve(
|
|||||||
composite_curve.Segments += (curve_segment,)
|
composite_curve.Segments += (curve_segment,)
|
||||||
assert len(curve_segment.UsingCurves) == 1
|
assert len(curve_segment.UsingCurves) == 1
|
||||||
else:
|
else:
|
||||||
|
# not the first segment, so get the zero_length segment (if it exists)
|
||||||
zero_length_segment = (
|
zero_length_segment = (
|
||||||
composite_curve.Segments[-1]
|
composite_curve.Segments[-1]
|
||||||
if ifcopenshell.api.alignment.has_zero_length_segment(composite_curve)
|
if ifcopenshell.api.alignment.has_zero_length_segment(composite_curve)
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
prev_segment = None
|
|
||||||
|
|
||||||
|
# get the previous segment, which is either the on preceeding the zero length segment (if it exists) or
|
||||||
|
# the last curve segment if there is no zero length segment.
|
||||||
|
# This segment's transition code will need to be updated to match the new curve segment.
|
||||||
if zero_length_segment and 1 < len(composite_curve.Segments):
|
if zero_length_segment and 1 < len(composite_curve.Segments):
|
||||||
prev_segment = composite_curve.Segments[-2]
|
prev_segment = composite_curve.Segments[-2]
|
||||||
elif zero_length_segment == None:
|
elif zero_length_segment == None:
|
||||||
prev_segment = composite_curve.Segments[-1]
|
prev_segment = composite_curve.Segments[-1]
|
||||||
|
|
||||||
curve_segment.Transition = "CONTINUOUS"
|
# IfcCompositeCurve is supposed to be comprised of continuous segments
|
||||||
|
curve_segment.Transition = "DISCONTINUOUS"
|
||||||
|
|
||||||
|
# get a list of all but the last segment (skips the zero length segment, if it exists)
|
||||||
segments = composite_curve.Segments[0:-1]
|
segments = composite_curve.Segments[0:-1]
|
||||||
if zero_length_segment:
|
if zero_length_segment:
|
||||||
|
# if there is a zero length segment, need to append new curve_segment and the zero length segment to the array
|
||||||
|
# them update the composite curve segments with the new array
|
||||||
segments += (
|
segments += (
|
||||||
curve_segment,
|
curve_segment,
|
||||||
zero_length_segment,
|
zero_length_segment,
|
||||||
@@ -79,31 +105,23 @@ def _add_curve_segment_to_composite_curve(
|
|||||||
composite_curve.Segments = []
|
composite_curve.Segments = []
|
||||||
composite_curve.Segments += segments
|
composite_curve.Segments += segments
|
||||||
else:
|
else:
|
||||||
|
# if there is no zero length segment, we can just append the new curve segment to the existing array of segments
|
||||||
composite_curve.Segments += (curve_segment,)
|
composite_curve.Segments += (curve_segment,)
|
||||||
|
|
||||||
if prev_segment:
|
if prev_segment:
|
||||||
_update_curve_segment_transition_code(prev_segment, curve_segment)
|
_update_curve_segment_transition_code(prev_segment, curve_segment)
|
||||||
|
|
||||||
if zero_length_segment:
|
end_point = _get_segment_endpoint(file, layout_segment)
|
||||||
settings = ifcopenshell.geom.settings()
|
if zero_length_segment:
|
||||||
segment_fn = ifcopenshell_wrapper.map_shape(settings, curve_segment.wrapped_data)
|
_update_zero_length_segment_placement(file, zero_length_segment, end_point)
|
||||||
segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, segment_fn)
|
_update_curve_segment_transition_code(curve_segment, zero_length_segment)
|
||||||
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
|
return end_point
|
||||||
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:
|
def _add_segment_to_curve(
|
||||||
|
file: ifcopenshell.file, layout_segment: entity_instance, curve: entity_instance
|
||||||
|
) -> Union[np.array, None]:
|
||||||
"""
|
"""
|
||||||
Creates an IfcCurveSegment from the IfcAlignmentSegment and adds it to the representation curve. The IfcCurveSegment is added
|
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
|
at the end of the curve, but before the manditory zero length segment. The IfcCurveSegment.Transition for the segment
|
||||||
@@ -114,16 +132,18 @@ def _add_segment_to_curve(file: ifcopenshell.file, segment: entity_instance, cur
|
|||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
expected_types = ["IfcAlignmentSegment"]
|
expected_types = ["IfcAlignmentSegment"]
|
||||||
if not segment.is_a() in expected_types:
|
if not layout_segment.is_a() in expected_types:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{segment.is_a()}"
|
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{layout_segment.is_a()}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment") and not curve.is_a("IfcCompositeCurve"):
|
if layout_segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment") and not curve.is_a("IfcCompositeCurve"):
|
||||||
raise TypeError(f"Expected to see IfcCompositeCurve, instead received '{curve.is_a()}'.")
|
raise TypeError(f"Expected to see IfcCompositeCurve, instead received '{curve.is_a()}'.")
|
||||||
elif segment.DesignParameters.is_a("IfcAlignmentVerticalSegment") and not curve.is_a("IfcGradientCurve"):
|
elif layout_segment.DesignParameters.is_a("IfcAlignmentVerticalSegment") and not curve.is_a("IfcGradientCurve"):
|
||||||
raise TypeError(f"Expected to see IfcGradientCurve, instead received '{curve.is_a()}'.")
|
raise TypeError(f"Expected to see IfcGradientCurve, instead received '{curve.is_a()}'.")
|
||||||
elif segment.DesignParameters.is_a("IfcAlignmentCantSegment") and not curve.is_a("IfcSegmentedReferenceCurve"):
|
elif layout_segment.DesignParameters.is_a("IfcAlignmentCantSegment") and not curve.is_a(
|
||||||
|
"IfcSegmentedReferenceCurve"
|
||||||
|
):
|
||||||
raise TypeError(f"Expected to see IfcSegmentedReferenceCurve, instead received '{curve.is_a()}'.")
|
raise TypeError(f"Expected to see IfcSegmentedReferenceCurve, instead received '{curve.is_a()}'.")
|
||||||
|
|
||||||
expected_type = "IfcCompositeCurve"
|
expected_type = "IfcCompositeCurve"
|
||||||
@@ -131,16 +151,18 @@ def _add_segment_to_curve(file: ifcopenshell.file, segment: entity_instance, cur
|
|||||||
raise TypeError(f"Expected to see {expected_type}, instead received {curve.is_a()}.")
|
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)
|
# map the IfcAlignmentSegment to an IfcCurveSegment (or two in the case of helmert curves)
|
||||||
if segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
|
if layout_segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
|
||||||
mapped_segments = _map_alignment_horizontal_segment(file, segment)
|
mapped_segments = _map_alignment_horizontal_segment(file, layout_segment)
|
||||||
elif segment.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
|
elif layout_segment.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
|
||||||
mapped_segments = _map_alignment_vertical_segment(file, segment)
|
mapped_segments = _map_alignment_vertical_segment(file, layout_segment)
|
||||||
elif segment.DesignParameters.is_a("IfcAlignmentCantSegment"):
|
elif layout_segment.DesignParameters.is_a("IfcAlignmentCantSegment"):
|
||||||
cant_layout = segment.Nests[0].RelatingObject
|
cant_layout = layout_segment.Nests[0].RelatingObject
|
||||||
mapped_segments = _map_alignment_cant_segment(file, segment, cant_layout.RailHeadDistance)
|
mapped_segments = _map_alignment_cant_segment(file, layout_segment, cant_layout.RailHeadDistance)
|
||||||
else:
|
else:
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
for mapped_segment in mapped_segments:
|
for mapped_segment in mapped_segments:
|
||||||
if mapped_segment:
|
if mapped_segment:
|
||||||
_add_curve_segment_to_composite_curve(file, mapped_segment, curve)
|
end_point = _add_curve_segment_to_composite_curve(file, layout_segment, mapped_segment, curve)
|
||||||
|
|
||||||
|
return end_point
|
||||||
|
|||||||
@@ -16,12 +16,14 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import math
|
from typing import Union
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api.alignment
|
import ifcopenshell.api.alignment
|
||||||
|
from ifcopenshell.api.alignment import _map_alignment_cant_segment
|
||||||
|
from ifcopenshell.api.alignment._update_zero_length_segment_placement import _update_zero_length_segment_placement
|
||||||
import ifcopenshell.api.nest
|
import ifcopenshell.api.nest
|
||||||
import ifcopenshell.api.pset
|
import ifcopenshell.api.pset
|
||||||
import ifcopenshell.geom
|
import ifcopenshell.geom
|
||||||
@@ -29,15 +31,29 @@ import ifcopenshell.util.alignment
|
|||||||
import ifcopenshell.util.unit
|
import ifcopenshell.util.unit
|
||||||
from ifcopenshell import entity_instance, ifcopenshell_wrapper
|
from ifcopenshell import entity_instance, ifcopenshell_wrapper
|
||||||
from ifcopenshell.api.alignment._add_segment_to_curve import _add_segment_to_curve
|
from ifcopenshell.api.alignment._add_segment_to_curve import _add_segment_to_curve
|
||||||
|
from ifcopenshell.api.alignment._get_segment_endpoint import _get_segment_endpoint
|
||||||
from ifcopenshell.api.alignment._get_segment_start_point_label import (
|
from ifcopenshell.api.alignment._get_segment_start_point_label import (
|
||||||
_get_segment_start_point_label,
|
_get_segment_start_point_label,
|
||||||
)
|
)
|
||||||
|
from ifcopenshell.api.alignment._map_alignment_cant_segment import (
|
||||||
|
_map_alignment_cant_segment,
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _add_segment_to_layout(file: ifcopenshell.file, layout: entity_instance, segment: entity_instance) -> None:
|
def _add_segment_to_layout(
|
||||||
|
file: ifcopenshell.file, layout: entity_instance, layout_segment: entity_instance
|
||||||
|
) -> Union[np.array, None]:
|
||||||
"""
|
"""
|
||||||
Adds an IfcAlignmentSegment to a layout alignment (IfcAlignmentHorizontal/Vertical/Cant). This segment is added at the end
|
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.
|
of the layout, before the manditory zero length segment (if it exists).
|
||||||
|
If the layout has a corresponding geometric representation, an IfcCurveSegment is created for it and appended at the end
|
||||||
|
of the representation curve, before the zero length segment (if it exists).
|
||||||
|
|
||||||
:param layout: The layout alignment
|
:param layout: The layout alignment
|
||||||
:param segment: The segment to be appended
|
:param segment: The segment to be appended
|
||||||
@@ -50,160 +66,31 @@ def _add_segment_to_layout(file: ifcopenshell.file, layout: entity_instance, seg
|
|||||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received {layout.is_a()}"
|
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received {layout.is_a()}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not (segment.is_a("IfcAlignmentSegment")):
|
if not (layout_segment.is_a("IfcAlignmentSegment")):
|
||||||
raise TypeError(f"Expected to see IfcAlignmentSegment, instead received {segment.is_a()}.")
|
raise TypeError(f"Expected to see IfcAlignmentSegment, instead received {layout_segment.is_a()}.")
|
||||||
|
|
||||||
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
|
|
||||||
|
|
||||||
# add the new segment to the layout
|
# add the new segment to the layout
|
||||||
ifcopenshell.api.nest.assign_object(file, related_objects=[segment], relating_object=layout)
|
ifcopenshell.api.nest.assign_object(file, related_objects=[layout_segment], relating_object=layout)
|
||||||
|
|
||||||
# segment is attached at the end, but this is after the zero length segment
|
# segment is attached at the end, but this is after the zero length segment
|
||||||
# swap the last two segments
|
# swap the last two segments
|
||||||
ifcopenshell.api.nest.reorder_nesting(file, segment, -1, -1)
|
ifcopenshell.api.nest.reorder_nesting(file, layout_segment, -1, -1)
|
||||||
|
|
||||||
|
# For cant segments, the end point depends on the next segment. The next segment is the
|
||||||
|
# zero-length segment and it hasn't been updated to match the end point.
|
||||||
|
# For this reason, we can't compute the end point from the IfcCurveSegment, but instead we
|
||||||
|
# compute it from the layout segment design parameters.
|
||||||
|
end_point = _get_segment_endpoint(file, layout_segment)
|
||||||
|
|
||||||
|
# update the position of the zero length layout segment to be at the end point of the newly added segment
|
||||||
|
segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(layout)
|
||||||
|
zero_length_layout_segment = segment_nest.RelatedObjects[-1]
|
||||||
|
_update_zero_length_segment_placement(file, zero_length_layout_segment, end_point)
|
||||||
|
|
||||||
|
# if there is a curve defined, add a new IfcCurveSegment to it.
|
||||||
|
# _add_segment_to_curve maps the layout segment to the appropriate IfcCurveSegment type and adds it to the curve.
|
||||||
|
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
|
||||||
if curve:
|
if curve:
|
||||||
# add the new segment to the geometric representation curve
|
_add_segment_to_curve(file, layout_segment, curve)
|
||||||
_add_segment_to_curve(file, segment, curve)
|
|
||||||
|
|
||||||
# gather information to:
|
return end_point
|
||||||
# (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_start_station(file, alignment)
|
|
||||||
station = start_station + dist_along
|
|
||||||
|
|
||||||
# update the zero length layout segment
|
|
||||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
|
|
||||||
|
|
||||||
segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(layout)
|
|
||||||
zero_length_segment = segment_nest.RelatedObjects[-1]
|
|
||||||
mapped_segments = ifcopenshell.api.alignment.get_mapped_segments(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("IfcAlignmentHorizontalSegment"):
|
|
||||||
x = float(end[0, 3]) / unit_scale
|
|
||||||
y = float(end[1, 3]) / unit_scale
|
|
||||||
dx = float(end[0, 0])
|
|
||||||
dy = float(end[1, 0])
|
|
||||||
zero_length_segment.DesignParameters.StartPoint.Coordinates = (x, y)
|
|
||||||
zero_length_segment.DesignParameters.StartDirection = dy / dx
|
|
||||||
elif 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.PositionedRelativeTo[0].RelatingPositioningElement
|
|
||||||
end_referent.Name = f"{_get_segment_start_point_label(zero_length_segment,None)} ({ifcopenshell.util.alignment.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)
|
|
||||||
|
|
||||||
start_station = ifcopenshell.api.alignment.get_alignment_start_station(file, alignment)
|
|
||||||
end_referent_station = start_station + start_dist_along
|
|
||||||
pset_stationing = ifcopenshell.api.pset.add_pset(file, product=end_referent, name="Pset_Stationing")
|
|
||||||
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties={"Station": end_referent_station})
|
|
||||||
|
|
||||||
# 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 = segment_nest.RelatedObjects[-3] if 2 < len(segment_nest.RelatedObjects) else None
|
|
||||||
name = f"{_get_segment_start_point_label(prev_segment,segment)} ({ifcopenshell.util.alignment.station_as_string(file,station)})"
|
|
||||||
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
|
||||||
file, alignment, distance_along=dist_along, station=station, name=name, positioned_product=segment
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
ref_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
|
||||||
stationing_referent = ref_nest.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)
|
|
||||||
|
|||||||
@@ -42,17 +42,8 @@ def _add_zero_length_segment(file: ifcopenshell.file, layout: entity_instance) -
|
|||||||
f"Expected layout type to be one of {[_ for _ in expected_types]}, instead received {layout.is_a()}"
|
f"Expected layout type to be one of {[_ for _ in expected_types]}, instead received {layout.is_a()}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not ifcopenshell.api.alignment.add_zero_length_segment(file, layout, include_referent=False):
|
ifcopenshell.api.alignment.add_zero_length_segment(file, layout)
|
||||||
return # zero length segment not added, probably because it already exists
|
|
||||||
|
|
||||||
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
|
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
|
||||||
|
|
||||||
if curve:
|
if curve:
|
||||||
ifcopenshell.api.alignment.add_zero_length_segment(file, curve)
|
ifcopenshell.api.alignment.add_zero_length_segment(file, curve)
|
||||||
|
|
||||||
segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(layout)
|
|
||||||
segment = segment_nest.RelatedObjects[-1]
|
|
||||||
alignment = ifcopenshell.api.alignment.get_alignment(layout)
|
|
||||||
station = ifcopenshell.api.alignment.get_alignment_start_station(file, alignment)
|
|
||||||
name = f"{_get_segment_start_point_label(segment,None)} ({ifcopenshell.util.alignment.station_as_string(file,station)})"
|
|
||||||
referent = ifcopenshell.api.alignment.add_stationing_referent(file, alignment, 0.0, station, name, segment)
|
|
||||||
|
|||||||
+3
-8
@@ -35,6 +35,8 @@ def _create_geometric_representation(file: ifcopenshell.file, alignment: entity_
|
|||||||
4) Vertical only (this occurs when horizontal is reused from a parent alignment) -> IfcGradientCurve
|
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
|
5) Vertical + Cant (this occurs when horizontal is reused from a parent alignment) -> IfcSegmentedReferenceCurve
|
||||||
|
|
||||||
|
This method creates the geometric representation entity and assigns it to the alignment, but does not populate the geometry of the representation.
|
||||||
|
|
||||||
:param alignment: The alignment for which the representation is being created
|
:param alignment: The alignment for which the representation is being created
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
@@ -43,13 +45,6 @@ def _create_geometric_representation(file: ifcopenshell.file, alignment: entity_
|
|||||||
if not alignment.is_a(expected_type):
|
if not alignment.is_a(expected_type):
|
||||||
raise TypeError(f"Expected {expected_type} but got {alignment.is_a()}")
|
raise TypeError(f"Expected {expected_type} but got {alignment.is_a()}")
|
||||||
|
|
||||||
placement = file.createIfcLocalPlacement(
|
|
||||||
PlacementRelTo=None,
|
|
||||||
RelativePlacement=file.createIfcAxis2Placement2D(Location=file.createIfcCartesianPoint(Coordinates=(0.0, 0.0))),
|
|
||||||
)
|
|
||||||
|
|
||||||
alignment.ObjectPlacement = placement
|
|
||||||
|
|
||||||
axis_geom_subcontext = ifcopenshell.api.alignment.get_axis_subcontext(file)
|
axis_geom_subcontext = ifcopenshell.api.alignment.get_axis_subcontext(file)
|
||||||
|
|
||||||
layouts = ifcopenshell.api.alignment.get_alignment_layouts(alignment)
|
layouts = ifcopenshell.api.alignment.get_alignment_layouts(alignment)
|
||||||
@@ -126,7 +121,7 @@ def _create_geometric_representation(file: ifcopenshell.file, alignment: entity_
|
|||||||
ifcopenshell.api.geometry.assign_representation(file, alignment, representation)
|
ifcopenshell.api.geometry.assign_representation(file, alignment, representation)
|
||||||
|
|
||||||
for child_alignment in children:
|
for child_alignment in children:
|
||||||
child_alignment.ObjectPlacement = placement
|
child_alignment.ObjectPlacement = alignment.ObjectPlacement
|
||||||
child_layouts = ifcopenshell.api.alignment.get_alignment_layouts(child_alignment)
|
child_layouts = ifcopenshell.api.alignment.get_alignment_layouts(child_alignment)
|
||||||
if len(child_layouts) == 1:
|
if len(child_layouts) == 1:
|
||||||
assert child_layouts[0].is_a("IfcAlignmentVertical")
|
assert child_layouts[0].is_a("IfcAlignmentVertical")
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# 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.api.alignment
|
||||||
|
from ifcopenshell import entity_instance, ifcopenshell_wrapper
|
||||||
|
from ifcopenshell.api.alignment._map_alignment_segment import _map_alignment_segment
|
||||||
|
from typing import Union
|
||||||
|
import math
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def _get_segment_endpoint(file: ifcopenshell.file, segment: entity_instance) -> Union[np.array, None]:
|
||||||
|
"""
|
||||||
|
Computes the 4x4 matrix for a segment end point. The segment can be an IfcAlignmentSegment
|
||||||
|
or IfcCurveSegment
|
||||||
|
"""
|
||||||
|
|
||||||
|
expected_types = ["IfcAlignmentSegment", "IfcCurveSegment"]
|
||||||
|
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()}"
|
||||||
|
)
|
||||||
|
|
||||||
|
file.begin_transaction() # use a transaction so we can discard any temporary IFC entities created
|
||||||
|
|
||||||
|
curve_segment = segment
|
||||||
|
if segment.is_a("IfcAlignmentSegment"):
|
||||||
|
layout = ifcopenshell.api.alignment.get_layout(segment)
|
||||||
|
mapped_segments = _map_alignment_segment(file, layout, segment)
|
||||||
|
curve_segment = mapped_segments[0] if mapped_segments[1] == None else mapped_segments[1]
|
||||||
|
|
||||||
|
# Inside of the IfcOpenShell C++ implementation where the IfcCurveSegment calculations occur,
|
||||||
|
# the composite curve owning the segment is evaluated to determine if a horizontal, vertical, or cant segment is being evaluated.
|
||||||
|
# This is necessary to determine how the end point of the curve segment is calculated.
|
||||||
|
# A temporary curve segment has been created and it needs to be associated with the correct composite curve for the end point to be calculated correctly.
|
||||||
|
# Inside the C++ implementation, if a composite curve isn't associated with the segment the segment is assumed to be horizontal. For this reason
|
||||||
|
# a temporary IfcCompositeCurve for horizontal segments doesn't need to be created.
|
||||||
|
if layout.is_a("IfcAlignmentVertical"):
|
||||||
|
gc = file.createIfcGradientCurve(Segments=[curve_segment])
|
||||||
|
elif layout.is_a("IfcAlignmentCant"):
|
||||||
|
# The evaluation of cant segments depend on the start conditions of the next segment. In the absense of a next segment the
|
||||||
|
# optional EndPoint is used. Since a tempoaryar IfcSegmentReferenceCurve is being used, there is not a next segment.
|
||||||
|
# For this reason the EndPoint must be created from the design parameters of the sementic segment definiton.
|
||||||
|
Dsl = segment.DesignParameters.StartCantLeft
|
||||||
|
Dsr = segment.DesignParameters.StartCantRight
|
||||||
|
Del = segment.DesignParameters.EndCantLeft if segment.DesignParameters.EndCantLeft != None else Dsl
|
||||||
|
Der = segment.DesignParameters.EndCantRight if segment.DesignParameters.EndCantRight != None else Dsr
|
||||||
|
cant = Der - Del
|
||||||
|
rh = layout.RailHeadDistance
|
||||||
|
Ay = cant / rh
|
||||||
|
Az = math.sqrt(rh**2 - cant**2) / rh
|
||||||
|
|
||||||
|
src = file.createIfcSegmentedReferenceCurve(
|
||||||
|
Segments=[curve_segment],
|
||||||
|
EndPoint=file.createIfcAxis2Placement3D(
|
||||||
|
Location=file.createIfcCartesianPoint((segment.DesignParameters.StartDistAlong, 0.5 * cant, 0.0)),
|
||||||
|
RefDirection=file.createIfcDirection((1.0, 0.0, 0.0)),
|
||||||
|
Axis=file.createIfcDirection((0.0, Ay, Az)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
x = segment_fn.end()
|
||||||
|
e = segment_evaluator.evaluate(x)
|
||||||
|
end = np.array(e)
|
||||||
|
|
||||||
|
file.discard_transaction()
|
||||||
|
|
||||||
|
return end
|
||||||
@@ -24,10 +24,12 @@ from ifcopenshell import entity_instance
|
|||||||
|
|
||||||
|
|
||||||
def _get_axis(file: ifcopenshell.file, Ds: float, rail_head_distance: float) -> entity_instance:
|
def _get_axis(file: ifcopenshell.file, Ds: float, rail_head_distance: float) -> entity_instance:
|
||||||
Dy = rail_head_distance
|
# solves the ratio right triangle legs to hypotenous
|
||||||
Dz = 2 * Ds
|
# Dh^2 = Dy^2 + Dz^2
|
||||||
D = math.sqrt(Dy * Dy + Dz * Dz)
|
Dh = rail_head_distance # hypotenous
|
||||||
return file.createIfcDirection((0.0, Dz / D, Dy / D))
|
Dy = 2 * Ds # horizontal leg
|
||||||
|
Dz = math.sqrt(Dh * Dh - Dy * Dy) # vertical leg
|
||||||
|
return file.createIfcDirection((0.0, Dy / Dh, Dz / Dh))
|
||||||
|
|
||||||
|
|
||||||
def _map_constant_cant(
|
def _map_constant_cant(
|
||||||
@@ -54,7 +56,7 @@ def _map_constant_cant(
|
|||||||
Transition=transition,
|
Transition=transition,
|
||||||
Placement=file.createIfcAxis2Placement3D(
|
Placement=file.createIfcAxis2Placement3D(
|
||||||
Location=start_point,
|
Location=start_point,
|
||||||
Axis=_get_axis(file, Ds, rail_head_distance),
|
Axis=_get_axis(file, 0.5 * (Dsr - Dsl), rail_head_distance),
|
||||||
RefDirection=file.createIfcDirection((math.cos(start_direction), math.sin(start_direction), 0.0)),
|
RefDirection=file.createIfcDirection((math.cos(start_direction), math.sin(start_direction), 0.0)),
|
||||||
),
|
),
|
||||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# 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 collections.abc import Sequence
|
||||||
|
|
||||||
|
import ifcopenshell
|
||||||
|
from ifcopenshell import entity_instance
|
||||||
|
|
||||||
|
from ifcopenshell.api.alignment._map_alignment_cant_segment import (
|
||||||
|
_map_alignment_cant_segment,
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _map_alignment_segment(
|
||||||
|
file: ifcopenshell.file, layout: entity_instance, segment: entity_instance
|
||||||
|
) -> Sequence[entity_instance]:
|
||||||
|
"""
|
||||||
|
Maps an IfcAlignmentSegment to its corresponding IfcCurveSegment(s) in the geometric representation.
|
||||||
|
The mapping is done based on the layout type and segment type.
|
||||||
|
"""
|
||||||
|
if layout.is_a("IfcAlignmentHorizontal"):
|
||||||
|
mapped_segments = _map_alignment_horizontal_segment(file, segment)
|
||||||
|
elif layout.is_a("IfcAlignmentVertical"):
|
||||||
|
mapped_segments = _map_alignment_vertical_segment(file, segment)
|
||||||
|
else:
|
||||||
|
mapped_segments = _map_alignment_cant_segment(file, segment, layout.RailHeadDistance)
|
||||||
|
|
||||||
|
return mapped_segments
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
# 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 numpy as np
|
||||||
|
|
||||||
|
import ifcopenshell
|
||||||
|
import math
|
||||||
|
import ifcopenshell.api.alignment
|
||||||
|
import ifcopenshell.util.unit
|
||||||
|
from ifcopenshell import entity_instance
|
||||||
|
|
||||||
|
|
||||||
|
def _update_zero_length_segment_placement(
|
||||||
|
file: ifcopenshell.file, zero_length_segment: entity_instance, placement: np.array
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Updates the placement of a zero length segment (i.e. a segment with identical start and end point) based on a 4x4 placement matrix.
|
||||||
|
The zero_length_segment can be an IfcAlignmentSegment or IfcCurveSegment.
|
||||||
|
"""
|
||||||
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
|
||||||
|
x = float(placement[0, 3]) / unit_scale
|
||||||
|
y = float(placement[1, 3]) / unit_scale
|
||||||
|
z = float(placement[2, 3]) / unit_scale
|
||||||
|
Rdx = float(placement[0, 0])
|
||||||
|
Rdy = float(placement[1, 0])
|
||||||
|
Rdz = float(placement[2, 0])
|
||||||
|
Adx = float(placement[0, 2])
|
||||||
|
Ady = float(placement[1, 2])
|
||||||
|
Adz = float(placement[2, 2])
|
||||||
|
|
||||||
|
if zero_length_segment.is_a("IfcCurveSegment"):
|
||||||
|
if zero_length_segment.Placement.is_a("IfcAxis2Placement2D"):
|
||||||
|
zero_length_segment.Placement.Location.Coordinates = (x, y)
|
||||||
|
zero_length_segment.Placement.RefDirection.DirectionRatios = (Rdx, Rdy)
|
||||||
|
else:
|
||||||
|
zero_length_segment.Placement.Location.Coordinates = (x, y, z)
|
||||||
|
zero_length_segment.Placement.RefDirection.DirectionRatios = (Rdx, Rdy, Rdz)
|
||||||
|
zero_length_segment.Placement.Axis.DirectionRatios = (Adx, Ady, Adz)
|
||||||
|
elif zero_length_segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
|
||||||
|
zero_length_segment.DesignParameters.StartPoint.Coordinates = (x, y)
|
||||||
|
zero_length_segment.DesignParameters.StartDirection = math.atan(Rdy / Rdx)
|
||||||
|
elif zero_length_segment.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
|
||||||
|
zero_length_segment.DesignParameters.StartDistAlong = x
|
||||||
|
zero_length_segment.DesignParameters.StartHeight = y
|
||||||
|
zero_length_segment.DesignParameters.StartGradient = Rdy / Rdx
|
||||||
|
zero_length_segment.DesignParameters.EndGradient = zero_length_segment.DesignParameters.StartGradient
|
||||||
|
else:
|
||||||
|
slope = Ady / math.sqrt(Ady**2 + Adz**2)
|
||||||
|
layout = ifcopenshell.api.alignment.get_layout(zero_length_segment)
|
||||||
|
railhead = layout.RailHeadDistance
|
||||||
|
|
||||||
|
zero_length_segment.DesignParameters.StartDistAlong = x
|
||||||
|
zero_length_segment.DesignParameters.StartCantLeft = y - slope * railhead / 2.0
|
||||||
|
zero_length_segment.DesignParameters.StartCantRight = y + slope * railhead / 2.0
|
||||||
|
zero_length_segment.DesignParameters.EndCantLeft = zero_length_segment.DesignParameters.StartCantLeft
|
||||||
|
zero_length_segment.DesignParameters.EndCantRight = zero_length_segment.DesignParameters.StartCantRight
|
||||||
@@ -20,6 +20,7 @@ import numpy as np
|
|||||||
|
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api.alignment
|
import ifcopenshell.api.alignment
|
||||||
|
from ifcopenshell.api.alignment.update_fallback_position import update_fallback_position
|
||||||
import ifcopenshell.api.pset
|
import ifcopenshell.api.pset
|
||||||
import ifcopenshell.geom
|
import ifcopenshell.geom
|
||||||
import ifcopenshell.guid
|
import ifcopenshell.guid
|
||||||
@@ -58,7 +59,7 @@ def add_stationing_referent(
|
|||||||
|
|
||||||
object_placement = None
|
object_placement = None
|
||||||
representation = None
|
representation = None
|
||||||
if basis_curve:
|
if basis_curve and basis_curve.is_a("IfcCompositeCurve") and 0 < len(basis_curve.Segments):
|
||||||
object_placement = file.createIfcLinearPlacement(
|
object_placement = file.createIfcLinearPlacement(
|
||||||
RelativePlacement=file.createIfcAxis2PlacementLinear(
|
RelativePlacement=file.createIfcAxis2PlacementLinear(
|
||||||
Location=file.createIfcPointByDistanceExpression(
|
Location=file.createIfcPointByDistanceExpression(
|
||||||
@@ -71,54 +72,13 @@ def add_stationing_referent(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
is_valid_curve = True
|
update_fallback_position(file, object_placement)
|
||||||
if basis_curve.is_a("IfcCompositeCurve") and len(basis_curve.Segments) == 0:
|
else:
|
||||||
is_valid_curve = False
|
object_placement = file.createIfcLocalPlacement(
|
||||||
if basis_curve.is_a("IfcPolyline") and len(basis_curve.Points) < 2:
|
PlacementRelTo=None,
|
||||||
is_valid_curve = False
|
RelativePlacement=file.createIfcAxis2Placement2D(
|
||||||
elif basis_curve.is_a("IfcIndexedPolyCurve") and len(basis_curve.Points.CoordList) < 2:
|
Location=file.createIfcCartesianPoint(alignment.ObjectPlacement.RelativePlacement.Location.Coordinates)
|
||||||
is_valid_curve = False
|
),
|
||||||
|
|
||||||
if is_valid_curve:
|
|
||||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
|
|
||||||
|
|
||||||
settings = ifcopenshell.geom.settings()
|
|
||||||
fn = ifcopenshell_wrapper.map_shape(settings, basis_curve.wrapped_data)
|
|
||||||
|
|
||||||
if basis_curve.is_a("IfcPolyline") or basis_curve.is_a("IfcIndexedPolyCurve"):
|
|
||||||
fn = ifcopenshell_wrapper.convert_loop_to_function_item(fn)
|
|
||||||
|
|
||||||
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])
|
|
||||||
else:
|
|
||||||
x = 0.0
|
|
||||||
y = 0.0
|
|
||||||
z = 0.0
|
|
||||||
rx = 1.0
|
|
||||||
ry = 0.0
|
|
||||||
rz = 0.0
|
|
||||||
ax = 0.0
|
|
||||||
ay = 0.0
|
|
||||||
az = 1.0
|
|
||||||
|
|
||||||
object_placement.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
|
# this commented out code is what you would do to add a geometric representation of the referent
|
||||||
@@ -144,7 +104,12 @@ def add_stationing_referent(
|
|||||||
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties={"Station": station})
|
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties={"Station": station})
|
||||||
|
|
||||||
nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
||||||
nest.RelatedObjects += (referent,)
|
if nest is None:
|
||||||
|
nest = file.createIfcRelNests(
|
||||||
|
GlobalId=ifcopenshell.guid.new(), RelatingObject=alignment, RelatedObjects=(referent,)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
nest.RelatedObjects += (referent,)
|
||||||
|
|
||||||
nest.RelatedObjects = sorted(
|
nest.RelatedObjects = sorted(
|
||||||
nest.RelatedObjects, key=lambda x: ifcopenshell.util.element.get_pset(x, name="Pset_Stationing", prop="Station")
|
nest.RelatedObjects, key=lambda x: ifcopenshell.util.element.get_pset(x, name="Pset_Stationing", prop="Station")
|
||||||
|
|||||||
@@ -18,14 +18,12 @@
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api.alignment
|
import ifcopenshell.api.alignment
|
||||||
|
from ifcopenshell.api.alignment._get_segment_endpoint import _get_segment_endpoint
|
||||||
|
from ifcopenshell.api.alignment._update_zero_length_segment_placement import _update_zero_length_segment_placement
|
||||||
import ifcopenshell.api.nest
|
import ifcopenshell.api.nest
|
||||||
import ifcopenshell.geom
|
|
||||||
import ifcopenshell.ifcopenshell_wrapper as wrapper
|
import ifcopenshell.ifcopenshell_wrapper as wrapper
|
||||||
import ifcopenshell.util.alignment
|
|
||||||
import ifcopenshell.util.unit
|
import ifcopenshell.util.unit
|
||||||
from ifcopenshell import entity_instance
|
from ifcopenshell import entity_instance
|
||||||
from ifcopenshell.api.alignment._get_segment_start_point_label import (
|
from ifcopenshell.api.alignment._get_segment_start_point_label import (
|
||||||
@@ -42,14 +40,13 @@ from ifcopenshell.api.alignment._update_curve_segment_transition_code import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_zero_length_segment(file: ifcopenshell.file, layout: entity_instance, include_referent: bool = True) -> bool:
|
def add_zero_length_segment(file: ifcopenshell.file, layout: entity_instance) -> bool:
|
||||||
"""
|
"""
|
||||||
Adds a zero length segment to the end of a layout.
|
Adds a zero length segment to the end of a layout.
|
||||||
|
|
||||||
If the layout already has a zero length segment, nothing is changed.
|
If the layout already has a zero length segment, nothing is changed.
|
||||||
|
|
||||||
:param layout: An IfcAlignmentHorizontal, IfcAlignmentVertical, IfcAlignmentCant, IfcCompositeCurve, IfcGradientCurve, IfcSegmentedReferenceCurve
|
:param layout: An IfcAlignmentHorizontal, IfcAlignmentVertical, IfcAlignmentCant, IfcCompositeCurve, IfcGradientCurve, IfcSegmentedReferenceCurve
|
||||||
:param include_referent: If True, an IfcReferent representing the ending point of the layout is included for IfcLinearElement layouts (i.e. business logic)
|
|
||||||
:return: True if segment is added
|
:return: True if segment is added
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -74,28 +71,6 @@ def add_zero_length_segment(file: ifcopenshell.file, layout: entity_instance, in
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
if layout.is_a("IfcCompositeCurve") or layout.is_a("IfcGradientCurve") or layout.is_a("IfcSegmentedReferenceCurve"):
|
if layout.is_a("IfcCompositeCurve") or layout.is_a("IfcGradientCurve") or layout.is_a("IfcSegmentedReferenceCurve"):
|
||||||
x = 0.0
|
|
||||||
y = 0.0
|
|
||||||
dx = 1.0
|
|
||||||
dy = 0.0
|
|
||||||
segment_start = 0.0
|
|
||||||
|
|
||||||
last_segment = None
|
|
||||||
if layout.Segments and 0 < len(layout.Segments):
|
|
||||||
# If there are segments, get the last segment and compute the end point and tangent direction
|
|
||||||
# because this becomes of placement of the zero length segment
|
|
||||||
last_segment = layout.Segments[-1]
|
|
||||||
settings = ifcopenshell.geom.settings()
|
|
||||||
fn = wrapper.map_shape(settings, last_segment.wrapped_data)
|
|
||||||
eval = wrapper.function_item_evaluator(settings, fn)
|
|
||||||
e = np.array(eval.evaluate(fn.end()))
|
|
||||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
|
|
||||||
e[:3, 3] /= unit_scale
|
|
||||||
x = float(e[0, 3])
|
|
||||||
y = float(e[1, 3])
|
|
||||||
dx = float(e[0, 0])
|
|
||||||
dy = float(e[1, 0])
|
|
||||||
|
|
||||||
parent_curve = file.createIfcLine(
|
parent_curve = file.createIfcLine(
|
||||||
Pnt=file.createIfcCartesianPoint(Coordinates=((0.0, 0.0))),
|
Pnt=file.createIfcCartesianPoint(Coordinates=((0.0, 0.0))),
|
||||||
Dir=file.createIfcVector(
|
Dir=file.createIfcVector(
|
||||||
@@ -103,22 +78,36 @@ def add_zero_length_segment(file: ifcopenshell.file, layout: entity_instance, in
|
|||||||
Magnitude=1.0,
|
Magnitude=1.0,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
if layout.is_a("IfcSegmentedReferenceCurve"):
|
||||||
|
placement = file.createIfcAxis2Placement3D(
|
||||||
|
Location=file.createIfcCartesianPoint((0.0, 0.0, 0.0)),
|
||||||
|
RefDirection=file.createIfcDirection((1.0, 0.0, 0.0)),
|
||||||
|
Axis=file.createIfcDirection((0.0, 0.0, 1.0)),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
placement = file.createIfcAxis2Placement2D(
|
||||||
|
Location=file.createIfcCartesianPoint((0.0, 0.0)),
|
||||||
|
RefDirection=file.createIfcDirection((1.0, 0.0)),
|
||||||
|
)
|
||||||
|
|
||||||
zero_length_curve_segment = file.createIfcCurveSegment(
|
zero_length_curve_segment = file.createIfcCurveSegment(
|
||||||
Transition="DISCONTINUOUS",
|
Transition="DISCONTINUOUS",
|
||||||
Placement=file.createIfcAxis2Placement2D(
|
Placement=placement,
|
||||||
Location=file.createIfcCartesianPoint((x, y)),
|
|
||||||
RefDirection=file.createIfcDirection((dx, dy)),
|
|
||||||
),
|
|
||||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||||
SegmentLength=file.createIfcLengthMeasure(0.0),
|
SegmentLength=file.createIfcLengthMeasure(0.0),
|
||||||
ParentCurve=parent_curve,
|
ParentCurve=parent_curve,
|
||||||
)
|
)
|
||||||
|
|
||||||
layout.Segments += (zero_length_curve_segment,)
|
if layout.Segments and 0 < len(layout.Segments):
|
||||||
|
# If there are segments, get the last segment and compute the end point and tangent direction
|
||||||
if last_segment:
|
# because this becomes of placement of the zero length segment
|
||||||
|
last_segment = layout.Segments[-1]
|
||||||
|
end_point = _get_segment_endpoint(file, last_segment)
|
||||||
|
_update_zero_length_segment_placement(file, zero_length_curve_segment, end_point)
|
||||||
_update_curve_segment_transition_code(last_segment, zero_length_curve_segment)
|
_update_curve_segment_transition_code(last_segment, zero_length_curve_segment)
|
||||||
|
|
||||||
|
layout.Segments += (zero_length_curve_segment,)
|
||||||
|
|
||||||
# add zero length segments to base curves
|
# add zero length segments to base curves
|
||||||
if layout.is_a("IfcSegmentedReferenceCurve"):
|
if layout.is_a("IfcSegmentedReferenceCurve"):
|
||||||
ifcopenshell.api.alignment.add_zero_length_segment(file, layout.BaseCurve)
|
ifcopenshell.api.alignment.add_zero_length_segment(file, layout.BaseCurve)
|
||||||
@@ -139,22 +128,14 @@ def add_zero_length_segment(file: ifcopenshell.file, layout: entity_instance, in
|
|||||||
break
|
break
|
||||||
|
|
||||||
if last_segment:
|
if last_segment:
|
||||||
file.begin_transaction() # use a transaction so we can discard any temporary IFC entities created
|
e = _get_segment_endpoint(file, last_segment)
|
||||||
|
|
||||||
settings = ifcopenshell.geom.settings()
|
|
||||||
mapped_segments = _map_alignment_horizontal_segment(file, last_segment)
|
|
||||||
geometry_segment = mapped_segments[0] if mapped_segments[1] == None else mapped_segments[1]
|
|
||||||
fn = wrapper.map_shape(settings, geometry_segment.wrapped_data)
|
|
||||||
eval = wrapper.function_item_evaluator(settings, fn)
|
|
||||||
e = np.array(eval.evaluate(fn.end()))
|
|
||||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
|
||||||
x = float(e[0, 3]) / unit_scale
|
x = float(e[0, 3]) / unit_scale
|
||||||
y = float(e[1, 3]) / unit_scale
|
y = float(e[1, 3]) / unit_scale
|
||||||
dx = float(e[0, 0])
|
dx = float(e[0, 0])
|
||||||
dy = float(e[1, 0])
|
dy = float(e[1, 0])
|
||||||
|
|
||||||
file.discard_transaction()
|
|
||||||
|
|
||||||
angle_unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file, "PLANEANGLEUNIT")
|
angle_unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file, "PLANEANGLEUNIT")
|
||||||
design_parameters = file.createIfcAlignmentHorizontalSegment(
|
design_parameters = file.createIfcAlignmentHorizontalSegment(
|
||||||
StartPoint=file.createIfcCartesianPoint((x, y)),
|
StartPoint=file.createIfcCartesianPoint((x, y)),
|
||||||
@@ -178,22 +159,14 @@ def add_zero_length_segment(file: ifcopenshell.file, layout: entity_instance, in
|
|||||||
break
|
break
|
||||||
|
|
||||||
if last_segment:
|
if last_segment:
|
||||||
file.begin_transaction()
|
|
||||||
last_segment_dist_along = (
|
last_segment_dist_along = (
|
||||||
last_segment.DesignParameters.StartDistAlong + last_segment.DesignParameters.HorizontalLength
|
last_segment.DesignParameters.StartDistAlong + last_segment.DesignParameters.HorizontalLength
|
||||||
)
|
)
|
||||||
last_segment_end_gradient = last_segment.DesignParameters.EndGradient
|
last_segment_end_gradient = last_segment.DesignParameters.EndGradient
|
||||||
settings = ifcopenshell.geom.settings()
|
e = _get_segment_endpoint(file, last_segment)
|
||||||
mapped_segments = _map_alignment_vertical_segment(file, last_segment)
|
|
||||||
geometry_segment = mapped_segments[0] if mapped_segments[1] == None else mapped_segments[1]
|
|
||||||
fn = wrapper.map_shape(settings, geometry_segment.wrapped_data)
|
|
||||||
eval = wrapper.function_item_evaluator(settings, fn)
|
|
||||||
e = np.array(eval.evaluate(fn.end()))
|
|
||||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
|
||||||
last_segment_height = float(e[1, 3]) / unit_scale
|
last_segment_height = float(e[1, 3]) / unit_scale
|
||||||
|
|
||||||
file.discard_transaction()
|
|
||||||
|
|
||||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||||
StartDistAlong=last_segment_dist_along,
|
StartDistAlong=last_segment_dist_along,
|
||||||
HorizontalLength=0.0,
|
HorizontalLength=0.0,
|
||||||
@@ -240,13 +213,4 @@ def add_zero_length_segment(file: ifcopenshell.file, layout: entity_instance, in
|
|||||||
|
|
||||||
ifcopenshell.api.nest.assign_object(file, related_objects=[zero_length_curve_segment], relating_object=layout)
|
ifcopenshell.api.nest.assign_object(file, related_objects=[zero_length_curve_segment], relating_object=layout)
|
||||||
|
|
||||||
if include_referent:
|
|
||||||
alignment = ifcopenshell.api.alignment.get_alignment(layout)
|
|
||||||
station = ifcopenshell.api.alignment.get_alignment_start_station(file, alignment)
|
|
||||||
name = f"{_get_segment_start_point_label(zero_length_curve_segment,None)} ({ifcopenshell.util.alignment.station_as_string(file,station)})"
|
|
||||||
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
|
||||||
file, alignment, 0.0, station, name, zero_length_curve_segment
|
|
||||||
)
|
|
||||||
referent.Description = f"Positions zero length segment {zero_length_curve_segment.id()}"
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -63,6 +63,12 @@ def create(
|
|||||||
alignment = file.createIfcAlignment(
|
alignment = file.createIfcAlignment(
|
||||||
GlobalId=ifcopenshell.guid.new(),
|
GlobalId=ifcopenshell.guid.new(),
|
||||||
Name=name,
|
Name=name,
|
||||||
|
ObjectPlacement=file.createIfcLocalPlacement(
|
||||||
|
PlacementRelTo=None,
|
||||||
|
RelativePlacement=file.createIfcAxis2Placement2D(
|
||||||
|
Location=file.createIfcCartesianPoint(Coordinates=(0.0, 0.0))
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
alignment_layouts = []
|
alignment_layouts = []
|
||||||
@@ -80,10 +86,10 @@ def create(
|
|||||||
if include_geometry:
|
if include_geometry:
|
||||||
_create_geometric_representation(file, alignment)
|
_create_geometric_representation(file, alignment)
|
||||||
|
|
||||||
name = ifcopenshell.util.alignment.station_as_string(file, start_station)
|
referent_name = ifcopenshell.util.alignment.station_as_string(file, start_station)
|
||||||
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
file, alignment, 0.0, start_station, name, alignment
|
file, alignment, 0.0, start_station, referent_name, alignment
|
||||||
)
|
)
|
||||||
|
|
||||||
for layout in alignment_layouts:
|
for layout in alignment_layouts:
|
||||||
_add_zero_length_segment(file, layout)
|
_add_zero_length_segment(file, layout)
|
||||||
|
|||||||
@@ -53,35 +53,8 @@ def create_layout_segment(
|
|||||||
|
|
||||||
# create the segment and add it to the layout.
|
# create the segment and add it to the layout.
|
||||||
segment = file.createIfcAlignmentSegment(GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters)
|
segment = file.createIfcAlignmentSegment(GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters)
|
||||||
_add_segment_to_layout(file, layout, segment) # adds to layout and geometric representation
|
end = _add_segment_to_layout(
|
||||||
|
file, layout, segment
|
||||||
|
) # adds to layout and geometric representation (if present, also updates zero length segment position)
|
||||||
|
|
||||||
# compute the 4x4 matrix at the end of the segment so this information can be
|
return end
|
||||||
# returned and used when defining the next segment
|
|
||||||
alignment = ifcopenshell.api.alignment.get_alignment(layout)
|
|
||||||
curve = ifcopenshell.api.alignment.get_curve(alignment)
|
|
||||||
|
|
||||||
if curve:
|
|
||||||
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
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from ifcopenshell.api.alignment._add_segment_to_curve import _add_segment_to_cur
|
|||||||
from ifcopenshell.api.alignment._create_geometric_representation import (
|
from ifcopenshell.api.alignment._create_geometric_representation import (
|
||||||
_create_geometric_representation,
|
_create_geometric_representation,
|
||||||
)
|
)
|
||||||
|
from ifcopenshell.api.alignment.update_fallback_position import update_fallback_position
|
||||||
|
|
||||||
|
|
||||||
def create_representation(
|
def create_representation(
|
||||||
@@ -34,8 +35,13 @@ def create_representation(
|
|||||||
This function is intended to be used when a model has only the semantic definition of an alignment
|
This function is intended to be used when a model has only the semantic definition of an alignment
|
||||||
and you want to add the geometric representation.
|
and you want to add the geometric representation.
|
||||||
|
|
||||||
If the alignments are complete, it is recommended that add_zero_length_segment is called after this method to ensure
|
If the alignments are complete, it is recommended that add_zero_length_segment is called before this method to ensure
|
||||||
the proper structure of the semantic and geometric definitions of the alignment
|
the proper structure of the semantic and geometric definitions of the alignment.
|
||||||
|
|
||||||
|
It is presumed that the alignment does not have any geometric representation. However, if the alignment has stationing defined,
|
||||||
|
the referent defining the stationing is not related to the alignment geometry (it can't be because the geometry doesn't exist yet).
|
||||||
|
When the geometric representation is created, the referent is updated to have an IfcLinearPlacement that references the basis curve geometry.
|
||||||
|
This function assumes the referent defines the stationing at the start of the alignment, and therefore sets the IfcLinearPlacement.RelativePlacement.Location.DistanceAlong to 0.0.
|
||||||
|
|
||||||
:param alignment: The alignment to create the representation.
|
:param alignment: The alignment to create the representation.
|
||||||
"""
|
"""
|
||||||
@@ -51,6 +57,40 @@ def create_representation(
|
|||||||
layouts = ifcopenshell.api.alignment.get_alignment_layouts(alignment)
|
layouts = ifcopenshell.api.alignment.get_alignment_layouts(alignment)
|
||||||
for layout in layouts:
|
for layout in layouts:
|
||||||
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
|
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
|
||||||
|
|
||||||
layout_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(layout)
|
layout_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(layout)
|
||||||
for segment in layout_nest.RelatedObjects:
|
for segment in layout_nest.RelatedObjects:
|
||||||
_add_segment_to_curve(file, segment, curve)
|
_add_segment_to_curve(file, segment, curve)
|
||||||
|
|
||||||
|
# if the alignment is created without geometry it's stationing referent isn't related to the alignment geometry.
|
||||||
|
# the stationing referent needs to be updated to have an IfcLinearPlacement that references the basis curve geometry
|
||||||
|
referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
||||||
|
if (
|
||||||
|
referent_nest
|
||||||
|
and 0 < len(referent_nest.RelatedObjects)
|
||||||
|
and referent_nest.RelatedObjects[0].ObjectPlacement
|
||||||
|
and not referent_nest.RelatedObjects[0].ObjectPlacement.is_a("IfcLinearPlacement")
|
||||||
|
):
|
||||||
|
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||||
|
|
||||||
|
if referent_nest.RelatedObjects[0].ObjectPlacement:
|
||||||
|
if referent_nest.RelatedObjects[0].ObjectPlacement.RelativePlacement.Location:
|
||||||
|
file.remove(referent_nest.RelatedObjects[0].ObjectPlacement.RelativePlacement.Location)
|
||||||
|
if referent_nest.RelatedObjects[0].ObjectPlacement.RelativePlacement.RefDirection:
|
||||||
|
file.remove(referent_nest.RelatedObjects[0].ObjectPlacement.RelativePlacement.RefDirection)
|
||||||
|
file.remove(referent_nest.RelatedObjects[0].ObjectPlacement.RelativePlacement)
|
||||||
|
file.remove(referent_nest.RelatedObjects[0].ObjectPlacement)
|
||||||
|
|
||||||
|
lp = file.createIfcLinearPlacement(
|
||||||
|
RelativePlacement=file.createIfcAxis2PlacementLinear(
|
||||||
|
Location=file.createIfcPointByDistanceExpression(
|
||||||
|
DistanceAlong=file.createIfcLengthMeasure(0.0),
|
||||||
|
OffsetLateral=None,
|
||||||
|
OffsetVertical=None,
|
||||||
|
OffsetLongitudinal=None,
|
||||||
|
BasisCurve=basis_curve,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
update_fallback_position(file, lp)
|
||||||
|
referent_nest.RelatedObjects[0].ObjectPlacement = lp
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# 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 collections.abc import Sequence
|
||||||
|
|
||||||
|
from ifcopenshell import entity_instance
|
||||||
|
|
||||||
|
import ifcopenshell.api.alignment
|
||||||
|
|
||||||
|
from ifcopenshell.api.alignment.get_mapped_segments import _get_curve_segment_count
|
||||||
|
|
||||||
|
|
||||||
|
def get_curve_segment(layout: entity_instance, segment: entity_instance) -> entity_instance:
|
||||||
|
"""
|
||||||
|
Returns the IfcCurveSegment associated with the given alignment segment. If the curve segment does not exist, None is returned.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
horizontal = model.by_type("IfcAlignmentHorizontal")[0]
|
||||||
|
curve_segment = ifcopenshell.api.alignment.get_curve_segment(horizontal, alignment_segment)
|
||||||
|
"""
|
||||||
|
index = 0
|
||||||
|
segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(layout)
|
||||||
|
for related_object in segment_nest.RelatedObjects:
|
||||||
|
if related_object == segment:
|
||||||
|
break
|
||||||
|
n = _get_curve_segment_count(related_object)
|
||||||
|
index += n
|
||||||
|
|
||||||
|
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
|
||||||
|
if curve and index < len(curve.Segments):
|
||||||
|
return curve.Segments[index]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# 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_layout(segment: entity_instance) -> entity_instance:
|
||||||
|
"""
|
||||||
|
Retrieves the layout to which an alignment segment belongs.
|
||||||
|
"""
|
||||||
|
if not segment.is_a("IfcAlignmentSegment"):
|
||||||
|
raise TypeError(f"Expected entity type to be IfcAlignmentSegment, instead received {segment.is_a()}")
|
||||||
|
|
||||||
|
layout = None
|
||||||
|
nests = segment.Nests
|
||||||
|
if nests:
|
||||||
|
layout = nests[0].RelatingObject
|
||||||
|
|
||||||
|
return layout
|
||||||
@@ -22,11 +22,11 @@ from ifcopenshell import entity_instance
|
|||||||
|
|
||||||
def get_referent_nest(file: ifcopenshell.file, alignment: entity_instance) -> entity_instance:
|
def get_referent_nest(file: ifcopenshell.file, alignment: entity_instance) -> entity_instance:
|
||||||
"""
|
"""
|
||||||
Searches for the IfcRelNest that contains IfcReferent. If one is not found, a empty IfcRelNests is created.
|
Searches for the IfcRelNest that contains IfcReferent.
|
||||||
|
|
||||||
:param file:
|
:param file:
|
||||||
:param alignment: The IfcAlignment which hosts IfcReferent
|
:param alignment: The IfcAlignment which hosts IfcReferent
|
||||||
:return: Returns the IfcRelNests.
|
:return: Returns the IfcRelNests or None
|
||||||
"""
|
"""
|
||||||
if not alignment.is_a("IfcAlignment"):
|
if not alignment.is_a("IfcAlignment"):
|
||||||
raise TypeError(f"Expected IfcAlignment, instead received {alignment.is_a()}")
|
raise TypeError(f"Expected IfcAlignment, instead received {alignment.is_a()}")
|
||||||
@@ -36,5 +36,4 @@ def get_referent_nest(file: ifcopenshell.file, alignment: entity_instance) -> en
|
|||||||
if related_object.is_a("IfcReferent"):
|
if related_object.is_a("IfcReferent"):
|
||||||
return nest
|
return nest
|
||||||
|
|
||||||
nest = file.createIfcRelNests(GlobalId=ifcopenshell.guid.new(), RelatingObject=alignment, RelatedObjects=[])
|
return None
|
||||||
return nest
|
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# 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 numpy as np
|
||||||
|
|
||||||
|
import ifcopenshell
|
||||||
|
import ifcopenshell.util.placement
|
||||||
|
from ifcopenshell import entity_instance
|
||||||
|
|
||||||
|
|
||||||
|
def update_end_point(file: ifcopenshell.file, curve: entity_instance):
|
||||||
|
"""
|
||||||
|
Updates the IfcGradientCurve.EndPoint and IfcSegmentedReferenceCurve.EndPoint.
|
||||||
|
|
||||||
|
If the curve does not have a zero length segment, one is added. The EndPoint is then updated to match the placement of the zero length segment.
|
||||||
|
|
||||||
|
:param curve: The gradient curve or segmented reference curve
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
expected_types = ["IfcGradientCurve", "IfcSegmentedReferenceCurve"]
|
||||||
|
if not curve.is_a() in expected_types:
|
||||||
|
raise TypeError(
|
||||||
|
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{curve.is_a()}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not ifcopenshell.api.alignment.has_zero_length_segment(curve):
|
||||||
|
ifcopenshell.api.alignment.add_zero_length_segment(file, curve)
|
||||||
|
|
||||||
|
zero_length_segment = curve.Segments[-1]
|
||||||
|
|
||||||
|
if not curve.EndPoint:
|
||||||
|
if curve.is_a("IfcGradientCurve"):
|
||||||
|
curve.EndPoint = file.createIfcAxis2Placement2D(
|
||||||
|
Location=file.createIfcCartesianPoint((0.0, 0.0)),
|
||||||
|
RefDirection=file.createIfcDirection((1.0, 0.0)),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
curve.EndPoint = file.createIfcAxis2Placement3D(
|
||||||
|
Location=file.createIfcCartesianPoint((0.0, 0.0, 0.0)),
|
||||||
|
RefDirection=file.createIfcDirection((1.0, 0.0, 0.0)),
|
||||||
|
Axis=file.createIfcDirection((0.0, 0.0, 1.0)),
|
||||||
|
)
|
||||||
|
|
||||||
|
p = np.array(ifcopenshell.util.placement.get_axis2placement(zero_length_segment.Placement))
|
||||||
|
|
||||||
|
x = float(p[0, 3])
|
||||||
|
y = float(p[1, 3])
|
||||||
|
z = float(p[2, 3])
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
|
if curve.is_a("IfcGradientCurve"):
|
||||||
|
curve.EndPoint.Location.Coordinates = (x, y)
|
||||||
|
|
||||||
|
if not curve.EndPoint.RefDirection:
|
||||||
|
curve.EndPoint.RefDirection = file.createIfcDirection((1.0, 0.0))
|
||||||
|
|
||||||
|
curve.EndPoint.RefDirection.DirectionRatios = (rx, ry)
|
||||||
|
else:
|
||||||
|
curve.EndPoint.Location.Coordinates = (x, y, z)
|
||||||
|
|
||||||
|
if not curve.EndPoint.RefDirection:
|
||||||
|
curve.EndPoint.RefDirection = file.createIfcDirection((1.0, 0.0, 0.0))
|
||||||
|
|
||||||
|
if not curve.EndPoint.Axis:
|
||||||
|
curve.EndPoint.Axis = file.createIfcDirection((0.0, 0.0, 1.0))
|
||||||
|
|
||||||
|
curve.EndPoint.RefDirection.DirectionRatios = (rx, ry, rz)
|
||||||
|
curve.EndPoint.Axis.DirectionRatios = (ax, ay, az)
|
||||||
@@ -34,7 +34,7 @@ def update_fallback_position(file: ifcopenshell.file, lp: entity_instance):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if not lp.CartesianPosition:
|
if not lp.CartesianPosition:
|
||||||
lp.CartesianPosition = file.createIfcAxis2Placement3D(Location=file.createIfcCartesianPoint((0.0, 0.0)))
|
lp.CartesianPosition = file.createIfcAxis2Placement3D(Location=file.createIfcCartesianPoint((0.0, 0.0, 0.0)))
|
||||||
|
|
||||||
p = np.array(ifcopenshell.util.placement.get_axis2placement(lp.RelativePlacement))
|
p = np.array(ifcopenshell.util.placement.get_axis2placement(lp.RelativePlacement))
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ def evaluate_segment(segment: entity_instance, dist_along: float) -> np.ndarray:
|
|||||||
segment_type = segment.is_a().upper()
|
segment_type = segment.is_a().upper()
|
||||||
if not segment_type in supported_segment_types:
|
if not segment_type in supported_segment_types:
|
||||||
raise NotImplementedError(f"Expected entity type 'IFCCURVESEGMENT', got '{segment_type}")
|
raise NotImplementedError(f"Expected entity type 'IFCCURVESEGMENT', got '{segment_type}")
|
||||||
if dist_along > segment.SegmentLength:
|
if dist_along > abs(segment.SegmentLength.wrappedValue):
|
||||||
raise ValueError(f"Provided value {dist_along=} is beyond the end of the segment ({segment.SegmentLength}).")
|
raise ValueError(f"Provided value {dist_along=} is beyond the end of the segment ({segment.SegmentLength}).")
|
||||||
|
|
||||||
s = ifcopenshell.geom.settings()
|
s = ifcopenshell.geom.settings()
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ def test_add_segment_to_layout():
|
|||||||
)
|
)
|
||||||
|
|
||||||
alignment = ifcopenshell.api.alignment.create(file, "")
|
alignment = ifcopenshell.api.alignment.create(file, "")
|
||||||
|
|
||||||
|
referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
||||||
|
assert (
|
||||||
|
len(referent_nest.RelatedObjects) == 1
|
||||||
|
) # the alignment creates the stationing nest and it has one referent to defined the stationing for the alignment
|
||||||
|
|
||||||
horizontal_alignment = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
horizontal_alignment = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
||||||
|
|
||||||
design_parameters = file.create_entity(
|
design_parameters = file.create_entity(
|
||||||
@@ -70,7 +76,7 @@ def test_add_segment_to_layout():
|
|||||||
segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(horizontal_alignment)
|
segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(horizontal_alignment)
|
||||||
assert len(segment_nest.RelatedObjects) == 2
|
assert len(segment_nest.RelatedObjects) == 2
|
||||||
referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
||||||
assert len(referent_nest.RelatedObjects) == 3
|
assert len(referent_nest.RelatedObjects) == 1 # test this a second time to make sure that it is still true
|
||||||
|
|
||||||
|
|
||||||
test_add_segment_to_layout()
|
test_add_segment_to_layout()
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ def test_add_vertical_alignment():
|
|||||||
assert len(layout_nest.RelatedObjects) == 1
|
assert len(layout_nest.RelatedObjects) == 1
|
||||||
assert layout_nest.RelatedObjects[0].is_a("IfcAlignmentHorizontal")
|
assert layout_nest.RelatedObjects[0].is_a("IfcAlignmentHorizontal")
|
||||||
referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
||||||
assert len(referent_nest.RelatedObjects) == 2
|
assert (
|
||||||
|
len(referent_nest.RelatedObjects) == 1
|
||||||
|
) # the alignment creates the stationing nest and it has one referent to defined the stationing for the alignment
|
||||||
assert referent_nest.RelatedObjects[0].is_a("IfcReferent")
|
assert referent_nest.RelatedObjects[0].is_a("IfcReferent")
|
||||||
|
|
||||||
curve = ifcopenshell.api.alignment.get_curve(alignment)
|
curve = ifcopenshell.api.alignment.get_curve(alignment)
|
||||||
@@ -62,7 +64,7 @@ def test_add_vertical_alignment():
|
|||||||
|
|
||||||
for child_alignment in alignment.IsDecomposedBy[0].RelatedObjects:
|
for child_alignment in alignment.IsDecomposedBy[0].RelatedObjects:
|
||||||
assert child_alignment.is_a("IfcAlignment")
|
assert child_alignment.is_a("IfcAlignment")
|
||||||
assert len(child_alignment.IsNestedBy) == 2
|
assert len(child_alignment.IsNestedBy) == 1
|
||||||
child_layout_nest = ifcopenshell.api.alignment.get_alignment_layout_nest(child_alignment)
|
child_layout_nest = ifcopenshell.api.alignment.get_alignment_layout_nest(child_alignment)
|
||||||
assert len(child_layout_nest.RelatedObjects) == 1 # The IfcAlignmentVertical
|
assert len(child_layout_nest.RelatedObjects) == 1 # The IfcAlignmentVertical
|
||||||
assert child_layout_nest.RelatedObjects[0].is_a("IfcAlignmentVertical")
|
assert child_layout_nest.RelatedObjects[0].is_a("IfcAlignmentVertical")
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ def test_create_by_pi_method():
|
|||||||
assert len(layout_nest.RelatedObjects) == 2
|
assert len(layout_nest.RelatedObjects) == 2
|
||||||
|
|
||||||
referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
||||||
assert len(referent_nest.RelatedObjects) == 19
|
assert len(referent_nest.RelatedObjects) == 1
|
||||||
|
|
||||||
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
||||||
horizontal_segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(horizontal_layout)
|
horizontal_segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(horizontal_layout)
|
||||||
|
|||||||
@@ -73,9 +73,16 @@ def _test_horizontal() -> ifcopenshell.file:
|
|||||||
assert y == 0.0
|
assert y == 0.0
|
||||||
assert z == 0.0
|
assert z == 0.0
|
||||||
|
|
||||||
|
# check the start point of the zero length segment
|
||||||
|
assert horizontal_alignment.IsNestedBy[0].RelatedObjects[1].DesignParameters.SegmentLength == 0.0
|
||||||
|
assert horizontal_alignment.IsNestedBy[0].RelatedObjects[1].DesignParameters.StartPoint.Coordinates[0] == x
|
||||||
|
assert horizontal_alignment.IsNestedBy[0].RelatedObjects[1].DesignParameters.StartPoint.Coordinates[1] == y
|
||||||
|
|
||||||
curve = ifcopenshell.api.alignment.get_curve(ali)
|
curve = ifcopenshell.api.alignment.get_curve(ali)
|
||||||
assert curve.is_a("IfcCompositeCurve")
|
assert curve.is_a("IfcCompositeCurve")
|
||||||
assert len(curve.Segments) == 2
|
assert len(curve.Segments) == 2
|
||||||
|
assert curve.Segments[0].Transition == "CONTSAMEGRADIENTSAMECURVATURE"
|
||||||
|
assert curve.Segments[1].Transition == "DISCONTINUOUS"
|
||||||
|
|
||||||
design_parameters = file.create_entity(
|
design_parameters = file.create_entity(
|
||||||
type="IfcAlignmentHorizontalSegment",
|
type="IfcAlignmentHorizontalSegment",
|
||||||
@@ -101,9 +108,16 @@ def _test_horizontal() -> ifcopenshell.file:
|
|||||||
assert y == 50.0 * math.sin(math.pi / 6)
|
assert y == 50.0 * math.sin(math.pi / 6)
|
||||||
assert z == 0.0
|
assert z == 0.0
|
||||||
|
|
||||||
|
# check the start point of the zero length segment
|
||||||
|
assert horizontal_alignment.IsNestedBy[0].RelatedObjects[2].DesignParameters.SegmentLength == 0.0
|
||||||
|
assert horizontal_alignment.IsNestedBy[0].RelatedObjects[2].DesignParameters.StartPoint.Coordinates[0] == x
|
||||||
|
assert horizontal_alignment.IsNestedBy[0].RelatedObjects[2].DesignParameters.StartPoint.Coordinates[1] == y
|
||||||
|
|
||||||
curve = ifcopenshell.api.alignment.get_curve(ali)
|
curve = ifcopenshell.api.alignment.get_curve(ali)
|
||||||
assert curve.is_a("IfcCompositeCurve")
|
assert curve.is_a("IfcCompositeCurve")
|
||||||
assert len(curve.Segments) == 3
|
assert len(curve.Segments) == 3
|
||||||
|
assert curve.Segments[1].Transition == "CONTSAMEGRADIENTSAMECURVATURE"
|
||||||
|
assert curve.Segments[2].Transition == "DISCONTINUOUS"
|
||||||
|
|
||||||
return file
|
return file
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,14 @@ def test_create_no_geometry():
|
|||||||
PredefinedType="LINE",
|
PredefinedType="LINE",
|
||||||
)
|
)
|
||||||
end = ifcopenshell.api.alignment.create_layout_segment(file, horizontal_alignment, design_parameters)
|
end = ifcopenshell.api.alignment.create_layout_segment(file, horizontal_alignment, design_parameters)
|
||||||
assert end == None
|
|
||||||
|
x = end[0, 3]
|
||||||
|
y = end[1, 3]
|
||||||
|
z = end[2, 3]
|
||||||
|
|
||||||
|
assert x == 100.0
|
||||||
|
assert y == 0.0
|
||||||
|
assert z == 0.0
|
||||||
|
|
||||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||||
StartDistAlong=0.0,
|
StartDistAlong=0.0,
|
||||||
@@ -61,7 +68,14 @@ def test_create_no_geometry():
|
|||||||
PredefinedType="CONSTANTGRADIENT",
|
PredefinedType="CONSTANTGRADIENT",
|
||||||
)
|
)
|
||||||
end = ifcopenshell.api.alignment.create_layout_segment(file, vertical_alignment, design_parameters)
|
end = ifcopenshell.api.alignment.create_layout_segment(file, vertical_alignment, design_parameters)
|
||||||
assert end == None
|
|
||||||
|
x = end[0, 3]
|
||||||
|
y = end[1, 3]
|
||||||
|
z = end[2, 3]
|
||||||
|
|
||||||
|
assert x == 50.0
|
||||||
|
assert y == 20.0 + 50.0 * 1.0 / 100.0
|
||||||
|
assert z == 0.0
|
||||||
|
|
||||||
|
|
||||||
test_create_no_geometry()
|
test_create_no_geometry()
|
||||||
|
|||||||
@@ -0,0 +1,443 @@
|
|||||||
|
# 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 math
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import ifcopenshell
|
||||||
|
import ifcopenshell.api.alignment
|
||||||
|
import ifcopenshell.api.unit
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_representation():
|
||||||
|
# expected values for horizontal segment ends points (X,Y,dx,dy)
|
||||||
|
h_expected = [
|
||||||
|
(500.0, 2500.0, math.cos(math.radians(327.0613)), math.sin(math.radians(327.0613))),
|
||||||
|
(2142.2378194934668, 1436.0145490066361, 0.8392527899703555, -0.5437414408769801),
|
||||||
|
(3660.446048592728, 2050.735651565721, 0.22453168741127044, 0.9744667882222808),
|
||||||
|
(4084.1161141648777, 3889.4623490042068, 0.22453168741127047, 0.9744667882222809),
|
||||||
|
(5469.395455576321, 4847.565492667097, 0.9910142023415828, -0.13375668490687387),
|
||||||
|
(7019.971720182908, 4638.284999653966, 0.9910142023415827, -0.13375668490687387),
|
||||||
|
(7790.932377201981, 4006.729563689594, 0.32621900658961334, -0.9452942186111613),
|
||||||
|
(8479.999918938518, 2009.9986857258034, 0.32621900658961345, -0.9452942186111613),
|
||||||
|
]
|
||||||
|
|
||||||
|
# expected values for vertical segment ends points (X,Y,dx,dy)
|
||||||
|
v_expected = [
|
||||||
|
(0.0, 100.0, 0.999846910161925, 0.01749732092783369),
|
||||||
|
(1200.0, 121.0, 0.999846910161925, 0.01749732092783369),
|
||||||
|
(2799.99999384661, 127.00000006153391, 0.9999500037507449, -0.009999499931751348),
|
||||||
|
(4399.99999384661, 111.00000023075212, 0.999950003750745, -0.009999499931751352),
|
||||||
|
(5599.9999883553455, 117.00000018438367, 0.999800059982751, 0.019996001062400855),
|
||||||
|
(6399.999988355345, 133.0000000745584, 0.999800059982751, 0.019996001062400855),
|
||||||
|
(8399.99998428796, 133.00000001862446, 0.999800059981633, -0.019996001118301257),
|
||||||
|
(9399.99998428796, 113.00000009997211, 0.999800059981633, -0.019996001118301257),
|
||||||
|
(10199.99998062693, 103.00000015081635, 0.9999875002340269, -0.004999937569813611),
|
||||||
|
(12799.99998062693, 89.99999997234107, 0.9999875002340269, -0.004999937569813611),
|
||||||
|
]
|
||||||
|
|
||||||
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
|
file.header.file_description.description = ["ViewDefinition [Alignment-basedView]"]
|
||||||
|
|
||||||
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="FHWA Alignment")
|
||||||
|
# ifcopenshell.api.unit.assign_unit(file)
|
||||||
|
# length = ifcopenshell.api.unit.add_si_unit(file,unit_type="LENGTHUNIT")
|
||||||
|
length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
|
||||||
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
|
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
||||||
|
axis_model_representation_subcontext = ifcopenshell.api.context.add_context(
|
||||||
|
file,
|
||||||
|
context_type="Model",
|
||||||
|
context_identifier="Axis",
|
||||||
|
target_view="MODEL_VIEW",
|
||||||
|
parent=geometric_representation_context,
|
||||||
|
)
|
||||||
|
|
||||||
|
site = file.createIfcSite(GlobalId=ifcopenshell.guid.new(), Name="Site")
|
||||||
|
ifcopenshell.api.aggregate.assign_object(file, relating_object=project, products=[site])
|
||||||
|
|
||||||
|
alignment = ifcopenshell.api.alignment.create(
|
||||||
|
file, "E-Line", include_vertical=True, start_station=10000.0, include_geometry=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# alignment is referenced into spatial structure of site per CT 4.1.5.1
|
||||||
|
ifcopenshell.api.spatial.reference_structure(file, products=[alignment], relating_structure=site)
|
||||||
|
|
||||||
|
layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
||||||
|
|
||||||
|
segment1 = file.createIfcAlignmentHorizontalSegment(
|
||||||
|
StartPoint=file.createIfcCartesianPoint(Coordinates=((500.0, 2500.0))),
|
||||||
|
StartDirection=math.radians(327.0613),
|
||||||
|
StartRadiusOfCurvature=0.0,
|
||||||
|
EndRadiusOfCurvature=0.0,
|
||||||
|
SegmentLength=1956.785654,
|
||||||
|
PredefinedType="LINE",
|
||||||
|
)
|
||||||
|
|
||||||
|
end = ifcopenshell.api.alignment.create_layout_segment(file, layout, segment1)
|
||||||
|
|
||||||
|
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])
|
||||||
|
dir = math.atan2(dy, dx)
|
||||||
|
assert (
|
||||||
|
pytest.approx(h_expected[1][0]) == x
|
||||||
|
and pytest.approx(h_expected[1][1]) == y
|
||||||
|
and pytest.approx(h_expected[1][2]) == dx
|
||||||
|
and pytest.approx(h_expected[1][3]) == dy
|
||||||
|
)
|
||||||
|
segment2 = file.createIfcAlignmentHorizontalSegment(
|
||||||
|
StartPoint=file.createIfcCartesianPoint((x, y)),
|
||||||
|
StartDirection=dir,
|
||||||
|
StartRadiusOfCurvature=1000.0,
|
||||||
|
EndRadiusOfCurvature=1000.0,
|
||||||
|
SegmentLength=1919.222667,
|
||||||
|
PredefinedType="CIRCULARARC",
|
||||||
|
)
|
||||||
|
end = ifcopenshell.api.alignment.create_layout_segment(file, layout, segment2)
|
||||||
|
|
||||||
|
x = float(end[0, 3]) / unit_scale
|
||||||
|
y = float(end[1, 3]) / unit_scale
|
||||||
|
dx = float(end[0, 0])
|
||||||
|
dy = float(end[1, 0])
|
||||||
|
dir = math.atan2(dy, dx)
|
||||||
|
assert (
|
||||||
|
pytest.approx(h_expected[2][0]) == x
|
||||||
|
and pytest.approx(h_expected[2][1]) == y
|
||||||
|
and pytest.approx(h_expected[2][2]) == dx
|
||||||
|
and pytest.approx(h_expected[2][3]) == dy
|
||||||
|
)
|
||||||
|
segment3 = file.createIfcAlignmentHorizontalSegment(
|
||||||
|
StartPoint=file.createIfcCartesianPoint((x, y)),
|
||||||
|
StartDirection=dir,
|
||||||
|
StartRadiusOfCurvature=0.0,
|
||||||
|
EndRadiusOfCurvature=0.0,
|
||||||
|
SegmentLength=1886.905454,
|
||||||
|
PredefinedType="LINE",
|
||||||
|
)
|
||||||
|
end = ifcopenshell.api.alignment.create_layout_segment(file, layout, segment3)
|
||||||
|
|
||||||
|
x = float(end[0, 3]) / unit_scale
|
||||||
|
y = float(end[1, 3]) / unit_scale
|
||||||
|
dx = float(end[0, 0])
|
||||||
|
dy = float(end[1, 0])
|
||||||
|
dir = math.atan2(dy, dx)
|
||||||
|
assert (
|
||||||
|
pytest.approx(h_expected[3][0]) == x
|
||||||
|
and pytest.approx(h_expected[3][1]) == y
|
||||||
|
and pytest.approx(h_expected[3][2]) == dx
|
||||||
|
and pytest.approx(h_expected[3][3]) == dy
|
||||||
|
)
|
||||||
|
segment4 = file.createIfcAlignmentHorizontalSegment(
|
||||||
|
StartPoint=file.createIfcCartesianPoint((x, y)),
|
||||||
|
StartDirection=dir,
|
||||||
|
StartRadiusOfCurvature=-1250.0,
|
||||||
|
EndRadiusOfCurvature=-1250.0,
|
||||||
|
SegmentLength=1848.115835,
|
||||||
|
PredefinedType="CIRCULARARC",
|
||||||
|
)
|
||||||
|
end = ifcopenshell.api.alignment.create_layout_segment(file, layout, segment4)
|
||||||
|
|
||||||
|
x = float(end[0, 3]) / unit_scale
|
||||||
|
y = float(end[1, 3]) / unit_scale
|
||||||
|
dx = float(end[0, 0])
|
||||||
|
dy = float(end[1, 0])
|
||||||
|
dir = math.atan2(dy, dx)
|
||||||
|
assert (
|
||||||
|
pytest.approx(h_expected[4][0]) == x
|
||||||
|
and pytest.approx(h_expected[4][1]) == y
|
||||||
|
and pytest.approx(h_expected[4][2]) == dx
|
||||||
|
and pytest.approx(h_expected[4][3]) == dy
|
||||||
|
)
|
||||||
|
segment5 = file.createIfcAlignmentHorizontalSegment(
|
||||||
|
StartPoint=file.createIfcCartesianPoint((x, y)),
|
||||||
|
StartDirection=dir,
|
||||||
|
StartRadiusOfCurvature=0.0,
|
||||||
|
EndRadiusOfCurvature=0.0,
|
||||||
|
SegmentLength=1564.635765,
|
||||||
|
PredefinedType="LINE",
|
||||||
|
)
|
||||||
|
end = ifcopenshell.api.alignment.create_layout_segment(file, layout, segment5)
|
||||||
|
|
||||||
|
x = float(end[0, 3]) / unit_scale
|
||||||
|
y = float(end[1, 3]) / unit_scale
|
||||||
|
dx = float(end[0, 0])
|
||||||
|
dy = float(end[1, 0])
|
||||||
|
dir = math.atan2(dy, dx)
|
||||||
|
assert (
|
||||||
|
pytest.approx(h_expected[5][0]) == x
|
||||||
|
and pytest.approx(h_expected[5][1]) == y
|
||||||
|
and pytest.approx(h_expected[5][2]) == dx
|
||||||
|
and pytest.approx(h_expected[5][3]) == dy
|
||||||
|
)
|
||||||
|
segment6 = file.createIfcAlignmentHorizontalSegment(
|
||||||
|
StartPoint=file.createIfcCartesianPoint((x, y)),
|
||||||
|
StartDirection=dir,
|
||||||
|
StartRadiusOfCurvature=-950.0,
|
||||||
|
EndRadiusOfCurvature=-950.0,
|
||||||
|
SegmentLength=1049.119737,
|
||||||
|
PredefinedType="CIRCULARARC",
|
||||||
|
)
|
||||||
|
end = ifcopenshell.api.alignment.create_layout_segment(file, layout, segment6)
|
||||||
|
|
||||||
|
x = float(end[0, 3]) / unit_scale
|
||||||
|
y = float(end[1, 3]) / unit_scale
|
||||||
|
dx = float(end[0, 0])
|
||||||
|
dy = float(end[1, 0])
|
||||||
|
dir = math.atan2(dy, dx)
|
||||||
|
assert (
|
||||||
|
pytest.approx(h_expected[6][0]) == x
|
||||||
|
and pytest.approx(h_expected[6][1]) == y
|
||||||
|
and pytest.approx(h_expected[6][2]) == dx
|
||||||
|
and pytest.approx(h_expected[6][3]) == dy
|
||||||
|
)
|
||||||
|
segment7 = file.createIfcAlignmentHorizontalSegment(
|
||||||
|
StartPoint=file.createIfcCartesianPoint((x, y)),
|
||||||
|
StartDirection=dir,
|
||||||
|
StartRadiusOfCurvature=0.0,
|
||||||
|
EndRadiusOfCurvature=0.0,
|
||||||
|
SegmentLength=2112.285084,
|
||||||
|
PredefinedType="LINE",
|
||||||
|
)
|
||||||
|
end = ifcopenshell.api.alignment.create_layout_segment(file, layout, segment7)
|
||||||
|
x = float(end[0, 3]) / unit_scale
|
||||||
|
y = float(end[1, 3]) / unit_scale
|
||||||
|
dx = float(end[0, 0])
|
||||||
|
dy = float(end[1, 0])
|
||||||
|
assert (
|
||||||
|
pytest.approx(h_expected[7][0]) == x
|
||||||
|
and pytest.approx(h_expected[7][1]) == y
|
||||||
|
and pytest.approx(h_expected[7][2]) == dx
|
||||||
|
and pytest.approx(h_expected[7][3]) == dy
|
||||||
|
)
|
||||||
|
|
||||||
|
vlayout = ifcopenshell.api.alignment.get_vertical_layout(alignment)
|
||||||
|
|
||||||
|
segment1 = file.createIfcAlignmentVerticalSegment(
|
||||||
|
StartDistAlong=0.0,
|
||||||
|
HorizontalLength=1200.0,
|
||||||
|
StartHeight=100.0,
|
||||||
|
StartGradient=1.75 / 100.0,
|
||||||
|
EndGradient=1.75 / 100.0,
|
||||||
|
PredefinedType="CONSTANTGRADIENT",
|
||||||
|
)
|
||||||
|
|
||||||
|
end = ifcopenshell.api.alignment.create_layout_segment(file, vlayout, segment1)
|
||||||
|
|
||||||
|
x = float(end[0, 3]) / unit_scale
|
||||||
|
y = float(end[1, 3]) / unit_scale
|
||||||
|
dx = float(end[0, 0])
|
||||||
|
dy = float(end[1, 0])
|
||||||
|
assert (
|
||||||
|
pytest.approx(v_expected[1][0]) == x
|
||||||
|
and pytest.approx(v_expected[1][1]) == y
|
||||||
|
and pytest.approx(v_expected[1][2]) == dx
|
||||||
|
and pytest.approx(v_expected[1][3]) == dy
|
||||||
|
)
|
||||||
|
segment2 = file.createIfcAlignmentVerticalSegment(
|
||||||
|
StartDistAlong=x,
|
||||||
|
HorizontalLength=1600.0,
|
||||||
|
StartHeight=y,
|
||||||
|
StartGradient=dy / dx,
|
||||||
|
EndGradient=-1.0 / 100.0,
|
||||||
|
PredefinedType="PARABOLICARC",
|
||||||
|
)
|
||||||
|
end = ifcopenshell.api.alignment.create_layout_segment(file, vlayout, segment2)
|
||||||
|
|
||||||
|
x = float(end[0, 3]) / unit_scale
|
||||||
|
y = float(end[1, 3]) / unit_scale
|
||||||
|
dx = float(end[0, 0])
|
||||||
|
dy = float(end[1, 0])
|
||||||
|
assert (
|
||||||
|
pytest.approx(v_expected[2][0]) == x
|
||||||
|
and pytest.approx(v_expected[2][1]) == y
|
||||||
|
and pytest.approx(v_expected[2][2]) == dx
|
||||||
|
and pytest.approx(v_expected[2][3]) == dy
|
||||||
|
)
|
||||||
|
segment3 = file.createIfcAlignmentVerticalSegment(
|
||||||
|
StartDistAlong=x,
|
||||||
|
HorizontalLength=1600.0,
|
||||||
|
StartHeight=y,
|
||||||
|
StartGradient=dy / dx,
|
||||||
|
EndGradient=-1.0 / 100.0,
|
||||||
|
PredefinedType="CONSTANTGRADIENT",
|
||||||
|
)
|
||||||
|
end = ifcopenshell.api.alignment.create_layout_segment(file, vlayout, segment3)
|
||||||
|
|
||||||
|
x = float(end[0, 3]) / unit_scale
|
||||||
|
y = float(end[1, 3]) / unit_scale
|
||||||
|
dx = float(end[0, 0])
|
||||||
|
dy = float(end[1, 0])
|
||||||
|
assert (
|
||||||
|
pytest.approx(v_expected[3][0]) == x
|
||||||
|
and pytest.approx(v_expected[3][1]) == y
|
||||||
|
and pytest.approx(v_expected[3][2]) == dx
|
||||||
|
and pytest.approx(v_expected[3][3]) == dy
|
||||||
|
)
|
||||||
|
segment4 = file.createIfcAlignmentVerticalSegment(
|
||||||
|
StartDistAlong=x,
|
||||||
|
HorizontalLength=1200.0,
|
||||||
|
StartHeight=y,
|
||||||
|
StartGradient=dy / dx,
|
||||||
|
EndGradient=2.0 / 100.0,
|
||||||
|
PredefinedType="PARABOLICARC",
|
||||||
|
)
|
||||||
|
end = ifcopenshell.api.alignment.create_layout_segment(file, vlayout, segment4)
|
||||||
|
|
||||||
|
x = float(end[0, 3]) / unit_scale
|
||||||
|
y = float(end[1, 3]) / unit_scale
|
||||||
|
dx = float(end[0, 0])
|
||||||
|
dy = float(end[1, 0])
|
||||||
|
assert (
|
||||||
|
pytest.approx(v_expected[4][0]) == x
|
||||||
|
and pytest.approx(v_expected[4][1]) == y
|
||||||
|
and pytest.approx(v_expected[4][2]) == dx
|
||||||
|
and pytest.approx(v_expected[4][3]) == dy
|
||||||
|
)
|
||||||
|
segment5 = file.createIfcAlignmentVerticalSegment(
|
||||||
|
StartDistAlong=x,
|
||||||
|
HorizontalLength=800.0,
|
||||||
|
StartHeight=y,
|
||||||
|
StartGradient=dy / dx,
|
||||||
|
EndGradient=2.0 / 100.0,
|
||||||
|
PredefinedType="CONSTANTGRADIENT",
|
||||||
|
)
|
||||||
|
end = ifcopenshell.api.alignment.create_layout_segment(file, vlayout, segment5)
|
||||||
|
|
||||||
|
x = float(end[0, 3]) / unit_scale
|
||||||
|
y = float(end[1, 3]) / unit_scale
|
||||||
|
dx = float(end[0, 0])
|
||||||
|
dy = float(end[1, 0])
|
||||||
|
assert (
|
||||||
|
pytest.approx(v_expected[5][0]) == x
|
||||||
|
and pytest.approx(v_expected[5][1]) == y
|
||||||
|
and pytest.approx(v_expected[5][2]) == dx
|
||||||
|
and pytest.approx(v_expected[5][3]) == dy
|
||||||
|
)
|
||||||
|
segment6 = file.createIfcAlignmentVerticalSegment(
|
||||||
|
StartDistAlong=x,
|
||||||
|
HorizontalLength=2000.0,
|
||||||
|
StartHeight=y,
|
||||||
|
StartGradient=dy / dx,
|
||||||
|
EndGradient=-2.0 / 100.0,
|
||||||
|
PredefinedType="PARABOLICARC",
|
||||||
|
)
|
||||||
|
end = ifcopenshell.api.alignment.create_layout_segment(file, vlayout, segment6)
|
||||||
|
|
||||||
|
x = float(end[0, 3]) / unit_scale
|
||||||
|
y = float(end[1, 3]) / unit_scale
|
||||||
|
dx = float(end[0, 0])
|
||||||
|
dy = float(end[1, 0])
|
||||||
|
assert (
|
||||||
|
pytest.approx(v_expected[6][0]) == x
|
||||||
|
and pytest.approx(v_expected[6][1]) == y
|
||||||
|
and pytest.approx(v_expected[6][2]) == dx
|
||||||
|
and pytest.approx(v_expected[6][3]) == dy
|
||||||
|
)
|
||||||
|
segment7 = file.createIfcAlignmentVerticalSegment(
|
||||||
|
StartDistAlong=x,
|
||||||
|
HorizontalLength=1000.0,
|
||||||
|
StartHeight=y,
|
||||||
|
StartGradient=dy / dx,
|
||||||
|
EndGradient=-2.0 / 100.0,
|
||||||
|
PredefinedType="CONSTANTGRADIENT",
|
||||||
|
)
|
||||||
|
end = ifcopenshell.api.alignment.create_layout_segment(file, vlayout, segment7)
|
||||||
|
|
||||||
|
x = float(end[0, 3]) / unit_scale
|
||||||
|
y = float(end[1, 3]) / unit_scale
|
||||||
|
dx = float(end[0, 0])
|
||||||
|
dy = float(end[1, 0])
|
||||||
|
assert (
|
||||||
|
pytest.approx(v_expected[7][0]) == x
|
||||||
|
and pytest.approx(v_expected[7][1]) == y
|
||||||
|
and pytest.approx(v_expected[7][2]) == dx
|
||||||
|
and pytest.approx(v_expected[7][3]) == dy
|
||||||
|
)
|
||||||
|
segment8 = file.createIfcAlignmentVerticalSegment(
|
||||||
|
StartDistAlong=x,
|
||||||
|
HorizontalLength=800.0,
|
||||||
|
StartHeight=y,
|
||||||
|
StartGradient=dy / dx,
|
||||||
|
EndGradient=-0.5 / 100.0,
|
||||||
|
PredefinedType="PARABOLICARC",
|
||||||
|
)
|
||||||
|
end = ifcopenshell.api.alignment.create_layout_segment(file, vlayout, segment8)
|
||||||
|
|
||||||
|
x = float(end[0, 3]) / unit_scale
|
||||||
|
y = float(end[1, 3]) / unit_scale
|
||||||
|
dx = float(end[0, 0])
|
||||||
|
dy = float(end[1, 0])
|
||||||
|
assert (
|
||||||
|
pytest.approx(v_expected[8][0]) == x
|
||||||
|
and pytest.approx(v_expected[8][1]) == y
|
||||||
|
and pytest.approx(v_expected[8][2]) == dx
|
||||||
|
and pytest.approx(v_expected[8][3]) == dy
|
||||||
|
)
|
||||||
|
segment9 = file.createIfcAlignmentVerticalSegment(
|
||||||
|
StartDistAlong=x,
|
||||||
|
HorizontalLength=2600.0,
|
||||||
|
StartHeight=y,
|
||||||
|
StartGradient=dy / dx,
|
||||||
|
EndGradient=-0.5 / 100.0,
|
||||||
|
PredefinedType="CONSTANTGRADIENT",
|
||||||
|
)
|
||||||
|
end = ifcopenshell.api.alignment.create_layout_segment(file, vlayout, segment9)
|
||||||
|
x = float(end[0, 3]) / unit_scale
|
||||||
|
y = float(end[1, 3]) / unit_scale
|
||||||
|
dx = float(end[0, 0])
|
||||||
|
dy = float(end[1, 0])
|
||||||
|
assert (
|
||||||
|
pytest.approx(v_expected[9][0]) == x
|
||||||
|
and pytest.approx(v_expected[9][1]) == y
|
||||||
|
and pytest.approx(v_expected[9][2]) == dx
|
||||||
|
and pytest.approx(v_expected[9][3]) == dy
|
||||||
|
)
|
||||||
|
|
||||||
|
ifcopenshell.api.alignment.create_representation(file, alignment)
|
||||||
|
|
||||||
|
curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||||
|
assert curve.is_a("IfcCompositeCurve")
|
||||||
|
for s in curve.Segments:
|
||||||
|
assert len(s.UsingCurves) == 1
|
||||||
|
|
||||||
|
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
|
||||||
|
assert curve.is_a("IfcCompositeCurve")
|
||||||
|
for index, s in enumerate(curve.Segments):
|
||||||
|
assert len(s.UsingCurves) == 1
|
||||||
|
assert s.Placement.Location.Coordinates[0] == pytest.approx(h_expected[index][0])
|
||||||
|
assert s.Placement.Location.Coordinates[1] == pytest.approx(h_expected[index][1])
|
||||||
|
assert s.Placement.RefDirection.DirectionRatios[0] == pytest.approx(h_expected[index][2])
|
||||||
|
assert s.Placement.RefDirection.DirectionRatios[1] == pytest.approx(h_expected[index][3])
|
||||||
|
|
||||||
|
curve = ifcopenshell.api.alignment.get_layout_curve(vlayout)
|
||||||
|
assert curve.is_a("IfcGradientCurve")
|
||||||
|
for index, s in enumerate(curve.Segments):
|
||||||
|
assert len(s.UsingCurves) == 1
|
||||||
|
assert s.Placement.Location.Coordinates[0] == pytest.approx(v_expected[index][0])
|
||||||
|
assert s.Placement.Location.Coordinates[1] == pytest.approx(v_expected[index][1])
|
||||||
|
assert s.Placement.RefDirection.DirectionRatios[0] == pytest.approx(v_expected[index][2])
|
||||||
|
assert s.Placement.RefDirection.DirectionRatios[1] == pytest.approx(v_expected[index][3])
|
||||||
|
|
||||||
|
|
||||||
|
test_create_representation()
|
||||||
@@ -65,7 +65,7 @@ def test_vertical_layout_by_pi_method():
|
|||||||
assert len(layout_nest.RelatedObjects) == 2
|
assert len(layout_nest.RelatedObjects) == 2
|
||||||
|
|
||||||
referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
||||||
assert len(referent_nest.RelatedObjects) == 6
|
assert len(referent_nest.RelatedObjects) == 1
|
||||||
|
|
||||||
segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(vlayout)
|
segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(vlayout)
|
||||||
assert len(segment_nest.RelatedObjects) == 3
|
assert len(segment_nest.RelatedObjects) == 3
|
||||||
|
|||||||
Reference in New Issue
Block a user