mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-05 23:41:44 +00:00
cleanup from original python alignment prototyping
`transition_curve.py` is now obsolete and has been removed. A python port of IfcAlignmentHelper.cpp has also been started. It needs further development to accommodate vertical alignment.
This commit is contained in:
@@ -1 +1 @@
|
|||||||
tests/
|
uv.lock
|
||||||
@@ -16,228 +16,83 @@
|
|||||||
# 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 operator
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
import math
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
import numpy
|
import numpy as np
|
||||||
|
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.geom
|
import ifcopenshell.geom
|
||||||
import ifcopenshell.express
|
import ifcopenshell.guid
|
||||||
import ifcopenshell.transition_curve
|
import ifcopenshell.template
|
||||||
|
from ifcopenshell import entity_instance
|
||||||
# geometric primitives
|
from ifcopenshell import ifcopenshell_wrapper
|
||||||
|
|
||||||
# @notes
|
|
||||||
# - not sure if the separation of geometric primitives make sense
|
|
||||||
# does it make handling the variety of distance expressions and
|
|
||||||
# interpolation harder?
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
def evaluate_representation(shape_rep: entity_instance, dist_along: float) -> np.ndarray:
|
||||||
class line:
|
|
||||||
start_point: numpy.ndarray
|
|
||||||
direction_vector: numpy.ndarray
|
|
||||||
|
|
||||||
def __call__(self, u):
|
|
||||||
p = numpy.ndarray((3,))
|
|
||||||
p[0:2] = self.start_point + self.direction_vector * u
|
|
||||||
p[2] = numpy.nan
|
|
||||||
return p
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class circle:
|
|
||||||
radius: numpy.ndarray
|
|
||||||
|
|
||||||
def __call__(self, u):
|
|
||||||
return numpy.array([self.radius * numpy.cos(u), self.radius * numpy.sin(u), numpy.nan])
|
|
||||||
|
|
||||||
|
|
||||||
def place(matrix, func):
|
|
||||||
"""
|
"""
|
||||||
Higher order function for application of a 3x3 matrix
|
Calculate the 4x4 geometric transform at a point on an alignment segment
|
||||||
to a 2D point. Assumes a functor such as line or circle.
|
@param shape_rep: The representation shape (composite curve, gradient curve, or segmented reference curve) to evaluate
|
||||||
|
@param dist_along: The distance along this representation at the point of interest (point to be calculated)
|
||||||
"""
|
"""
|
||||||
|
supported_rep_types = ["IFCCOMPOSITECURVE", "IFCGRADIENTCURVE", "IFCSEGMENTEDREFERENCECURVE"]
|
||||||
|
shape_rep_type = shape_rep.is_a().upper()
|
||||||
|
if not shape_rep_type in supported_rep_types:
|
||||||
|
raise NotImplementedError(f"Expected entity type to be one of {[_ for _ in supported_rep_types]}, got '{shape_rep_type}")
|
||||||
|
|
||||||
def inner(*args):
|
# TODO: confirm point is not beyond limits of alignment
|
||||||
v = func(*args)
|
|
||||||
# homogenize
|
|
||||||
v = numpy.insert(v[0:2], v[0:2].shape, 1, axis=-1)
|
|
||||||
p = numpy.ndarray((3,))
|
|
||||||
p[0:2] = (matrix @ v)[0:2]
|
|
||||||
p[2] = numpy.nan
|
|
||||||
return p
|
|
||||||
|
|
||||||
return inner
|
s = ifcopenshell.geom.settings()
|
||||||
|
piecewise_function = ifcopenshell_wrapper.map_shape(s, shape_rep.wrapped_data)
|
||||||
|
|
||||||
|
trans_matrix = piecewise_function.evaluate(dist_along)
|
||||||
|
|
||||||
|
return np.array(trans_matrix, dtype=np.float64).T
|
||||||
|
|
||||||
|
|
||||||
# primitives for manipulating and joining curve functor domains
|
def evaluate_segment(segment: entity_instance, dist_along: float) -> np.ndarray:
|
||||||
|
|
||||||
|
|
||||||
def reparametrized_curve(fn, a, b):
|
|
||||||
return lambda u: fn(a * u + b)
|
|
||||||
|
|
||||||
|
|
||||||
def normalized_curve(fn):
|
|
||||||
return lambda u: fn(u / fn.length)
|
|
||||||
|
|
||||||
|
|
||||||
class trimmed_curve:
|
|
||||||
def __init__(self, fn, length):
|
|
||||||
self.fn = fn
|
|
||||||
self.length = length
|
|
||||||
|
|
||||||
def __call__(self, u):
|
|
||||||
assert u >= 0.0 and u <= self.length
|
|
||||||
return self.fn(u)
|
|
||||||
|
|
||||||
|
|
||||||
class piecewise:
|
|
||||||
# takes a set of functors and returns a function f(u) that delegates to the correct segment
|
|
||||||
|
|
||||||
def __init__(self, fns):
|
|
||||||
self.fns = fns
|
|
||||||
self.length = sum(map(operator.attrgetter("length"), fns))
|
|
||||||
|
|
||||||
def __call__(self, u):
|
|
||||||
# this is silly, assuming `u` is monotonically increases we should not always start
|
|
||||||
# searching from the first segment or at least binary search into the segment
|
|
||||||
# lengths
|
|
||||||
u0 = 0
|
|
||||||
for fn in self.fns:
|
|
||||||
u1 = u0 + fn.length
|
|
||||||
if u >= u0 and u <= u1:
|
|
||||||
return fn(u - u0)
|
|
||||||
u0 = u1
|
|
||||||
|
|
||||||
|
|
||||||
# mapping functions from IFC entities
|
|
||||||
|
|
||||||
|
|
||||||
def map_inst(inst):
|
|
||||||
"""
|
"""
|
||||||
Looks up one of the implementation functions below in the global namespace
|
Calculate the 4x4 geometric transform at a point on an alignment segment
|
||||||
|
@param segment: The segment containing the point that we would like to
|
||||||
|
@param dist_along: The distance along this segment at the point of interest (point to be calculated)
|
||||||
"""
|
"""
|
||||||
return globals()[f"impl_{inst.is_a()}"](inst)
|
supported_segment_types = ["IFCCURVESEGMENT"]
|
||||||
|
segment_type = segment.is_a().upper()
|
||||||
|
if not segment_type in supported_segment_types:
|
||||||
|
raise NotImplementedError(f"Expected entity type 'IFCCURVESEGMENT', got '{segment_type}")
|
||||||
|
if dist_along > segment.SegmentLength:
|
||||||
|
raise ValueError(f"Provided value {dist_along=} is beyond the end of the segment ({segment.SegmentLength}).")
|
||||||
|
|
||||||
|
s = ifcopenshell.geom.settings()
|
||||||
|
piecewise_function = ifcopenshell_wrapper.map_shape(s, segment.wrapped_data)
|
||||||
|
|
||||||
|
trans_matrix = piecewise_function.evaluate(dist_along)
|
||||||
|
|
||||||
|
return np.array(trans_matrix, dtype=np.float64).T
|
||||||
|
|
||||||
|
|
||||||
def impl_IfcLine(inst):
|
def generate_vertices(rep_curve: entity_instance, distance_interval: float = 5.0) -> np.ndarray:
|
||||||
return line(
|
|
||||||
numpy.array(inst.Pnt.Coordinates),
|
|
||||||
numpy.array(inst.Dir.Orientation.DirectionRatios) * inst.Dir.Magnitude,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def impl_IfcCircle(inst):
|
|
||||||
return place(map_inst(inst.Position), circle(inst.Radius))
|
|
||||||
|
|
||||||
|
|
||||||
def impl_IfcClothoid(inst):
|
|
||||||
# @todo
|
|
||||||
# place = map_inst(inst.Position)
|
|
||||||
# ifcopenshell.transition_curve.TransitionCurve(
|
|
||||||
# StartPoint = place.T[2]
|
|
||||||
# StartDirection = numpy.arctan2(place.T[0][1], place.T[0][0]),
|
|
||||||
# SegmentLength =
|
|
||||||
# IsStartRadiusCCW =
|
|
||||||
# IsEndRadiusCCW =
|
|
||||||
# TransitionCurveType =
|
|
||||||
# StartRadius =
|
|
||||||
# EndRadius =
|
|
||||||
# )
|
|
||||||
return lambda *args: numpy.array((0.0, 0.0))
|
|
||||||
|
|
||||||
|
|
||||||
def impl_IfcAxis2Placement2D(inst):
|
|
||||||
arr = numpy.eye(3)
|
|
||||||
|
|
||||||
if inst is None:
|
|
||||||
return arr
|
|
||||||
|
|
||||||
arr.T[2, 0:2] = inst.Location.Coordinates
|
|
||||||
|
|
||||||
if inst.RefDirection is None:
|
|
||||||
return arr
|
|
||||||
|
|
||||||
arr.T[0, 0:2] = inst.RefDirection.DirectionRatios
|
|
||||||
arr.T[0, 0:2] /= numpy.linalg.norm(arr.T[0, 0:2])
|
|
||||||
arr.T[1, 0:2] = -arr.T[0, 1], arr.T[0, 0]
|
|
||||||
|
|
||||||
return arr
|
|
||||||
|
|
||||||
|
|
||||||
# conversion functions for semantic design parameters (not used atm)
|
|
||||||
|
|
||||||
|
|
||||||
def convert(inst):
|
|
||||||
"""
|
"""
|
||||||
Looks up one of the conversion functions below in the global namespace
|
Generate vertices along an alignment
|
||||||
|
|
||||||
|
@param rep_curve: The alignment's representation curve to use to generate vertices.
|
||||||
|
|
||||||
|
Note: rep_curve must be IfcCompositeCurve, IfcGradientCurve, or IfcSegmentedReferenceCurve
|
||||||
|
|
||||||
|
@param distance_interval: The distance between points along the alignment at which to generate the points
|
||||||
"""
|
"""
|
||||||
yield from globals()[f"convert_{inst.is_a()}_{inst.PredefinedType}"](inst)
|
if rep_curve is None:
|
||||||
|
raise ValueError("Alignment representation not found.")
|
||||||
|
|
||||||
|
s = ifcopenshell.geom.settings()
|
||||||
def convert_IfcAlignmentHorizontalSegment_LINE(data):
|
s.set("PIECEWISE_STEP_PARAM", distance_interval)
|
||||||
xy = numpy.array(data.StartPoint.Coordinates)
|
shape = ifcopenshell.geom.create_shape(s, rep_curve)
|
||||||
yield xy
|
vertices = shape.verts
|
||||||
di = numpy.array([numpy.cos(data.StartDirection), numpy.sin(data.StartDirection)])
|
if len(vertices) == 0:
|
||||||
yield xy + di * data.SegmentLength
|
msg = f"[ERROR] No vertices generated by ifcopenshell.geom.create_shape()."
|
||||||
|
raise ValueError(msg)
|
||||||
|
return np.array(vertices).reshape((-1, 3))
|
||||||
# Two approaches, either DesignParameters or Representation
|
|
||||||
|
|
||||||
|
|
||||||
def interpret_linear_element_semantics(settings, crv):
|
|
||||||
# traverse decomposition
|
|
||||||
for rel in crv.IsNestedBy:
|
|
||||||
for obj in rel.RelatedObjects:
|
|
||||||
yield from interpret_linear_element_semantics(settings, obj)
|
|
||||||
|
|
||||||
# lookup design parameters and dispatch to conversion function
|
|
||||||
if crv.is_a("IfcAlignmentSegment"):
|
|
||||||
dp = crv.DesignParameters
|
|
||||||
yield from convert(dp)
|
|
||||||
|
|
||||||
|
|
||||||
def evaluate_segment(segment):
|
|
||||||
# print(segment)
|
|
||||||
# print(segment.ParentCurve)
|
|
||||||
# print()
|
|
||||||
|
|
||||||
func = place(map_inst(segment.Placement), map_inst(segment.ParentCurve))
|
|
||||||
|
|
||||||
# reparam so domain starts at zero
|
|
||||||
reparam = reparametrized_curve(func, 1.0, -segment.SegmentStart[0])
|
|
||||||
|
|
||||||
# embed curve length (doesn't do much, just make length recoverable)
|
|
||||||
trimmed = trimmed_curve(reparam, segment.SegmentLength[0])
|
|
||||||
|
|
||||||
return trimmed
|
|
||||||
|
|
||||||
|
|
||||||
def interpret_linear_element_geometry(settings, crv):
|
|
||||||
func = piecewise(
|
|
||||||
list(
|
|
||||||
map(
|
|
||||||
evaluate_segment,
|
|
||||||
crv.Representation.Representations[0].Items[0].Segments,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for u in numpy.linspace(0, func.length, num=int(numpy.ceil(func.length / 0.05))):
|
|
||||||
yield func(u)
|
|
||||||
|
|
||||||
|
|
||||||
interpret_linear_element = interpret_linear_element_geometry
|
|
||||||
|
|
||||||
|
|
||||||
def create_shape(settings, elem):
|
|
||||||
if elem.is_a("IfcLinearPositioningElement") or elem.is_a("IfcLinearElement"):
|
|
||||||
return numpy.row_stack(list(interpret_linear_element(settings, elem)))
|
|
||||||
else:
|
|
||||||
return ifcopenshell.geom.create_shape(settings, elem)
|
|
||||||
|
|
||||||
|
|
||||||
def print_structure(alignment, indent=0):
|
def print_structure(alignment, indent=0):
|
||||||
@@ -250,17 +105,544 @@ def print_structure(alignment, indent=0):
|
|||||||
print_structure(child, indent + 2)
|
print_structure(child, indent + 2)
|
||||||
|
|
||||||
|
|
||||||
|
def name_segments(prefix: str, segments: Sequence[entity_instance]) -> None:
|
||||||
|
"""
|
||||||
|
Sets the segment name like ("H1" for horizontal, "V1" for vertical, "C1" for cant)
|
||||||
|
"""
|
||||||
|
for i, segment in enumerate(segments):
|
||||||
|
segment.Name = f"{prefix}{i + 1}"
|
||||||
|
|
||||||
|
|
||||||
|
class IfcAlignmentHelper:
|
||||||
|
"""
|
||||||
|
Create a new IfcAlignment including horizontal and vertical alignments by PI points.
|
||||||
|
|
||||||
|
Currently only supports horizontal lines and circular arcs (no spirals or other transitions)
|
||||||
|
Currently only supports parabolic vertical curves.
|
||||||
|
Does not yet accommodate cant alignment considerations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO: add missing functionality noted in the docstring
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
file: ifcopenshell.file = None,
|
||||||
|
filename: str = None,
|
||||||
|
creator: str = None,
|
||||||
|
organization: str = None,
|
||||||
|
application: str = None,
|
||||||
|
project_globalid=None,
|
||||||
|
project_name: str = None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
@param file: An existing model that the alignment will be added to
|
||||||
|
@param filename: Name for a new model to be created that will contain the alignment
|
||||||
|
@param creator: Name of the actor creating the file
|
||||||
|
@param organization: Name of the creator's organization
|
||||||
|
@param application: Name of the authoring application
|
||||||
|
@param project_globalid: value for the file's IfcProject.GlobalId attribute
|
||||||
|
@param project_name: value for the file's IfcProject.Name attribute
|
||||||
|
"""
|
||||||
|
if file is None:
|
||||||
|
self._file = ifcopenshell.template.create(
|
||||||
|
filename=filename,
|
||||||
|
creator=creator,
|
||||||
|
organization=organization,
|
||||||
|
application=application,
|
||||||
|
project_globalid=project_globalid,
|
||||||
|
project_name=project_name,
|
||||||
|
schema_identifier="IFC4X3_ADD2",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._file = file
|
||||||
|
|
||||||
|
self._geom_context = self._file.by_type("IfcGeometricRepresentationContext")[0]
|
||||||
|
self._axis_geom_subcontext = self._file.createIfcGeometricRepresentationSubContext(
|
||||||
|
ContextIdentifier="Axis",
|
||||||
|
ContextType="Model",
|
||||||
|
ParentContext=self._geom_context,
|
||||||
|
TargetView="GRAPH_VIEW"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _create_segment_representations(self, global_placement: entity_instance,
|
||||||
|
curve_segments: Sequence[entity_instance], segments: Sequence[entity_instance]):
|
||||||
|
for curve_segment, alignment_segment in zip(curve_segments, segments):
|
||||||
|
axis_representation = self._file.create_entity(
|
||||||
|
type="IfcShapeRepresentation",
|
||||||
|
ContextOfItems=self._axis_geom_subcontext,
|
||||||
|
RepresentationIdentifier="Axis",
|
||||||
|
RepresentationType="Segment",
|
||||||
|
Items=(curve_segment,)
|
||||||
|
)
|
||||||
|
product = self._file.create_entity(
|
||||||
|
type="IfcProductDefinitionShape",
|
||||||
|
Name=None,
|
||||||
|
Description=None,
|
||||||
|
Representations=(axis_representation,)
|
||||||
|
)
|
||||||
|
alignment_segment.ObjectPlacement = global_placement
|
||||||
|
alignment_segment.Representation = product
|
||||||
|
|
||||||
|
def _map_alignment_horizontal_segment(self, segment: entity_instance) -> Sequence[entity_instance]:
|
||||||
|
segment_type = segment.is_a().upper()
|
||||||
|
expected_type = "IFCALIGNMENTHORIZONTALSEGMENT"
|
||||||
|
if not segment_type == expected_type:
|
||||||
|
raise TypeError(f"Expected to see type '{expected_type}', instead received '{segment_type}'.")
|
||||||
|
|
||||||
|
start_point = segment.StartPoint
|
||||||
|
start_direction = segment.StartDirection
|
||||||
|
start_radius = segment.StartRadiusOfCurvature
|
||||||
|
length = segment.SegmentLength
|
||||||
|
_type = segment.PredefinedType
|
||||||
|
|
||||||
|
if math.isclose(length, 0):
|
||||||
|
# set transition value based on whether this is the final zero-length segment
|
||||||
|
transition = "DISCONTINUOUS"
|
||||||
|
else:
|
||||||
|
transition = "CONTSAMEGRADIENTSAMECURVATURE"
|
||||||
|
|
||||||
|
match _type:
|
||||||
|
case "LINE":
|
||||||
|
parent_curve = self._file.create_entity(
|
||||||
|
type="IfcLine",
|
||||||
|
Pnt=self._file.create_entity(
|
||||||
|
type="IfcCartesianPoint",
|
||||||
|
Coordinates=(0., 0.),
|
||||||
|
),
|
||||||
|
Dir=self._file.create_entity(
|
||||||
|
type="IfcVector",
|
||||||
|
Orientation=self._file.create_entity(
|
||||||
|
type="IfcDirection",
|
||||||
|
DirectionRatios=(1., 0.),
|
||||||
|
),
|
||||||
|
Magnitude=1.,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
curve_segment = self._file.create_entity(
|
||||||
|
type="IfcCurveSegment",
|
||||||
|
Transition=transition,
|
||||||
|
Placement=self._file.create_entity(
|
||||||
|
type="IfcAxis2Placement2D",
|
||||||
|
Location=start_point,
|
||||||
|
RefDirection=self._file.createIfcDirection(
|
||||||
|
(math.cos(start_direction), math.sin(start_direction)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SegmentStart=self._file.createIfcLengthMeasure(0.),
|
||||||
|
SegmentLength=self._file.createIfcLengthMeasure(length),
|
||||||
|
ParentCurve=parent_curve,
|
||||||
|
)
|
||||||
|
result = (curve_segment, None)
|
||||||
|
case "CIRCULARARC":
|
||||||
|
parent_curve = self._file.createIfcCircle(
|
||||||
|
Position=self._file.createIfcAxis2Placement2D(
|
||||||
|
Location=self._file.createIfcCartesianPoint(Coordinates=(0., 0.)),
|
||||||
|
RefDirection=self._file.createIfcDirection(
|
||||||
|
(math.cos(start_direction), math.sin(start_direction))),
|
||||||
|
),
|
||||||
|
Radius=abs(start_radius),
|
||||||
|
)
|
||||||
|
|
||||||
|
curve_segment = self._file.create_entity(
|
||||||
|
type="IfcCurveSegment",
|
||||||
|
Transition=transition,
|
||||||
|
Placement=self._file.create_entity(
|
||||||
|
type="IfcAxis2Placement2D",
|
||||||
|
Location=start_point,
|
||||||
|
RefDirection=self._file.createIfcDirection(
|
||||||
|
(math.cos(start_direction), math.sin(start_direction)))),
|
||||||
|
SegmentStart=self._file.createIfcLengthMeasure(0.),
|
||||||
|
SegmentLength=self._file.createIfcLengthMeasure(length * start_radius / abs(start_radius)),
|
||||||
|
ParentCurve=parent_curve,
|
||||||
|
)
|
||||||
|
result = (curve_segment, None)
|
||||||
|
|
||||||
|
case _:
|
||||||
|
result = (None, None)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _create_horizontal_alignment(
|
||||||
|
self, name: str, description: str, points: Sequence[Sequence[float]], radii: Sequence[float],
|
||||||
|
include_geometry: bool = True
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Create a horizontal alignment using the PI layout method.
|
||||||
|
|
||||||
|
@param name: value for Name attribute
|
||||||
|
@param description: value for Description attribute
|
||||||
|
@param points: (X, Y) pairs denoting the location of the horizontal PIs, including start (POB) and end (POE).
|
||||||
|
@param radii: radii values to use for transition
|
||||||
|
@param include_geometry: optionally create the alignment geometric representation as well as the semantic business logic
|
||||||
|
"""
|
||||||
|
horizontal_segments = list() # business logic
|
||||||
|
horizontal_curve_segments = list() # geometry
|
||||||
|
|
||||||
|
xBT, yBT = points[0]
|
||||||
|
xPI, yPI = points[1]
|
||||||
|
|
||||||
|
i = 1
|
||||||
|
|
||||||
|
for radius in radii:
|
||||||
|
# back tangent
|
||||||
|
dxBT = xPI - xBT
|
||||||
|
dyBT = yPI - yBT
|
||||||
|
angleBT = math.atan2(dyBT, dxBT)
|
||||||
|
lengthBT = math.sqrt(dxBT * dxBT + dyBT * dyBT)
|
||||||
|
|
||||||
|
# forward tangent
|
||||||
|
i += 1
|
||||||
|
xFT, yFT = points[i]
|
||||||
|
dxFT = xFT - xPI
|
||||||
|
dyFT = yFT - yPI
|
||||||
|
angleFT = math.atan2(dyFT, dxFT)
|
||||||
|
|
||||||
|
delta = angleFT - angleBT
|
||||||
|
|
||||||
|
tangent = abs(radius * math.tan(delta / 2))
|
||||||
|
|
||||||
|
lc = abs(radius * delta)
|
||||||
|
|
||||||
|
radius *= delta / abs(delta)
|
||||||
|
|
||||||
|
xPC = xPI - tangent * math.cos(angleBT)
|
||||||
|
yPC = yPI - tangent * math.sin(angleBT)
|
||||||
|
|
||||||
|
xPT = xPI + tangent * math.cos(angleFT)
|
||||||
|
yPT = yPI + tangent * math.sin(angleFT)
|
||||||
|
|
||||||
|
tangent_run = lengthBT - tangent
|
||||||
|
|
||||||
|
# create back tangent run
|
||||||
|
pt = self._file.create_entity(
|
||||||
|
type="IfcCartesianPoint",
|
||||||
|
Coordinates=(xBT, yBT),
|
||||||
|
)
|
||||||
|
design_parameters = self._file.create_entity(
|
||||||
|
type="IfcAlignmentHorizontalSegment",
|
||||||
|
StartTag=None,
|
||||||
|
EndTag=None,
|
||||||
|
StartPoint=pt,
|
||||||
|
StartDirection=angleBT,
|
||||||
|
StartRadiusOfCurvature=0.0,
|
||||||
|
EndRadiusOfCurvature=0.0,
|
||||||
|
SegmentLength=tangent_run,
|
||||||
|
GravityCenterLineHeight=None,
|
||||||
|
PredefinedType="LINE",
|
||||||
|
)
|
||||||
|
alignment_segment = self._file.create_entity(
|
||||||
|
type="IfcAlignmentSegment",
|
||||||
|
GlobalId=ifcopenshell.guid.new(),
|
||||||
|
OwnerHistory=None,
|
||||||
|
Name=None,
|
||||||
|
Description=None,
|
||||||
|
ObjectType=None,
|
||||||
|
ObjectPlacement=None,
|
||||||
|
Representation=None,
|
||||||
|
DesignParameters=design_parameters,
|
||||||
|
)
|
||||||
|
horizontal_segments.append(alignment_segment)
|
||||||
|
|
||||||
|
if include_geometry:
|
||||||
|
horizontal_curve_segments.append(
|
||||||
|
self._map_alignment_horizontal_segment(design_parameters)[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
# create circular curve
|
||||||
|
pc = self._file.create_entity(
|
||||||
|
type="IfcCartesianPoint",
|
||||||
|
Coordinates=(xPC, yPC),
|
||||||
|
)
|
||||||
|
design_parameters = self._file.create_entity(
|
||||||
|
type="IfcAlignmentHorizontalSegment",
|
||||||
|
StartTag=None,
|
||||||
|
EndTag=None,
|
||||||
|
StartPoint=pc,
|
||||||
|
StartDirection=angleBT,
|
||||||
|
StartRadiusOfCurvature=float(radius),
|
||||||
|
EndRadiusOfCurvature=float(radius),
|
||||||
|
SegmentLength=lc,
|
||||||
|
GravityCenterLineHeight=None,
|
||||||
|
PredefinedType="CIRCULARARC",
|
||||||
|
)
|
||||||
|
alignment_segment = self._file.create_entity(
|
||||||
|
type="IfcAlignmentSegment",
|
||||||
|
GlobalId=ifcopenshell.guid.new(),
|
||||||
|
OwnerHistory=None,
|
||||||
|
Name=None,
|
||||||
|
Description=None,
|
||||||
|
ObjectType=None,
|
||||||
|
ObjectPlacement=None,
|
||||||
|
Representation=None,
|
||||||
|
DesignParameters=design_parameters,
|
||||||
|
)
|
||||||
|
horizontal_segments.append(alignment_segment)
|
||||||
|
|
||||||
|
if include_geometry:
|
||||||
|
horizontal_curve_segments.append(
|
||||||
|
self._map_alignment_horizontal_segment(design_parameters)[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
xBT = xPT
|
||||||
|
yBT = yPT
|
||||||
|
xPI = xFT
|
||||||
|
yPI = yFT
|
||||||
|
|
||||||
|
# done processing radii
|
||||||
|
# create last tangent run
|
||||||
|
dx = xPI - xBT
|
||||||
|
dy = yPI - yBT
|
||||||
|
angleBT = math.atan2(dy, dx)
|
||||||
|
tangent_run = math.sqrt(dx * dx + dy * dy)
|
||||||
|
pt = self._file.create_entity(
|
||||||
|
type="IfcCartesianPoint",
|
||||||
|
Coordinates=(xBT, yBT)
|
||||||
|
)
|
||||||
|
|
||||||
|
design_parameters = self._file.create_entity(
|
||||||
|
type="IfcAlignmentHorizontalSegment",
|
||||||
|
StartTag=None,
|
||||||
|
EndTag=None,
|
||||||
|
StartPoint=pt,
|
||||||
|
StartDirection=angleBT,
|
||||||
|
StartRadiusOfCurvature=0.0,
|
||||||
|
EndRadiusOfCurvature=0.0,
|
||||||
|
SegmentLength=tangent_run,
|
||||||
|
GravityCenterLineHeight=None,
|
||||||
|
PredefinedType="LINE",
|
||||||
|
)
|
||||||
|
alignment_segment = self._file.create_entity(
|
||||||
|
type="IfcAlignmentSegment",
|
||||||
|
GlobalId=ifcopenshell.guid.new(),
|
||||||
|
OwnerHistory=None,
|
||||||
|
Name=None,
|
||||||
|
Description=None,
|
||||||
|
ObjectType=None,
|
||||||
|
ObjectPlacement=None,
|
||||||
|
Representation=None,
|
||||||
|
DesignParameters=design_parameters,
|
||||||
|
)
|
||||||
|
horizontal_segments.append(alignment_segment)
|
||||||
|
if include_geometry:
|
||||||
|
horizontal_curve_segments.append(
|
||||||
|
self._map_alignment_horizontal_segment(design_parameters)[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
# create zero length terminator segment
|
||||||
|
poe = self._file.create_entity(
|
||||||
|
type="IfcCartesianPoint",
|
||||||
|
Coordinates=(xPI, yPI)
|
||||||
|
)
|
||||||
|
|
||||||
|
design_parameters = self._file.create_entity(
|
||||||
|
type="IfcAlignmentHorizontalSegment",
|
||||||
|
StartTag="POE",
|
||||||
|
EndTag="POE",
|
||||||
|
StartPoint=poe,
|
||||||
|
StartDirection=angleBT,
|
||||||
|
StartRadiusOfCurvature=0.0,
|
||||||
|
EndRadiusOfCurvature=0.0,
|
||||||
|
SegmentLength=0.0,
|
||||||
|
GravityCenterLineHeight=None,
|
||||||
|
PredefinedType="LINE",
|
||||||
|
)
|
||||||
|
alignment_segment = self._file.create_entity(
|
||||||
|
type="IfcAlignmentSegment",
|
||||||
|
GlobalId=ifcopenshell.guid.new(),
|
||||||
|
OwnerHistory=None,
|
||||||
|
Name=None,
|
||||||
|
Description=None,
|
||||||
|
ObjectType=None,
|
||||||
|
ObjectPlacement=None,
|
||||||
|
Representation=None,
|
||||||
|
DesignParameters=design_parameters,
|
||||||
|
)
|
||||||
|
horizontal_segments.append(alignment_segment)
|
||||||
|
if include_geometry:
|
||||||
|
horizontal_curve_segments.append(
|
||||||
|
self._map_alignment_horizontal_segment(design_parameters)[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
if include_geometry:
|
||||||
|
composite_curve = self._file.create_entity(
|
||||||
|
type="IfcCompositeCurve",
|
||||||
|
Segments=horizontal_curve_segments,
|
||||||
|
SelfIntersect=False,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
composite_curve = None
|
||||||
|
|
||||||
|
return horizontal_segments, horizontal_curve_segments, composite_curve
|
||||||
|
|
||||||
|
def _add_horizontal_alignment(
|
||||||
|
self, alignment_name: str, points: Sequence[Sequence[float]], radii: Sequence[float],
|
||||||
|
include_geometry: bool = True, alignment_description: str = None, start_station : float = 1000.,
|
||||||
|
):
|
||||||
|
horizontal_segments, horizontal_curve_segments, composite_curve = self._create_horizontal_alignment(
|
||||||
|
alignment_name, alignment_description, points, radii, include_geometry,
|
||||||
|
)
|
||||||
|
|
||||||
|
name_segments(prefix="H", segments=horizontal_segments)
|
||||||
|
|
||||||
|
# Create the horizontal alignment (IfcAlignmentHorizontal) and nest alignment segments
|
||||||
|
horizontal_alignment = self._file.create_entity(
|
||||||
|
type="IfcAlignmentHorizontal",
|
||||||
|
GlobalId=ifcopenshell.guid.new(),
|
||||||
|
OwnerHistory=None,
|
||||||
|
Name=f"{alignment_name} - Horizontal",
|
||||||
|
Description=alignment_description,
|
||||||
|
ObjectType=None,
|
||||||
|
ObjectPlacement=None,
|
||||||
|
Representation=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
nests_horizontal_segments = self._file.create_entity(
|
||||||
|
type="IfcRelNests",
|
||||||
|
GlobalId=ifcopenshell.guid.new(),
|
||||||
|
OwnerHistory=None,
|
||||||
|
Name="Nests horizontal alignment segments under horizontal alignment",
|
||||||
|
RelatingObject=horizontal_alignment,
|
||||||
|
RelatedObjects=horizontal_segments,
|
||||||
|
)
|
||||||
|
|
||||||
|
placement = self._file.createIfcLocalPlacement(
|
||||||
|
PlacementRelTo=None,
|
||||||
|
RelativePlacement=self._file.createIfcAxis2Placement2D(
|
||||||
|
Location=self._file.createIfcCartesianPoint(
|
||||||
|
Coordinates=(0., 0.)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# create the alignment
|
||||||
|
alignment = self._file.create_entity(
|
||||||
|
type="IfcAlignment",
|
||||||
|
GlobalId=ifcopenshell.guid.new(),
|
||||||
|
OwnerHistory=None,
|
||||||
|
Name=alignment_name,
|
||||||
|
Description=alignment_description,
|
||||||
|
ObjectType=None,
|
||||||
|
ObjectPlacement=placement,
|
||||||
|
Representation=None,
|
||||||
|
PredefinedType=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# create geometric representation
|
||||||
|
if include_geometry:
|
||||||
|
# create the footprint representation
|
||||||
|
footprint_shape_representation = self._file.create_entity(
|
||||||
|
type="IfcShapeRepresentation",
|
||||||
|
ContextOfItems=self._axis_geom_subcontext,
|
||||||
|
RepresentationIdentifier="FootPrint",
|
||||||
|
RepresentationType="Curve2D",
|
||||||
|
Items=(composite_curve,)
|
||||||
|
)
|
||||||
|
|
||||||
|
# create the alignment product definition
|
||||||
|
product_definition_shape = self._file.create_entity(
|
||||||
|
type="IfcProductDefinitionShape",
|
||||||
|
Name="Alignment Product Definition Shape",
|
||||||
|
Description=None,
|
||||||
|
Representations=(footprint_shape_representation,)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
# create representations for each segment
|
||||||
|
self._create_segment_representations(
|
||||||
|
placement, horizontal_curve_segments, horizontal_segments)
|
||||||
|
|
||||||
|
# add the representation to the alignment
|
||||||
|
alignment.Representation = product_definition_shape
|
||||||
|
|
||||||
|
|
||||||
|
# create referent for start station
|
||||||
|
start_referent = self._file.createIfcReferent(
|
||||||
|
GlobalId=ifcopenshell.guid.new(),
|
||||||
|
OwnerHistory=None,
|
||||||
|
Name="Start Station",
|
||||||
|
Description=None,
|
||||||
|
ObjectType=None,
|
||||||
|
ObjectPlacement=self._file.createIfcLinearPlacement(
|
||||||
|
RelativePlacement=self._file.createIfcAxis2PlacementLinear(
|
||||||
|
Location=self._file.createIfcPointByDistanceExpression(
|
||||||
|
DistanceAlong=self._file.createIfcLengthMeasure(0.),
|
||||||
|
OffsetLateral=None,
|
||||||
|
OffsetVertical=None,
|
||||||
|
OffsetLongitudinal=None,
|
||||||
|
BasisCurve=composite_curve,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CartesianPosition=None,
|
||||||
|
),
|
||||||
|
Representation=None,
|
||||||
|
PredefinedType="STATION",
|
||||||
|
)
|
||||||
|
|
||||||
|
# nest the horizontal and the referent under the alignment
|
||||||
|
nesting_of_alignment = self._file.create_entity(
|
||||||
|
type="IfcRelNests",
|
||||||
|
GlobalId=ifcopenshell.guid.new(),
|
||||||
|
OwnerHistory=None,
|
||||||
|
Name="Nests horizontal alignment and referents under overall alignment",
|
||||||
|
RelatingObject=alignment,
|
||||||
|
RelatedObjects=(horizontal_alignment, start_referent)
|
||||||
|
)
|
||||||
|
|
||||||
|
# aggregate the horizontal under the project
|
||||||
|
project = self._file.by_type("IfcProject")[0]
|
||||||
|
alignment_within_project = self._file.createIfcRelAggregates(
|
||||||
|
GlobalId=ifcopenshell.guid.new(),
|
||||||
|
OwnerHistory=None,
|
||||||
|
Name="Aggregates alignment under the project",
|
||||||
|
RelatingObject=project,
|
||||||
|
RelatedObjects=(alignment, )
|
||||||
|
)
|
||||||
|
|
||||||
|
return alignment
|
||||||
|
|
||||||
|
def add_vertical_alignment(
|
||||||
|
self, name: str, description: str, vpoints: Sequence[Sequence[float]], vclengths: Sequence[Sequence[float]],
|
||||||
|
include_geometry: bool = True
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Create a vertical alignment using the PI layout method.
|
||||||
|
|
||||||
|
@param name: value for Name attribute
|
||||||
|
@param description: value for Description attribute
|
||||||
|
@param vpoints: (distance_along, Z_height) pairs denoting the location of the vertical PIs, including start and end.
|
||||||
|
@param vclengths: radii values to use for transition
|
||||||
|
@param include_geometry: optionally create the alignment geometric representation as well as the semantic business logic
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def add_alignment(self,
|
||||||
|
name: str, hpoints: Sequence[Sequence[float]],
|
||||||
|
radii: Sequence[float],
|
||||||
|
include_geometry: bool = True,
|
||||||
|
description: str = None,
|
||||||
|
start_station: float = 1000.,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Create a new alignment with a horizontal alignment using the PI layout method
|
||||||
|
"""
|
||||||
|
self._add_horizontal_alignment(alignment_name=name, points=hpoints, radii=radii,
|
||||||
|
include_geometry=include_geometry, alignment_description=description,
|
||||||
|
start_station=start_station)
|
||||||
|
|
||||||
|
def save_file(self, filename) -> None:
|
||||||
|
self._file.write(filename)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
import sys
|
||||||
from matplotlib import pyplot as plt
|
from matplotlib import pyplot as plt
|
||||||
|
|
||||||
s = ifcopenshell.express.parse("IFC4x3_RC3.exp")
|
|
||||||
ifcopenshell.register_schema(s)
|
|
||||||
f = ifcopenshell.open(sys.argv[1])
|
f = ifcopenshell.open(sys.argv[1])
|
||||||
print_structure(f.by_type("IfcAlignment")[0])
|
print_structure(f.by_type("IfcAlignment")[0])
|
||||||
|
|
||||||
al_hor = f.by_type("IfcAlignmentHorizontal")[0]
|
al_hor_rep = f.by_type("IfcCompositeCurve")[0]
|
||||||
xy = create_shape({}, al_hor)
|
|
||||||
|
|
||||||
plt.plot(xy.T[0], xy.T[1])
|
xy = generate_vertices(rep_curve=al_hor_rep, distance_interval=10.0)
|
||||||
|
|
||||||
|
plt.plot(xy[0], xy[1])
|
||||||
plt.savefig("horizontal_alignment.png")
|
plt.savefig("horizontal_alignment.png")
|
||||||
|
|||||||
@@ -1,186 +0,0 @@
|
|||||||
# IfcOpenShell - IFC toolkit and geometry engine
|
|
||||||
# Copyright (C) 2021 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 enum import Enum
|
|
||||||
from dataclasses import dataclass
|
|
||||||
import math
|
|
||||||
|
|
||||||
from OCC.Core.gp import gp_Pnt2d
|
|
||||||
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeEdge2d
|
|
||||||
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeWire
|
|
||||||
|
|
||||||
|
|
||||||
class IfcTransitionCurveType(Enum):
|
|
||||||
"""IFC 4.1 Section 8.9.2.9
|
|
||||||
[https://standards.buildingsmart.org/IFC/RELEASE/IFC4_1/FINAL/HTML/schema/ifcgeometryresource/lexical/ifctransitioncurvetype.htm]
|
|
||||||
|
|
||||||
The IfcTransitionCurveType indicates the curvature of a transition curve.
|
|
||||||
"""
|
|
||||||
|
|
||||||
BIQUADRATICPARABOLA = 1 # NOTE also referred to as Schramm curve.
|
|
||||||
BLOSSCURVE = 2
|
|
||||||
CLOTHOIDCURVE = 3
|
|
||||||
COSINECURVE = 4
|
|
||||||
CUBICPARABOLA = 5
|
|
||||||
SINECURVE = 6 # NOTE also referred to as Klein curve
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class TransitionCurve:
|
|
||||||
"""
|
|
||||||
A curve that transitions between a straight line and a circular arc
|
|
||||||
(or the reverse).
|
|
||||||
"""
|
|
||||||
|
|
||||||
StartPoint: tuple # IfcSchema::IfcCartesianPoint
|
|
||||||
StartDirection: float # IfcSchema::IfcPlaneAngleMeasure
|
|
||||||
SegmentLength: float # IfcSchema::IfcPositiveLengthMeasure
|
|
||||||
IsStartRadiusCCW: bool # IfcSchema::IfcBoolean
|
|
||||||
IsEndRadiusCCW: bool # IfcSchema::IfcBoolean
|
|
||||||
TransitionCurveType: IfcTransitionCurveType
|
|
||||||
StartRadius: float = None # IfcSchema::IfcPositiveLengthMeasure
|
|
||||||
EndRadius: float = None # IfcSchema::IfcPositiveLengthMeasure
|
|
||||||
|
|
||||||
def _calc_biquadratic_parabola_point(self, lpt, L, R, ccw):
|
|
||||||
x = lpt
|
|
||||||
if x <= (L / 2):
|
|
||||||
y = x**4 / (6 * R * L**2)
|
|
||||||
else:
|
|
||||||
|
|
||||||
yterm_1 = (-1 * x**4) / (6 * R * L**2)
|
|
||||||
yterm_2 = (2 * x**3) / (3 * R * L)
|
|
||||||
yterm_3 = x**2 / (2 * R)
|
|
||||||
yterm_4 = (L * x) / (6 * R)
|
|
||||||
yterm_5 = L**2 / (48 * R)
|
|
||||||
|
|
||||||
y = yterm_1 + yterm_2 - yterm_3 + yterm_4 - yterm_5
|
|
||||||
|
|
||||||
if not ccw:
|
|
||||||
y = -y
|
|
||||||
|
|
||||||
return gp_Pnt2d(x, y)
|
|
||||||
|
|
||||||
def _calc_bloss_curve_point(self, lpt, L, R, ccw):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _calc_clothoid_curve_point(self, lpt, L, R, ccw):
|
|
||||||
RL = R * L
|
|
||||||
xterm_1 = 1
|
|
||||||
xterm_2 = lpt**4 / (40 * RL**2)
|
|
||||||
xterm_3 = lpt**8 / (3456 * RL**4)
|
|
||||||
xterm_4 = lpt**12 / (599040 * RL**6)
|
|
||||||
x = lpt * (xterm_1 - xterm_2 + xterm_3 - xterm_4)
|
|
||||||
|
|
||||||
factor = lpt**3 / (6 * RL)
|
|
||||||
yterm_1 = 1
|
|
||||||
yterm_2 = lpt**4 / (56 * RL**2)
|
|
||||||
yterm_3 = lpt**8 / (7040 * RL**4)
|
|
||||||
yterm_4 = lpt**12 / (1612800 * RL**6)
|
|
||||||
|
|
||||||
y = factor * (yterm_1 - yterm_2 + yterm_3 - yterm_4)
|
|
||||||
|
|
||||||
if not ccw:
|
|
||||||
y = -y
|
|
||||||
|
|
||||||
return gp_Pnt2d(x, y)
|
|
||||||
|
|
||||||
def _calc_cosine_curve_point(self, lpt, L, R, ccw):
|
|
||||||
pi = math.pi
|
|
||||||
psi_x = (pi * lpt) / L
|
|
||||||
|
|
||||||
xterm_1 = (L**2) / (8.0 * pi**2 * R**2)
|
|
||||||
xterm_2 = L / pi
|
|
||||||
xterm_3 = psi_x**3 / (3.0)
|
|
||||||
xterm_4 = psi_x / (2.0)
|
|
||||||
xterm_5 = (math.sin(psi_x) * math.cos(psi_x)) / (2.0)
|
|
||||||
xterm_6 = psi_x * math.cos(psi_x)
|
|
||||||
|
|
||||||
x = lpt - xterm_1 * xterm_2 * (xterm_3 + xterm_4 - xterm_5 - (2.0 * xterm_6))
|
|
||||||
|
|
||||||
# TODO: code for y - coordinate
|
|
||||||
y = 0
|
|
||||||
|
|
||||||
if not ccw:
|
|
||||||
y = -y
|
|
||||||
|
|
||||||
return gp_Pnt2d(x, y)
|
|
||||||
|
|
||||||
def _calc_cubic_parabola_point(self, lpt, L, R, ccw):
|
|
||||||
|
|
||||||
x = lpt
|
|
||||||
y = math.pow(x, 3) / (6 * R * L)
|
|
||||||
if not ccw:
|
|
||||||
y = -y
|
|
||||||
|
|
||||||
return gp_Pnt2d(x, y)
|
|
||||||
|
|
||||||
def _calc_sine_curve_point(self, lpt, L, R, ccw):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _calc_transition_curve_point(self, lpt, L, R, ccw, trans_type):
|
|
||||||
|
|
||||||
if trans_type == "BIQUADRATICPARABOLA":
|
|
||||||
return self._calc_cubic_parabola_point(lpt, L, R, ccw)
|
|
||||||
elif trans_type == "BLOSSCURVE":
|
|
||||||
# return _calc_bloss_curve_point(lpt, L, R, ccw)
|
|
||||||
raise ValueError(f"Transition Curve type '{trans_type}' not implemented yet.")
|
|
||||||
elif trans_type == "CLOTHOIDCURVE":
|
|
||||||
return self._calc_clothoid_curve_point(lpt, L, R, ccw)
|
|
||||||
elif trans_type == "COSINECURVE":
|
|
||||||
# return _calc_cosine_curve_point(lpt, L, R, ccw)
|
|
||||||
raise ValueError(f"Transition Curve type '{trans_type}' not implemented yet.")
|
|
||||||
elif trans_type == "CUBICPARABOLA":
|
|
||||||
return self._calc_cubic_parabola_point(lpt, L, R, ccw)
|
|
||||||
elif trans_type == "SINECURVE":
|
|
||||||
# return _calc_sine_curve_point(lpt, L, R, ccw)
|
|
||||||
raise ValueError(f"Transition Curve type '{trans_type}' not implemented yet.")
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Invalid Transition Curve type '{trans_type}'.")
|
|
||||||
|
|
||||||
def to_wire(self, stroking_interval=5.0):
|
|
||||||
"""convert IfcTransitionSegment2D to OCC wire
|
|
||||||
|
|
||||||
:param stroking_interval: maximum curve length between points to be calculated
|
|
||||||
:type stroking_interval: float
|
|
||||||
:return: OCC wire containing interpolated points
|
|
||||||
"""
|
|
||||||
points = list()
|
|
||||||
|
|
||||||
L = self.SegmentLength
|
|
||||||
R = self.EndRadius
|
|
||||||
ccw = self.IsStartRadiusCCW
|
|
||||||
trans_type = self.TransitionCurveType.name
|
|
||||||
|
|
||||||
num_intervals = math.ceil(L / stroking_interval)
|
|
||||||
interval_dist = L / num_intervals
|
|
||||||
lpt = 0.0 # length along the curve at the point to be calculated
|
|
||||||
|
|
||||||
for _ in range(num_intervals):
|
|
||||||
points.append(self._calc_transition_curve_point(lpt, L, R, ccw, trans_type))
|
|
||||||
lpt += interval_dist
|
|
||||||
|
|
||||||
edges = list()
|
|
||||||
for i in range(len(points) - 1):
|
|
||||||
edges.append(BRepBuilderAPI_MakeEdge2d(points[i], points[i + 1]))
|
|
||||||
|
|
||||||
wire = BRepBuilderAPI_MakeWire()
|
|
||||||
for e in edges:
|
|
||||||
wire.Add(e.Edge())
|
|
||||||
# return wire
|
|
||||||
return points
|
|
||||||
Reference in New Issue
Block a user