mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
Import alignment from csv (#6234)
* Start of the official alignment API * Import alignment into bonsai model using CSV file
This commit is contained in:
@@ -83,6 +83,7 @@ modules = {
|
||||
"covering": None,
|
||||
"web": None,
|
||||
"light": None,
|
||||
"alignment": None,
|
||||
# Uncomment this line to enable loading of the demo module. Happy hacking!
|
||||
# The name "demo" must correlate to a folder name in `bim/module/`.
|
||||
# "demo": None,
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Bonsai - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of Bonsai.
|
||||
#
|
||||
# Bonsai is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Bonsai 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
|
||||
# from . import ui, prop, operator
|
||||
from . import operator
|
||||
|
||||
classes = (operator.ImportAlignmentCSV,)
|
||||
|
||||
|
||||
def menu_func_import(self, context):
|
||||
self.layout.operator(operator.ImportAlignmentCSV.bl_idname, text="Alignment (.csv)")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
|
||||
@@ -0,0 +1,113 @@
|
||||
# Bonsai - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>, 2022 Yassine Oualid <yassine@sigmadimensions.com>
|
||||
#
|
||||
# This file is part of Bonsai.
|
||||
#
|
||||
# Bonsai is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Bonsai 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# pyright: reportUnnecessaryTypeIgnoreComment=error
|
||||
|
||||
import os
|
||||
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.alignment.add_stationing_to_alignment
|
||||
|
||||
import bpy
|
||||
import json
|
||||
import time
|
||||
import calendar
|
||||
import isodate
|
||||
import bonsai.core.sequence as core
|
||||
import bonsai.tool as tool
|
||||
import bonsai.bim.module.sequence.helper as helper
|
||||
import ifcopenshell.util.sequence
|
||||
import ifcopenshell.util.selector
|
||||
from datetime import datetime
|
||||
from dateutil import parser, relativedelta
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
from typing import get_args, TYPE_CHECKING
|
||||
from typing_extensions import assert_never
|
||||
|
||||
|
||||
class ImportAlignmentCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
bl_idname = "bim.import_alignment_csv"
|
||||
bl_label = "Import Alignment CSV"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".csv"
|
||||
filter_glob: bpy.props.StringProperty(default="*.csv", options={"HIDDEN"})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
ifc_file = tool.Ifc.get()
|
||||
if ifc_file is None:
|
||||
cls.poll_message_set("No IFC file is loaded.")
|
||||
return False
|
||||
elif ifc_file.schema != "IFC4X3":
|
||||
cls.poll_message_set("Schema must be IFC4x3.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
import ifcopenshell.api.alignment
|
||||
|
||||
self.file = tool.Ifc.get()
|
||||
start = time.time()
|
||||
alignment = ifcopenshell.api.alignment.create_alignment_from_csv(self.file, self.filepath)
|
||||
ifcopenshell.api.alignment.create_geometric_representation(self.file, alignment)
|
||||
ifcopenshell.api.alignment.add_stationing_to_alignment(self.file, alignment=alignment, start_station=0.0)
|
||||
|
||||
# IFC 4.1.5.1 alignments cannot be contained in spatial structures, but can be referenced into them
|
||||
sites = self.file.by_type("IfcSite")
|
||||
for site in sites:
|
||||
ifcopenshell.api.spatial.reference_structure(self.file, products=[alignment], relating_structure=site)
|
||||
|
||||
# process the generated IfcReferent for the alignment
|
||||
for rel in alignment.IsNestedBy:
|
||||
for referent in rel.RelatedObjects:
|
||||
if referent.is_a("IfcReferent"):
|
||||
referent_obj = bpy.data.objects.new(tool.Loader.get_name(referent), None)
|
||||
tool.Geometry.link(referent, referent_obj)
|
||||
tool.Collector.assign(referent_obj, should_clean_users_collection=False)
|
||||
|
||||
# an alignment can be an aggregation of multiple child alignments (ie. multiple verticals for a single horizontal)
|
||||
# get all the alignment curves
|
||||
curves = []
|
||||
for rel in alignment.IsDecomposedBy:
|
||||
for agg in rel.RelatedObjects:
|
||||
if agg.is_a("IfcAlignment"):
|
||||
curves.append(ifcopenshell.api.alignment.get_curve(agg)) # 3D curve
|
||||
|
||||
# if there aren't any curves from aggregation, then there is only a single vertical or no vertical
|
||||
if len(curves) == 0:
|
||||
curves.append(ifcopenshell.api.alignment.get_curve(alignment))
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
for curve in curves:
|
||||
shape = ifcopenshell.geom.create_shape(settings, curve)
|
||||
|
||||
# create a new Blender mesh
|
||||
mesh_name = tool.Loader.get_mesh_name_from_shape(shape)
|
||||
mesh = bpy.data.meshes.new(mesh_name)
|
||||
m = tool.Loader.convert_geometry_to_mesh(shape, mesh)
|
||||
|
||||
# create a new Blender object
|
||||
alignment_obj = bpy.data.objects.new(tool.Loader.get_name(alignment), m)
|
||||
|
||||
# link the blender object to with the alignment element
|
||||
tool.Geometry.link(alignment, alignment_obj)
|
||||
|
||||
# assign the object to the blender collections
|
||||
tool.Collector.assign(alignment_obj, should_clean_users_collection=False)
|
||||
|
||||
self.report({"INFO"}, "Imported in %s seconds" % (time.time() - start))
|
||||
@@ -19,6 +19,7 @@
|
||||
# pyright: reportUnnecessaryTypeIgnoreComment=error
|
||||
|
||||
import os
|
||||
|
||||
import bpy
|
||||
import json
|
||||
import time
|
||||
@@ -653,7 +654,6 @@ class DisableEditingWorkCalendar(bpy.types.Operator):
|
||||
core.disable_editing_work_calendar(tool.Sequence)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ImportCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
bl_idname = "bim.import_csv"
|
||||
bl_label = "Import CSV"
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
Road and Rail Alignments
|
||||
========================
|
||||
|
||||
.. Note::
|
||||
|
||||
Bonsai lacks modeling features for road and rail alignments. This feature is intended to be a stop-gap measure to allow alignments
|
||||
to be defined and imported into an IFC model. This feature is most likely temporary and will be phased out as robust alignment
|
||||
modeling capabilities are developed.
|
||||
|
||||
Alignments may be defined by the PI method in a CSV file for import into an IFC4X3 Bonsai project. The format of the CSV file is as follows:
|
||||
|
||||
.. csv-table:: Alignment by PI Method
|
||||
|
||||
"X1","Y1","R1","X2","Y2","R2","...,","Xn-1","Yn-1","Rn-1","Xn","Yn","Rn"
|
||||
"D1","Z1","L1","D2","Z2","L2","...,","Dn-1","Zn-1","Ln-1","Dn","Zn","Ln"
|
||||
"D1","Z1","L1","D2","Z2","L2","...,","Dn-1","Zn-1","Ln-1","Dn","Zn","Ln"
|
||||
|
||||
|
||||
where:
|
||||
Xi,Yi are horizontal alignment PI points
|
||||
Ri are horizontal curve radii.
|
||||
Di,Zi are vertical alignment PI points as Distance_Along,Elevation
|
||||
Li are the horizontal length of parabolic vertical transition curves
|
||||
|
||||
R1 and Rn, as well as L1 and Ln, are placeholder values and should be set to 0.0
|
||||
|
||||
The CSV file must contain exactly one horizontal alignment definition with a minimum of three points.
|
||||
X1,Y1 is the Point of Beginning (POB). Xn,Yn is the Point of Ending (POE).
|
||||
|
||||
The CSV file may contain zero, one or more vertical alignment definitions.
|
||||
|
||||
Alignments with a single horizontal layout and zero or one vertical layout are modeled per `IFC Concept Template 4.1.4.4.1.1, Alignment Layout - Horizontal, Vertical, and Cant, <https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/concepts/Object_Composition/Nesting/Alignment_Layouts/Alignment_Layout_-_Horizontal,_Vertical_and_Cant/content.html>`_. Alignments with multiple vertical layouts are modeled per `IFC Concept Template 4.1.4.4.1.2, Alignment Layout - Reusing Horizontal Layout, <https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/concepts/Object_Composition/Nesting/Alignment_Layouts/Alignment_Layout_-_Reusing_Horizontal_Layout/content.html>`_.
|
||||
|
||||
Example based on the `FHWA Bridge Geometry Manual <https://www.fhwa.dot.gov/bridge/pubs/hif22034.pdf>`_:
|
||||
|
||||
500,2500,0.0,3340,660,1000,4340,5000,1250,7600,4560,950,8480,2010,0
|
||||
0,100,0,2000,135,1600,5000,105,1200,7400,153,2000,9800,105,800,12800,90,0
|
||||
@@ -56,6 +56,7 @@ and data-rich OpenBIM with Blender :)
|
||||
guides/authoring/georeferencing
|
||||
guides/authoring/git_support
|
||||
guides/development/index
|
||||
guides/alignment
|
||||
guides/authoring/other_addons
|
||||
guides/troubleshooting
|
||||
guides/debugging
|
||||
|
||||
@@ -98,3 +98,4 @@ Imports data from external sources into the Blender session or IFC model.
|
||||
- **P6 (.xer)**: Imports a P6 XER file containing a work schedule into the active IFC model.
|
||||
- **Powerproject (.pp)**: Imports a Powerproject file containing a work schedule into the active IFC model.
|
||||
- **Microsoft Project (.xml)**: Imports a Microsoft Project XML file containing a work schedule into the active IFC model.
|
||||
- **Alignment (.csv)**: Imports a CSV containing horizontal and vertical alignments defined by the PI method into the active IFC model.
|
||||
@@ -365,6 +365,12 @@ namespace ifcopenshell {
|
||||
static constexpr bool defaultvalue = false;
|
||||
};
|
||||
|
||||
struct ComputeCurvature : public SettingBase<ComputeCurvature, bool> {
|
||||
static constexpr const char* const name = "compute-curvature";
|
||||
static constexpr const char* const description = "Specifies whether function_item_evaluator.evaluate() computes curvature.";
|
||||
static constexpr bool defaultvalue = false;
|
||||
};
|
||||
|
||||
enum FunctionStepMethod {
|
||||
MAXSTEPSIZE,
|
||||
MINSTEPS };
|
||||
@@ -504,7 +510,7 @@ namespace ifcopenshell {
|
||||
};
|
||||
|
||||
class IFC_GEOM_API Settings : public SettingsContainer<
|
||||
std::tuple<MesherLinearDeflection, MesherAngularDeflection, ReorientShells, LengthUnit, PlaneUnit, Precision, OutputDimensionality, LayersetFirst, DisableBooleanResult, NoWireIntersectionCheck, NoWireIntersectionTolerance, PrecisionFactor, DebugBooleanOperations, BooleanAttempt2d, SurfaceColour, WeldVertices, UseWorldCoords, UnifyShapes, UseMaterialNames, ConvertBackUnits, ContextIds, ContextTypes, ContextIdentifiers, IteratorOutput, DisableOpeningSubtractions, ApplyDefaultMaterials, DontEmitNormals, GenerateUvs, ApplyLayerSets, UseElementHierarchy, ValidateQuantities, EdgeArrows, BuildingLocalPlacement, SiteLocalPlacement, ForceSpaceTransparency, CircleSegments, KeepBoundingBoxes, FunctionStepType, FunctionStepParam, NoParallelMapping, ModelOffset, ModelRotation, TriangulationType, CgalEmitOriginalEdges>
|
||||
std::tuple<MesherLinearDeflection, MesherAngularDeflection, ReorientShells, LengthUnit, PlaneUnit, Precision, OutputDimensionality, LayersetFirst, DisableBooleanResult, NoWireIntersectionCheck, NoWireIntersectionTolerance, PrecisionFactor, DebugBooleanOperations, BooleanAttempt2d, SurfaceColour, WeldVertices, UseWorldCoords, UnifyShapes, UseMaterialNames, ConvertBackUnits, ContextIds, ContextTypes, ContextIdentifiers, IteratorOutput, DisableOpeningSubtractions, ApplyDefaultMaterials, DontEmitNormals, GenerateUvs, ApplyLayerSets, UseElementHierarchy, ValidateQuantities, EdgeArrows, BuildingLocalPlacement, SiteLocalPlacement, ForceSpaceTransparency, CircleSegments, KeepBoundingBoxes, ComputeCurvature, FunctionStepType, FunctionStepParam, NoParallelMapping, ModelOffset, ModelRotation, TriangulationType, CgalEmitOriginalEdges>
|
||||
>
|
||||
{};
|
||||
}
|
||||
|
||||
@@ -5,12 +5,21 @@
|
||||
|
||||
using namespace ifcopenshell::geometry;
|
||||
|
||||
double ifcopenshell::geometry::polynomial_length(double A, double B, double C, double horizontal_length) {
|
||||
auto fn = [A, B, C](double x) -> double { return sqrt(pow(B + 2 * C * x, 2.0) + 1.0); };
|
||||
auto l = boost::math::quadrature::trapezoidal(fn, 0.0, horizontal_length);
|
||||
return l;
|
||||
}
|
||||
std::vector<double> ifcopenshell::geometry::helmert_curve_point(double A0, double A1, double A2, double s) {
|
||||
auto theta = [A0, A1, A2](double t) -> double {
|
||||
auto a0 = A0 ? t / A0 : 0.0;
|
||||
auto a1 = A1 ? A1 * std::pow(t, 2) / (2 * fabs(std::pow(A1, 3))) : 0.0;
|
||||
auto a2 = A2 ? std::pow(t, 3) / (3 * std::pow(A2, 3)) : 0.0;
|
||||
return a0 + a1 + a2;
|
||||
};
|
||||
|
||||
auto fn_x = [theta](double t) -> double { return cos(theta(t)); };
|
||||
auto fn_y = [theta](double t) -> double { return sin(theta(t)); };
|
||||
auto x = boost::math::quadrature::trapezoidal(fn_x, 0.0, s);
|
||||
auto y = boost::math::quadrature::trapezoidal(fn_y, 0.0, s);
|
||||
auto angle = theta(x);
|
||||
return {x, y, angle};
|
||||
}
|
||||
|
||||
struct functor_fn_evaluator : public fn_evaluator {
|
||||
functor_fn_evaluator(taxonomy::functor_item::const_ptr fn, const ifcopenshell::geometry::Settings& settings) : fn_evaluator(settings),
|
||||
@@ -97,12 +106,27 @@ struct gradient_fn_evaluator : public fn_evaluator {
|
||||
auto xy = horizontal_evaluator_.evaluate(u + start_);
|
||||
auto uz = vertical_evaluator_.evaluate(u);
|
||||
|
||||
uz.col(3)(0) = 0.0; // x is distance along. zero it out so it doesn't add to the x from horizontal
|
||||
// curvature is stored in row 3 - capture it and remove it from the xy and uz matrices
|
||||
// so the matrix operations (ie multiplication) works correct.y
|
||||
auto horizontal_curvature = xy.row(3);
|
||||
xy.row(3) = Eigen::Vector4d(0, 0, 0, 1);
|
||||
|
||||
auto vertical_curvature = uz.row(3);
|
||||
uz.row(3) = Eigen::Vector4d(0, 0, 0, 1);
|
||||
|
||||
uz(0, 3) = 0.0; // x is distance along. zero it out so it doesn't add to the x from horizontal
|
||||
uz.col(1).swap(uz.col(2)); // uz is 2D in distance along - y plane, swap y and z so elevations become z
|
||||
uz.row(1).swap(uz.row(2));
|
||||
|
||||
Eigen::Matrix4d m;
|
||||
m = xy * uz; // combine horizontal and vertical
|
||||
|
||||
// Put curvature back into the solution matrix
|
||||
// curvature for vertical is in column 0, need it to be in column 1
|
||||
// so it doesn't add to curvature for horizontal
|
||||
std::swap(vertical_curvature(3, 0), vertical_curvature(3, 1));
|
||||
m.row(3) = horizontal_curvature + vertical_curvature;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
@@ -129,6 +153,15 @@ struct cant_fn_evaluator : public fn_evaluator {
|
||||
auto g = gradient_evaluator_.evaluate(u + start_);
|
||||
auto c = cant_evaluator_.evaluate(u);
|
||||
|
||||
|
||||
// curvature is stored in row 3 - capture it and remove it from the xy and uz matrices
|
||||
// so the matrix operations (ie multiplication) works correctly
|
||||
auto gradient_curvature = g.row(3);
|
||||
g.row(3) = Eigen::Vector4d(0, 0, 0, 1);
|
||||
|
||||
auto cant_curvature = c.row(3);
|
||||
c.row(3) = Eigen::Vector4d(0, 0, 0, 1);
|
||||
|
||||
// Need to multiply g and c so the axis vectors
|
||||
// from cant have the correct rotation applied so
|
||||
// they are relative to the gradient curve coordinate system
|
||||
@@ -155,6 +188,11 @@ struct cant_fn_evaluator : public fn_evaluator {
|
||||
m(1, 3) = y;
|
||||
m(2, 3) = z + s;
|
||||
|
||||
// reinstate values for curvature.
|
||||
// cant_curvature is cant alone. this needs to be combined with gradient in column 3
|
||||
gradient_curvature[3] = gradient_curvature[2] + cant_curvature[3];
|
||||
m.row(3) = gradient_curvature;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
@@ -274,5 +312,9 @@ taxonomy::item::ptr function_item_evaluator::evaluate(const std::vector<double>&
|
||||
}
|
||||
|
||||
Eigen::Matrix4d function_item_evaluator::evaluate(double u) const {
|
||||
return fn_evaluator_->evaluate(u);
|
||||
Eigen::Matrix4d m = fn_evaluator_->evaluate(u);
|
||||
if (!fn_evaluator_->settings_.get<ifcopenshell::geometry::settings::ComputeCurvature>().get()) {
|
||||
m.row(3) = Eigen::Vector4d(0, 0, 0, 1);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
@@ -7,16 +7,9 @@
|
||||
|
||||
namespace ifcopenshell { namespace geometry {
|
||||
|
||||
/// @brief Computes the curve length of a polynomial of the form y = A + Bx + Cx^2
|
||||
/// This function is needed on the python side. To do this computation, a large library like scipy
|
||||
/// is needed. That is too much overhead. For this reason, a simple function is here on the C++ side
|
||||
/// that the python side can call
|
||||
/// @param A constant term
|
||||
/// @param B linear term
|
||||
/// @param C quadradic term
|
||||
/// @param horizontal_length length of the polynomal projected onto the horizontal axis
|
||||
/// @return curve length
|
||||
double polynomial_length(double A, double B, double C,double horizontal_length);
|
||||
/// @brief Computes a point on a helmert curve at s.
|
||||
/// Returns (x,y,theta) at L/2. The results are in a vector so they can be returned to python
|
||||
std::vector<double> helmert_curve_point(double A0, double A1, double A2, double s);
|
||||
|
||||
/// @brief Abstract class for evaluating a function_item. This class is specialized for each of the function_item types.
|
||||
struct fn_evaluator {
|
||||
@@ -66,7 +59,7 @@ class function_item_evaluator {
|
||||
|
||||
/// @brief evaluates the function at u
|
||||
/// @param u u is constrained to be between start_ and start_+length
|
||||
/// @return 4x4 placement matrix
|
||||
/// @return 4x4 placement matrix. Curvature values for horizontal, vertical, and vertical + cant are stored in the last row.
|
||||
Eigen::Matrix4d evaluate(double u) const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -100,18 +100,15 @@ typedef boost::mpl::vector<
|
||||
struct parent_curve_function {
|
||||
parent_curve_function() = default;
|
||||
parent_curve_function(const parent_curve_function&) = default;
|
||||
parent_curve_function(std::function<Eigen::Matrix4d(double)> fn) : fn_(fn) {
|
||||
}
|
||||
|
||||
parent_curve_function& operator=(std::function<Eigen::Matrix4d(double)> fn) {
|
||||
fn_ = fn;
|
||||
return *this;
|
||||
parent_curve_function(std::function<Eigen::Matrix4d(double)> fn, std::function<Eigen::Matrix4d(double)> cfn) : fn_(fn), cfn_(cfn) {
|
||||
}
|
||||
|
||||
virtual Eigen::Matrix4d operator()(double u) const { return fn_(u); }
|
||||
virtual Eigen::Matrix4d curvature(double u) const { return cfn_(u); }
|
||||
|
||||
private:
|
||||
std::function<Eigen::Matrix4d(double)> fn_;
|
||||
std::function<Eigen::Matrix4d(double)> cfn_;
|
||||
};
|
||||
|
||||
struct polynomial_parent_curve : public parent_curve_function {
|
||||
@@ -142,7 +139,7 @@ struct curve_segment_function {
|
||||
Eigen::Matrix4d operator()(double u) const {
|
||||
Eigen::Matrix4d parent_curve_point = (*parent_curve_fn_)(u);
|
||||
Eigen::Matrix4d curve_segment_point = curve_segment_placement_ * remove_parent_curve_rotation_ * remove_parent_curve_translation_ * parent_curve_point;
|
||||
return curve_segment_point;
|
||||
return curve_segment_point + parent_curve_fn_->curvature(u);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -167,7 +164,7 @@ struct cant_curve_segment_function {
|
||||
Eigen::Matrix4d parent_curve_point = (*parent_curve_fn_)(u);
|
||||
Eigen::Matrix4d cant_increment = parent_curve_point - parent_curve_start_point_;
|
||||
Eigen::Matrix4d curve_segment_point = curve_segment_placement_ + cant_increment;
|
||||
return curve_segment_point;
|
||||
return curve_segment_point + parent_curve_fn_->curvature(u);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -246,7 +243,7 @@ class curve_segment_evaluator {
|
||||
Logger::Error(std::runtime_error("multiple uses of IfcSegmentCurve not supported"), inst_);
|
||||
}
|
||||
|
||||
segment_type_ = is_horizontal ? ST_HORIZONTAL : is_vertical ? ST_VERTICAL : ST_CANT;
|
||||
segment_type_ = is_horizontal ? ST_HORIZONTAL : is_vertical ? ST_VERTICAL : is_cant ? ST_CANT : ST_HORIZONTAL;
|
||||
|
||||
|
||||
start_ = translate_if_param_value(inst->ParentCurve(), inst->SegmentStart()) * length_unit;
|
||||
@@ -336,7 +333,7 @@ class curve_segment_evaluator {
|
||||
}
|
||||
}
|
||||
|
||||
void set_spiral_function(double s, std::function<double(double)> fnX, std::function<double(double)> fnY) {
|
||||
void set_spiral_function(double s, std::function<double(double)> fnX, std::function<double(double)> fnY, std::function<double(double)> curvature) {
|
||||
if (segment_type_ == ST_HORIZONTAL || segment_type_ == ST_VERTICAL) {
|
||||
// start of trimmed curve
|
||||
double pcStartX = 0.0, pcStartY = 0.0;
|
||||
@@ -381,24 +378,32 @@ class curve_segment_evaluator {
|
||||
};
|
||||
}
|
||||
|
||||
parent_curve_fn_ = std::make_shared<spiral_parent_curve>([start=start_, s, convert_u, fnX, fnY](double u) {
|
||||
u = convert_u(u+start);
|
||||
parent_curve_fn_ = std::make_shared<spiral_parent_curve>(
|
||||
[start=start_, s, convert_u, fnX, fnY](double u)->Eigen::Matrix4d {
|
||||
u = convert_u(u+start);
|
||||
|
||||
// integration limits, integrate from a to b
|
||||
auto b = s ? u / s : 0.0;
|
||||
// integration limits, integrate from a to b
|
||||
auto b = s ? u / s : 0.0;
|
||||
|
||||
// point on parent curve
|
||||
auto x = boost::math::quadrature::trapezoidal(fnX, 0.0, b);
|
||||
auto y = boost::math::quadrature::trapezoidal(fnY, 0.0, b);
|
||||
auto dx = s ? fnX(b) / s : 1.0;
|
||||
auto dy = s ? fnY(b) / s : 0.0;
|
||||
// point on parent curve
|
||||
auto x = boost::math::quadrature::trapezoidal(fnX, 0.0, b);
|
||||
auto y = boost::math::quadrature::trapezoidal(fnY, 0.0, b);
|
||||
auto dx = s ? fnX(b) / s : 1.0;
|
||||
auto dy = s ? fnY(b) / s : 0.0;
|
||||
|
||||
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
|
||||
m.col(0) = Eigen::Vector4d(dx, dy, 0, 0);
|
||||
m.col(1) = Eigen::Vector4d(-dy, dx, 0, 0);
|
||||
m.col(3) = Eigen::Vector4d(x, y, 0, 1);
|
||||
return m;
|
||||
});
|
||||
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
|
||||
m.col(0) = Eigen::Vector4d(dx, dy, 0, 0);
|
||||
m.col(1) = Eigen::Vector4d(-dy, dx, 0, 0);
|
||||
m.col(3) = Eigen::Vector4d(x, y, 0, 1);
|
||||
return m;
|
||||
},
|
||||
[start = start_, convert_u, curvature](double u) -> Eigen::Matrix4d {
|
||||
u = convert_u(u + start);
|
||||
Eigen::Matrix4d c = Eigen::Matrix4d::Zero();
|
||||
c(3, 0) = curvature(u);
|
||||
return c;
|
||||
}
|
||||
);
|
||||
|
||||
if (segment_type_ == ST_VERTICAL) {
|
||||
// for vertical, the input curve length is measured along the spiral.
|
||||
@@ -428,10 +433,16 @@ class curve_segment_evaluator {
|
||||
}
|
||||
} else if (segment_type_ == ST_CANT) {
|
||||
Logger::Error(std::runtime_error("Unexpected segment type encountered - cant is handled in set_cant_spiral_function - should never get here"));
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>([](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>(
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); },
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); }
|
||||
);
|
||||
} else {
|
||||
Logger::Error(std::runtime_error("Unexpected segment type encountered"));
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>([](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>(
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); },
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,33 +461,40 @@ class curve_segment_evaluator {
|
||||
auto end_cant = Cant(/* start_ + */ length_);
|
||||
auto delta_cant = end_cant - start_cant;
|
||||
|
||||
parent_curve_fn_ = std::make_shared<spiral_parent_curve>([start_angle,delta_angle,start_cant,delta_cant,Superelevation, SuperelevationSlope, Cant](double u) -> Eigen::Matrix4d {
|
||||
// departure of the curve segment from the base curve (superelevation)
|
||||
auto super_elevation = Superelevation(u);
|
||||
auto slope = SuperelevationSlope(u);
|
||||
parent_curve_fn_ = std::make_shared<spiral_parent_curve>(
|
||||
[start_angle,delta_angle,start_cant,delta_cant,Superelevation, SuperelevationSlope, Cant](double u) -> Eigen::Matrix4d {
|
||||
// departure of the curve segment from the base curve (superelevation)
|
||||
auto super_elevation = Superelevation(u);
|
||||
auto slope = SuperelevationSlope(u);
|
||||
|
||||
// direction along curve segment
|
||||
auto angle = atan(slope);
|
||||
auto dx = cos(angle);
|
||||
auto dy = sin(angle);
|
||||
Eigen::Vector4d ref_dir(dx, dy, 0.0, 0.0);
|
||||
// direction along curve segment
|
||||
auto angle = atan(slope);
|
||||
auto dx = cos(angle);
|
||||
auto dy = sin(angle);
|
||||
Eigen::Vector4d ref_dir(dx, dy, 0.0, 0.0);
|
||||
|
||||
// tilt angle in the plane of the cross section
|
||||
auto cant = Cant(u);
|
||||
auto tilt_angle = start_angle + delta_angle * (cant - start_cant) / delta_cant;
|
||||
Eigen::Vector4d z(0.0, cos(tilt_angle), sin(tilt_angle), 0.0);
|
||||
// tilt angle in the plane of the cross section
|
||||
auto cant = Cant(u);
|
||||
auto tilt_angle = start_angle + delta_angle * (cant - start_cant) / delta_cant;
|
||||
Eigen::Vector4d z(0.0, cos(tilt_angle), sin(tilt_angle), 0.0);
|
||||
|
||||
// compute axis direction
|
||||
Eigen::Vector4d y = z.cross3(ref_dir);
|
||||
Eigen::Vector4d axis = ref_dir.cross3(y);
|
||||
// compute axis direction
|
||||
Eigen::Vector4d y = z.cross3(ref_dir);
|
||||
Eigen::Vector4d axis = ref_dir.cross3(y);
|
||||
|
||||
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
|
||||
m.col(0) = ref_dir;
|
||||
m.col(1) = y;
|
||||
m.col(2) = axis;
|
||||
m.col(3) = Eigen::Vector4d(u, super_elevation, 0.0, 1.0);
|
||||
return m;
|
||||
});
|
||||
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
|
||||
m.col(0) = ref_dir;
|
||||
m.col(1) = y;
|
||||
m.col(2) = axis;
|
||||
m.col(3) = Eigen::Vector4d(u, super_elevation, 0.0, 1.0);
|
||||
return m;
|
||||
},
|
||||
[Cant](double u) -> Eigen::Matrix4d {
|
||||
Eigen::Matrix4d c = Eigen::Matrix4d::Zero();
|
||||
c(3, 0) = Cant(u);
|
||||
return c;
|
||||
}
|
||||
);
|
||||
|
||||
parent_curve_start_point_ = (*parent_curve_fn_)(0.0);
|
||||
}
|
||||
@@ -526,7 +544,8 @@ class curve_segment_evaluator {
|
||||
auto s = fabs(A * sqrt(PI)); // curve length when u = 1.0
|
||||
auto fn_x = [A, s](double t) -> double { return A ? s * cos(PI * A * t * t / (2 * fabs(A))) : 0.0; };
|
||||
auto fn_y = [A, s](double t) -> double { return A ? s * sin(PI * A * t * t / (2 * fabs(A))) : 0.0; };
|
||||
set_spiral_function(s, fn_x, fn_y);
|
||||
auto curvature = [A](double t) -> double { return A ? A * t / fabs(A * A * A) : 0.0; };
|
||||
set_spiral_function(s, fn_x, fn_y, curvature);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -548,8 +567,13 @@ class curve_segment_evaluator {
|
||||
};
|
||||
auto fn_x = [theta](double t) -> double { return cos(theta(t)); };
|
||||
auto fn_y = [theta](double t) -> double { return sin(theta(t)); };
|
||||
auto curvature = [constant_term, cosine_term, L](double t) -> double {
|
||||
auto a0 = constant_term.has_value() ? L / constant_term.value() : 0.0;
|
||||
auto a1 = (L / cosine_term) * cos((PI / L) * t);
|
||||
return a0 + a1;
|
||||
};
|
||||
double s = 1.0;
|
||||
set_spiral_function(s, fn_x, fn_y);
|
||||
set_spiral_function(s, fn_x, fn_y, curvature);
|
||||
} else if (segment_type_ == ST_CANT) {
|
||||
boost::optional<std::function<double(double)>> super, slope;
|
||||
std::tie(super, slope) = get_superelevation_functions();
|
||||
@@ -574,10 +598,16 @@ class curve_segment_evaluator {
|
||||
set_cant_spiral_function(*super, *slope, cant);
|
||||
} else if (segment_type_ == ST_VERTICAL) {
|
||||
Logger::Error(std::runtime_error("IfcCosineSpiral cannot be used for vertical alignment"));
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>([](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>(
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); },
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); }
|
||||
);
|
||||
} else {
|
||||
Logger::Error(std::runtime_error("Unexpected segment type encountered"));
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>([](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>(
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); },
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); }
|
||||
);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -603,8 +633,14 @@ class curve_segment_evaluator {
|
||||
};
|
||||
auto fn_x = [theta](double t) -> double { return cos(theta(t)); };
|
||||
auto fn_y = [theta](double t) -> double { return sin(theta(t)); };
|
||||
auto curvature = [constant_term, linear_term, sine_term, L](double t) -> double {
|
||||
auto a0 = constant_term.has_value() ? L / constant_term.value() : 0.0;
|
||||
auto a1 = linear_term.has_value() ? sign(linear_term.value()) * pow(L / linear_term.value(), 2.0)*(t/L) : 0.0;
|
||||
auto a2 = (L / sine_term) * sin(2 * PI * t / L);
|
||||
return a0 + a1 + a2;
|
||||
};
|
||||
double s = 1.0;
|
||||
set_spiral_function(s, fn_x, fn_y);
|
||||
set_spiral_function(s, fn_x, fn_y, curvature);
|
||||
} else if (segment_type_ == ST_CANT) {
|
||||
boost::optional<std::function<double(double)>> super, slope;
|
||||
std::tie(super, slope) = get_superelevation_functions();
|
||||
@@ -631,16 +667,20 @@ class curve_segment_evaluator {
|
||||
set_cant_spiral_function(*super, *slope, cant);
|
||||
} else if (segment_type_ == ST_VERTICAL) {
|
||||
Logger::Error(std::runtime_error("IfcSineSpiral cannot be used for vertical alignment"));
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>([](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>(
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); },
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
} else {
|
||||
Logger::Error(std::runtime_error("Unexpected segment type encountered"));
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>([](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>(
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); },
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void polynomial_spiral(boost::optional<double> A0, boost::optional<double> A1, boost::optional<double> A2, boost::optional<double> A3, boost::optional<double> A4, boost::optional<double> A5, boost::optional<double> A6, boost::optional<double> A7) {
|
||||
auto theta = [A0, A1, A2, A3, A4, A5, A6, A7, start = start_ * length_unit_, lu = length_unit_](double t) {
|
||||
auto theta = [A0, A1, A2, A3, A4, A5, A6, A7, start = start_ * length_unit_, lu = length_unit_](double t) -> double {
|
||||
auto a0 = A0.has_value() ? t / (A0.value() * lu) : 0.0;
|
||||
auto a1 = A1.has_value() ? A1.value() * lu * std::pow(t, 2) / (2 * fabs(std::pow(A1.value() * lu, 3))) : 0.0;
|
||||
auto a2 = A2.has_value() ? std::pow(t, 3) / (3 * std::pow(A2.value() * lu, 3)) : 0.0;
|
||||
@@ -655,15 +695,31 @@ class curve_segment_evaluator {
|
||||
auto fn_x = [theta](double t) -> double { return cos(theta(t)); };
|
||||
auto fn_y = [theta](double t) -> double { return sin(theta(t)); };
|
||||
|
||||
|
||||
// this is same as cant function in polynomial_cant_spiral
|
||||
auto curvature = [A0, A1, A2, A3, A4, A5, A6, A7, start = start_, L = length_, lu = length_unit_, length = length_](double t) -> double {
|
||||
t += start;
|
||||
auto a0 = A0.has_value() ? 1 / (A0.value() * lu) : 0.0;
|
||||
auto a1 = A1.has_value() ? A1.value() * lu * t / fabs(std::pow(A1.value() * lu, 3)) : 0.0;
|
||||
auto a2 = A2.has_value() ? std::pow(t, 2) / std::pow(A2.value() * lu, 3) : 0.0;
|
||||
auto a3 = A3.has_value() ? A3.value() * lu * std::pow(t, 3) / fabs(std::pow(A3.value() * lu, 5)) : 0.0;
|
||||
auto a4 = A4.has_value() ? std::pow(t, 4) / std::pow(A4.value() * lu, 5) : 0.0;
|
||||
auto a5 = A5.has_value() ? A5.value() * lu * std::pow(t, 5) / fabs(std::pow(A5.value() * lu, 7)) : 0.0;
|
||||
auto a6 = A6.has_value() ? std::pow(t, 6) / std::pow(A6.value() * lu, 7) : 0.0;
|
||||
auto a7 = A7.has_value() ? A7.value() * lu * std::pow(t, 7) / fabs(std::pow(A7.value() * lu, 9)) : 0.0;
|
||||
return L * (a0 + a1 + a2 + a3 + a4 + a5 + a6 + a7);
|
||||
};
|
||||
|
||||
|
||||
double s = 1.0;
|
||||
set_spiral_function(s, fn_x, fn_y);
|
||||
set_spiral_function(s, fn_x, fn_y, curvature);
|
||||
}
|
||||
|
||||
void polynomial_cant_spiral(boost::optional<double> A0, boost::optional<double> A1, boost::optional<double> A2, boost::optional<double> A3, boost::optional<double> A4, boost::optional<double> A5, boost::optional<double> A6, boost::optional<double> A7) {
|
||||
boost::optional<std::function<double(double)>> super, slope;
|
||||
std::tie(super, slope) = get_superelevation_functions();
|
||||
|
||||
auto cant = [A0, A1, A2, A3, A4, A5, A6, A7, start = start_, L = length_, lu = length_unit_, length = length_](double t) {
|
||||
auto cant = [A0, A1, A2, A3, A4, A5, A6, A7, start = start_, L = length_, lu = length_unit_, length = length_](double t) -> double {
|
||||
t += start;
|
||||
auto a0 = A0.has_value() ? 1 / (A0.value() * lu) : 0.0;
|
||||
auto a1 = A1.has_value() ? A1.value() * lu * t / fabs(std::pow(A1.value() * lu, 3)) : 0.0;
|
||||
@@ -681,7 +737,7 @@ class curve_segment_evaluator {
|
||||
}
|
||||
|
||||
if (!slope.has_value()) {
|
||||
slope = [A1, A2, A3, A4, A5, A6, A7, start = start_, L = length_, lu = length_unit_, length = length_](double t) {
|
||||
slope = [A1, A2, A3, A4, A5, A6, A7, start = start_, L = length_, lu = length_unit_, length = length_](double t) -> double {
|
||||
t += start;
|
||||
auto a1 = A1.has_value() ? A1.value() * lu / fabs(std::pow(A1.value() * lu, 3)) : 0.0;
|
||||
auto a2 = A2.has_value() ? 2 * t / std::pow(A2.value() * lu, 3) : 0.0;
|
||||
@@ -813,29 +869,36 @@ class curve_segment_evaluator {
|
||||
};
|
||||
}
|
||||
|
||||
parent_curve_fn_ = std::make_shared<circle_parent_curve>([segment_type = segment_type_, R, pcCenterX, pcCenterY, start_angle, sign_l, convert_u](double u) {
|
||||
u = convert_u(u);
|
||||
parent_curve_fn_ = std::make_shared<circle_parent_curve>(
|
||||
[segment_type = segment_type_, R, pcCenterX, pcCenterY, start_angle, sign_l, convert_u](double u)->Eigen::Matrix4d {
|
||||
u = convert_u(u);
|
||||
|
||||
// u is measured along the circle
|
||||
// angle from the X=0 axis to the current point
|
||||
auto delta = R ? sign_l * u / R : 0.0;
|
||||
auto sweep_angle = start_angle + delta;
|
||||
auto cos_sweep_angle = cos(sweep_angle);
|
||||
auto sin_sweep_angle = sin(sweep_angle);
|
||||
// u is measured along the circle
|
||||
// angle from the X=0 axis to the current point
|
||||
auto delta = R ? sign_l * u / R : 0.0;
|
||||
auto sweep_angle = start_angle + delta;
|
||||
auto cos_sweep_angle = cos(sweep_angle);
|
||||
auto sin_sweep_angle = sin(sweep_angle);
|
||||
|
||||
// point on the parent curve
|
||||
auto pcX = R * cos_sweep_angle + pcCenterX;
|
||||
auto pcY = R * sin_sweep_angle + pcCenterY;
|
||||
// point on the parent curve
|
||||
auto pcX = R * cos_sweep_angle + pcCenterX;
|
||||
auto pcY = R * sin_sweep_angle + pcCenterY;
|
||||
|
||||
auto pcDx = -sign_l * sin_sweep_angle;
|
||||
auto pcDy = sign_l * cos_sweep_angle;
|
||||
auto pcDx = -sign_l * sin_sweep_angle;
|
||||
auto pcDy = sign_l * cos_sweep_angle;
|
||||
|
||||
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
|
||||
m.col(0) = Eigen::Vector4d(pcDx, pcDy, 0, 0);
|
||||
m.col(1) = Eigen::Vector4d(-pcDy, pcDx, 0, 0);
|
||||
m.col(3) = Eigen::Vector4d(pcX, pcY, 0.0, 1.0);
|
||||
return m;
|
||||
});
|
||||
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
|
||||
m.col(0) = Eigen::Vector4d(pcDx, pcDy, 0, 0);
|
||||
m.col(1) = Eigen::Vector4d(-pcDy, pcDx, 0, 0);
|
||||
m.col(3) = Eigen::Vector4d(pcX, pcY, 0.0, 1.0);
|
||||
return m;
|
||||
},
|
||||
[R](double) -> Eigen::Matrix4d {
|
||||
Eigen::Matrix4d c = Eigen::Matrix4d::Zero();
|
||||
c(3, 0) = 1 / R;
|
||||
return c;
|
||||
}
|
||||
);
|
||||
|
||||
if (segment_type_ == ST_HORIZONTAL) {
|
||||
parent_curve_start_point_ = (*parent_curve_fn_)(start_);
|
||||
@@ -865,10 +928,14 @@ class curve_segment_evaluator {
|
||||
|
||||
} else if (segment_type_ == ST_CANT) {
|
||||
Logger::Warning(std::runtime_error("Use of IfcCircle for cant is not supported"));
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>([](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>(
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); },
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
} else {
|
||||
Logger::Error(std::runtime_error("Unexpected segment type encountered"));
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>([](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>(
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); },
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -916,23 +983,32 @@ class curve_segment_evaluator {
|
||||
convert_u = [pcDx](double u) { return u/pcDx; };
|
||||
}
|
||||
|
||||
parent_curve_fn_ = std::make_shared<line_parent_curve>([pcX, pcY, pcDx, pcDy, convert_u](double u) {
|
||||
u = convert_u(u);
|
||||
parent_curve_fn_ = std::make_shared<line_parent_curve>(
|
||||
[pcX, pcY, pcDx, pcDy, convert_u](double u)->Eigen::Matrix4d {
|
||||
u = convert_u(u);
|
||||
|
||||
auto x = pcX + pcDx * u;
|
||||
auto y = pcY + pcDy * u;
|
||||
auto x = pcX + pcDx * u;
|
||||
auto y = pcY + pcDy * u;
|
||||
|
||||
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
|
||||
m.col(0) = Eigen::Vector4d(pcDx, pcDy, 0, 0);
|
||||
m.col(1) = Eigen::Vector4d(-pcDy, pcDx, 0, 0);
|
||||
m.col(3) = Eigen::Vector4d(x, y, 0.0, 1.0);
|
||||
return m;
|
||||
});
|
||||
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
|
||||
m.col(0) = Eigen::Vector4d(pcDx, pcDy, 0, 0);
|
||||
m.col(1) = Eigen::Vector4d(-pcDy, pcDx, 0, 0);
|
||||
m.col(3) = Eigen::Vector4d(x, y, 0.0, 1.0);
|
||||
return m;
|
||||
},
|
||||
[](double /*u*/) -> Eigen::Matrix4d {
|
||||
// curvature is zero for a line. identity initializes c(3,0) = 0
|
||||
Eigen::Matrix4d c = Eigen::Matrix4d::Zero();
|
||||
return c;
|
||||
}
|
||||
);
|
||||
|
||||
parent_curve_start_point_ = (*parent_curve_fn_)(start_);
|
||||
} else {
|
||||
Logger::Warning(std::runtime_error("Unexpected segment type encountered"));
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>([](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
Logger::Warning(std::runtime_error("Unexpected segment type encountered"));
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>(
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); },
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1022,50 +1098,62 @@ class curve_segment_evaluator {
|
||||
}
|
||||
|
||||
// This functor evaluates the polynomial at a distance u along the curve
|
||||
parent_curve_fn_ = std::make_shared<polynomial_parent_curve>([start = start_, lu = length_unit_, coeffX, coeffY, convert_u](double u)->Eigen::Matrix4d {
|
||||
auto x = convert_u(u + start); // find x for u
|
||||
// evaluate the polynomial at x
|
||||
std::array<const std::vector<double>*, 2> coefficients{&coeffX, &coeffY};
|
||||
std::array<double, 2> position{0.0, 0.0}; // = SUM(coeff*u^pos)
|
||||
std::array<double, 2> slope{0.0, 0.0}; // slope is derivative of the curve = SUM( coeff*pos*u^(pos-1) )
|
||||
for (int i = 0; i < 2; i++) { // loop over X and Y
|
||||
auto begin = coefficients[i]->cbegin();
|
||||
auto end = coefficients[i]->cend();
|
||||
for (auto iter = begin; iter != end; iter++) {
|
||||
auto exp = std::distance(begin, iter);
|
||||
auto coeff = (*iter);
|
||||
position[i] += coeff * pow(lu, 1-exp) * pow(x, exp);
|
||||
parent_curve_fn_ = std::make_shared<polynomial_parent_curve>(
|
||||
[start = start_, lu = length_unit_, coeffX, coeffY, convert_u](double u)->Eigen::Matrix4d {
|
||||
auto x = convert_u(u + start); // find x for u
|
||||
// evaluate the polynomial at x
|
||||
std::array<const std::vector<double>*, 2> coefficients{&coeffX, &coeffY};
|
||||
std::array<double, 2> position{0.0, 0.0}; // = SUM(coeff*u^pos)
|
||||
std::array<double, 2> slope{0.0, 0.0}; // slope is derivative of the curve = SUM( coeff*pos*u^(pos-1) )
|
||||
for (int i = 0; i < 2; i++) { // loop over X and Y
|
||||
auto begin = coefficients[i]->cbegin();
|
||||
auto end = coefficients[i]->cend();
|
||||
for (auto iter = begin; iter != end; iter++) {
|
||||
auto exp = std::distance(begin, iter);
|
||||
auto coeff = (*iter);
|
||||
position[i] += coeff * pow(lu, 1-exp) * pow(x, exp);
|
||||
|
||||
if (iter != begin) {
|
||||
slope[i] += exp * coeff * pow(lu, 1-exp) * pow(x, exp - 1);
|
||||
}
|
||||
}
|
||||
if (iter != begin) {
|
||||
slope[i] += exp * coeff * pow(lu, 1-exp) * pow(x, exp - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto X = position[0];
|
||||
auto Y = position[1];
|
||||
|
||||
auto Dx = slope[0];
|
||||
auto Dy = slope[1];
|
||||
|
||||
auto angle = atan2(Dy, Dx);
|
||||
Dx = cos(angle);
|
||||
Dy = sin(angle);
|
||||
|
||||
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
|
||||
m.col(0) = Eigen::Vector4d(Dx, Dy, 0, 0);
|
||||
m.col(1) = Eigen::Vector4d(-Dy, Dx, 0, 0);
|
||||
m.col(3) = Eigen::Vector4d(X, Y, 0.0, 1.0);
|
||||
return m;
|
||||
},
|
||||
[start = start_, lu = length_unit_, coeffX, coeffY, convert_u](double u) -> Eigen::Matrix4d {
|
||||
auto x = convert_u(u + start); // find x for u
|
||||
Eigen::Matrix4d c = Eigen::Matrix4d::Zero();
|
||||
c(3, 0) = coeffY[2]; // this may need a unit conversion (also assume there is only 3 coefficients)
|
||||
return c;
|
||||
}
|
||||
|
||||
auto X = position[0];
|
||||
auto Y = position[1];
|
||||
|
||||
auto Dx = slope[0];
|
||||
auto Dy = slope[1];
|
||||
|
||||
auto angle = atan2(Dy, Dx);
|
||||
Dx = cos(angle);
|
||||
Dy = sin(angle);
|
||||
|
||||
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
|
||||
m.col(0) = Eigen::Vector4d(Dx, Dy, 0, 0);
|
||||
m.col(1) = Eigen::Vector4d(-Dy, Dx, 0, 0);
|
||||
m.col(3) = Eigen::Vector4d(X, Y, 0.0, 1.0);
|
||||
return m;
|
||||
});
|
||||
);
|
||||
|
||||
parent_curve_start_point_ = (*parent_curve_fn_)(0.0); // start is added to u in parent_curve_fn_, so use 0.0 here
|
||||
} else if (segment_type_ == ST_CANT) {
|
||||
Logger::Warning(std::runtime_error("Use of IfcPolynomialCurve for cant is not supported"));
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>([](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>(
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); },
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
} else {
|
||||
Logger::Error(std::runtime_error("Unexpected segment type encountered"));
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>([](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
parent_curve_fn_ = std::make_shared<parent_curve_function>(
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); },
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,7 +39,12 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcOffsetCurveByDistances* inst
|
||||
auto first_offset_value = *(offset_values->begin());
|
||||
|
||||
auto basis_curve = inst->BasisCurve();
|
||||
auto curve = taxonomy::dcast<taxonomy::piecewise_function>(map(basis_curve));
|
||||
auto curve = taxonomy::dcast<taxonomy::function_item>(map(basis_curve));
|
||||
if (!curve) {
|
||||
// Only implement on alignment curves
|
||||
Logger::Warning("IfcOffsetCurveByDistances is only implemented for BasisCurves curves based on taxonomy::function_item", inst);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
double start = curve->start();
|
||||
double basis_curve_length = curve->length();
|
||||
|
||||
@@ -43,7 +43,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSectionedSolidHorizontal* in
|
||||
auto csps = inst->CrossSectionPositions();
|
||||
std::vector<taxonomy::face::ptr> faces;
|
||||
|
||||
// The PointByDistanceExpressesions are factored out into (a) a cartesian offset relative to the
|
||||
// The PointByDistanceExpressions are factored out into (a) a cartesian offset relative to the
|
||||
// reference frame along a certain curve location (b) the longitude.
|
||||
|
||||
// The longitudes determine the range of the sweep and the offsets are interpolated in between
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.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/>.
|
||||
|
||||
"""
|
||||
Manages alignment layout (business logical) and alignment geometry (geometric representations).
|
||||
|
||||
This API does not determine alignment parameters based on rules, such as minimum curve radius as a function of design speed or sight distance.
|
||||
|
||||
This API is under development and subject to code breaking changes in the future.
|
||||
|
||||
Presently, this API supports:
|
||||
1. Creating alignments, both horizontal and vertical, using the PI method. Alignment definition can be read from a CSV file.
|
||||
2. Adding business logic and geometric segments to the end of an alignment
|
||||
3. Adding and removing the zero length segment at the end of alignments
|
||||
4. Creating geometric representations from a business logical definition
|
||||
5. Mapping individual business logical segments to geometric segments (complete for horizontal, missing clothoid for vertical, not implemented for cant)
|
||||
6. Using curve geometry to determine IfcCurveSegment.Transition transition code.
|
||||
7. Utility functions for printing business logical and geometric representations, as well as minimumal geometry evaluations
|
||||
|
||||
Future versions of this API will support:
|
||||
1. Defining alignments using the PI method, including transition spirals
|
||||
2. Updating horizontal curve definitions by revising transition spiral parameters and circular curve radii
|
||||
3. Updating vertical curve definitions by revising horizontal length of curves
|
||||
4. Removing a segment at any location along a curve
|
||||
5. Adding a segment at any location along a curve
|
||||
"""
|
||||
|
||||
from .add_segment_to_curve import add_segment_to_curve
|
||||
from .add_segment_to_layout import add_segment_to_layout
|
||||
from .add_stationing_to_alignment import add_stationing_to_alignment
|
||||
from .add_vertical_alignment_by_pi_method import add_vertical_alignment_by_pi_method
|
||||
from .add_vertical_alignment import add_vertical_alignment
|
||||
from .add_zero_length_segment import add_zero_length_segment
|
||||
from .create_alignment_by_pi_method import create_alignment_by_pi_method
|
||||
from .create_alignment_from_csv import create_alignment_from_csv
|
||||
from .create_horizontal_alignment_by_pi_method import create_horizontal_alignment_by_pi_method
|
||||
from .create_geometric_representation import create_geometric_representation
|
||||
from .create_vertical_alignment_by_pi_method import create_vertical_alignment_by_pi_method
|
||||
from .get_alignment_layouts import get_alignment_layouts
|
||||
from .get_axis_subcontext import get_axis_subcontext
|
||||
from .get_basis_curve import get_basis_curve
|
||||
from .get_child_alignments import get_child_alignments
|
||||
from .get_curve import get_curve
|
||||
from .get_parent_alignment import get_parent_alignment
|
||||
from .has_zero_length_segment import has_zero_length_segment
|
||||
from .map_alignment_segments import map_alignment_segments
|
||||
from .map_alignment_segment import map_alignment_segment
|
||||
from .map_alignment_horizontal_segment import map_alignment_horizontal_segment
|
||||
from .map_alignment_vertical_segment import map_alignment_vertical_segment
|
||||
from .map_alignment_cant_segment import map_alignment_cant_segment
|
||||
from .name_segments import name_segments
|
||||
from .remove_last_segment import remove_last_segment
|
||||
from .remove_zero_length_segment import remove_zero_length_segment
|
||||
from .update_curve_segment_transition_code import update_curve_segment_transition_code
|
||||
from .util import *
|
||||
@@ -0,0 +1,76 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.geom
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def add_segment_to_curve(file: ifcopenshell.file, segment: entity_instance, composite_curve: entity_instance) -> None:
|
||||
"""
|
||||
Adds a segment to a composite curve. The segment must not belong to another composite curve (len(segment.UsingCurves) == 0).
|
||||
If the composite curve does not have any segments, the segment is simply appended to the curve.
|
||||
If the composite curve has segments, the position, ref. direction, and curvature at the end of the last segment is
|
||||
compared to the position, ref. direction and curvature at the start of the new segment. The IfcCurveSegment.Transition of the last curve segment is updated.
|
||||
|
||||
:param segment: The segment to be added to the curve
|
||||
:param composite_curve: The curve receiving the segment
|
||||
:return: None
|
||||
"""
|
||||
expected_type = "IfcCurveSegment"
|
||||
if not segment.is_a(expected_type):
|
||||
raise TypeError(f"Expected to see '{expected_type}', instead received '{segment.is_a()}'.")
|
||||
|
||||
if 0 < len(segment.UsingCurves):
|
||||
raise TypeError("IfcCurveSegment cannot belong to other curves")
|
||||
|
||||
expected_type = "IfcCompositeCurve"
|
||||
if not composite_curve.is_a(expected_type):
|
||||
raise TypeError(f"Expected to see '{expected_type}', instead received '{composite_curve.is_a()}'.")
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
if composite_curve.Segments == None or 0 == len(composite_curve.Segments):
|
||||
# this is the first segment so just add it
|
||||
if composite_curve.Segments == None:
|
||||
composite_curve.Segments = []
|
||||
|
||||
# the last segment is always discontinuous
|
||||
segment.Transition = "DISCONTINUOUS"
|
||||
|
||||
composite_curve.Segments += (segment,)
|
||||
assert len(segment.UsingCurves) == 1
|
||||
else:
|
||||
zero_length_segment = (
|
||||
ifcopenshell.api.alignment.remove_zero_length_segment(file, composite_curve)
|
||||
if ifcopenshell.api.alignment.has_zero_length_segment(composite_curve)
|
||||
else None
|
||||
)
|
||||
|
||||
prev_segment = composite_curve.Segments[-1]
|
||||
|
||||
# the last segment is always discontinuous
|
||||
segment.Transition = "DISCONTINUOUS"
|
||||
|
||||
# must add the new segment to the curve before updating the transition code
|
||||
composite_curve.Segments += (segment,)
|
||||
|
||||
ifcopenshell.api.alignment.update_curve_segment_transition_code(prev_segment, segment)
|
||||
|
||||
if zero_length_segment:
|
||||
ifcopenshell.api.alignment.add_segment_to_curve(zero_length_segment, composite_curve)
|
||||
@@ -0,0 +1,52 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.nest
|
||||
from ifcopenshell import entity_instance
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def add_segment_to_layout(file: ifcopenshell.file, alignment: entity_instance, segment: entity_instance) -> None:
|
||||
"""
|
||||
Adds a segment to a layout alignment (horizontal, vertical, or cant)
|
||||
|
||||
:param alignment: The alignment
|
||||
:param segment: The segment to be appended
|
||||
:return: None
|
||||
"""
|
||||
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
|
||||
if not alignment.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{alignment.is_a()}"
|
||||
)
|
||||
|
||||
if not (segment.is_a("IfcAlignmentSegment")):
|
||||
raise TypeError(f"Expected to see IfcAlignmentSegment, instead received '{segment.is_a()}.")
|
||||
|
||||
zero_length_segment = (
|
||||
ifcopenshell.api.alignment.remove_zero_length_segment(file, alignment)
|
||||
if ifcopenshell.api.alignment.has_zero_length_segment(alignment)
|
||||
else None
|
||||
)
|
||||
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=[segment], relating_object=alignment)
|
||||
|
||||
if zero_length_segment:
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=[zero_length_segment], relating_object=alignment)
|
||||
@@ -0,0 +1,79 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.guid
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def add_stationing_to_alignment(file: ifcopenshell.file, alignment: entity_instance, start_station: float) -> None:
|
||||
"""
|
||||
Adds stationing to an alignment by creating an IfcReferent with the Pset_Stationing property set to establish the stationing at the start of the alignment.
|
||||
Note - this function assumes the stationing has not been previously defined
|
||||
|
||||
:param alignment: the alignment to be stationed
|
||||
:param start_station: station value at the start of the alignment
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
alignment = model.by_type("IfcAlignment")[0]
|
||||
ifcopenshell.api.alignment.add_stationing_to_alignment(model,alignment=alignment,start_station=100.0)
|
||||
"""
|
||||
# this commented out code is what you would do to add a geometric representation of the referent
|
||||
# the example is a circle. a better way would be to pass a representation into the function
|
||||
object_placement = None
|
||||
representation = None
|
||||
# basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
# if basis_curve:
|
||||
# object_placement = file.createIfcLinearPlacement(
|
||||
# RelativePlacement=file.createIfcAxis2PlacementLinear(
|
||||
# Location=file.createIfcPointByDistanceExpression(
|
||||
# DistanceAlong=file.createIfcLengthMeasure(0.0),
|
||||
# OffsetLateral=None,
|
||||
# OffsetVertical=None,
|
||||
# OffsetLongitudinal=None,
|
||||
# BasisCurve=basis_curve,
|
||||
# )
|
||||
# ),
|
||||
# CartesianPosition=None,
|
||||
# )
|
||||
# representation = file.create_entity(
|
||||
# name="IfcCircle",
|
||||
# position=file.createIfcAxis2Placement2D(Location=file.createIfcCartesianPoint(Coordinates=(0.0, 0.0)),
|
||||
# radius=1.0)
|
||||
# )
|
||||
|
||||
# create referent for start station
|
||||
start_referent = file.createIfcReferent(
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=ifcopenshell.util.stationing.station_as_string(start_station),
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=object_placement,
|
||||
Representation=representation,
|
||||
PredefinedType="STATION",
|
||||
)
|
||||
pset_stationing = ifcopenshell.api.pset.add_pset(file, product=start_referent, name="Pset_Stationing")
|
||||
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties={"Station": start_station})
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=[start_referent], relating_object=alignment)
|
||||
@@ -0,0 +1,198 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.stationing
|
||||
import ifcopenshell.api
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def _move_vertical_to_child_alignment(
|
||||
file: ifcopenshell.file, parent_alignment: entity_instance, vertical_alignment: entity_instance
|
||||
):
|
||||
"""
|
||||
Creates a new child alignment and aggregates it to the parent alignment. Moves the vertical alignment from the parent
|
||||
alignment to the child alignment. Also moves the "Axis/Curve3D" representation to the child alignment, if present.
|
||||
This function supports the transition of vertical alignment between CT 4.1.4.4.1.1 and 4.1.4.4.1.2 because a subsequent
|
||||
vertical alignment is being added and the Alignment Layout - Reusing Horizontal Layout concept applies.
|
||||
"""
|
||||
# unhook the vertical alignment from the parent alignment
|
||||
ifcopenshell.api.nest.unassign_object(file, related_objects=[vertical_alignment])
|
||||
|
||||
# create the child alignment
|
||||
child_alignment = ifcopenshell.api.root.create_entity(
|
||||
file, ifc_class="IfcAlignment", name=f"Child of {parent_alignment.Name}"
|
||||
)
|
||||
|
||||
# nest the vertical alignment onto the child alignment
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=[vertical_alignment], relating_object=child_alignment)
|
||||
|
||||
# aggreage the child alignment to the parent alignment
|
||||
ifcopenshell.api.aggregate.assign_object(file, products=[child_alignment], relating_object=parent_alignment)
|
||||
|
||||
# if the parent alignment has a representation, move the Axis/Curve3D represention to the child alignment
|
||||
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
|
||||
if base_curve:
|
||||
representations = ifcopenshell.util.representation.get_representations_iter(parent_alignment)
|
||||
for representation in representations:
|
||||
if representation.RepresentationIdentifier == "Axis" and representation.RepresentationType == "Curve3D":
|
||||
ifcopenshell.api.geometry.unassign_representation(file, parent_alignment, representation)
|
||||
ifcopenshell.api.geometry.assign_representation(file, child_alignment, representation)
|
||||
break
|
||||
|
||||
|
||||
def add_vertical_alignment(
|
||||
file: ifcopenshell.file, parent_alignment: entity_instance, vertical_alignment: entity_instance
|
||||
) -> None:
|
||||
"""
|
||||
Adds a vertical alignment to a previously created alignment.
|
||||
|
||||
If this is the first vertical alignment assigned to the parent_alignment the IFC CT 4.1.4.4.1.1 Alignment Layout - Horizontal, Vertical and Cant
|
||||
is followed. If this is the second or subsequent vertical alignment assigned to the parent_alignment the
|
||||
IFC CT 4.1.4.4.1.2 Alignment Layout - Reusing Horizontal Layout is followed.
|
||||
|
||||
When the second vertical alignment is added, the structure of the IFC model must transition from one concept template to the other.
|
||||
Specifically, the following occurs:
|
||||
|
||||
1) The first child IfcAlignment is created and is IfcRelAggregates with the parent alignment.
|
||||
2) The first vertical alignment is unassigned from the IfcRelNests of the parent alignment and assigned to the new child alignment IfcRelNests
|
||||
3) A second child IfcAlignment is created ant is is IfcRelAggregates with the parent alignment.
|
||||
4) The vertical_alignment is assigned to the second child alignment
|
||||
|
||||
For the third and subsequent vertical alignments, a new child alignment is created and aggregated to the parent alignment and an IfcAlignmentVertical is created
|
||||
from vpoints and lengths and assigned to the new child alignment.
|
||||
|
||||
If the parent_alignment has a geometric representation, a geometric representation will be created for the vertical alignment.
|
||||
|
||||
:param parent_alignment: The parent alignment
|
||||
:param vertical_alignment: The vertical alignment to be added
|
||||
:return: None
|
||||
"""
|
||||
|
||||
# get all the child alignments under alignment
|
||||
child_alignments = [
|
||||
c for c in ifcopenshell.util.element.get_decomposition(parent_alignment) if c.is_a("IfcAlignment")
|
||||
]
|
||||
|
||||
# Get all the IfcAlignmentVertical that are nesting alignment (there should be 0 or 1)
|
||||
# if 0, alignment is just horizontal and we are adding the first vertical so it will nest to the alignment,
|
||||
# or there are multiple vertical and they nest to the aggregated child alignments
|
||||
# if 1, there is one vertical alignments. Move it to a child alignment
|
||||
vertical_alignments_nesting_alignment = [
|
||||
c for c in ifcopenshell.util.element.get_components(parent_alignment) if c.is_a("IfcAlignmentVertical")
|
||||
]
|
||||
|
||||
# move the vertical alignment to a child alignment because there is going to be more than one vertical
|
||||
assert len(vertical_alignments_nesting_alignment) == 0 or len(vertical_alignments_nesting_alignment) == 1
|
||||
for vertical_alignment_nesting_alignment in vertical_alignments_nesting_alignment:
|
||||
_move_vertical_to_child_alignment(file, parent_alignment, vertical_alignment_nesting_alignment)
|
||||
|
||||
if len(child_alignments) == 0 and len(vertical_alignments_nesting_alignment) == 0:
|
||||
# this is the first vertical alignment so nest it into the parent alignment (IFC CT 4.1.4.4.1.1)
|
||||
ifcopenshell.api.nest.assign_object(
|
||||
file, related_objects=[vertical_alignment], relating_object=parent_alignment
|
||||
)
|
||||
|
||||
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
|
||||
if base_curve:
|
||||
# the parent alignment has a Representation so create a representation for the vertical
|
||||
gradient_curve = file.create_entity(
|
||||
type="IfcGradientCurve", Segments=[], SelfIntersect=False, BaseCurve=base_curve, EndPoint=None
|
||||
)
|
||||
|
||||
# using the business logic definition of vertical_alignment, create the curve segments and assign to gradient_curve
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, vertical_alignment, gradient_curve)
|
||||
|
||||
# Per IFC CT 4.1.7.1.1.1, the shape representation for Horizontal geometry only is
|
||||
# RepresentationIdentifier="Axis" and RepresentationType="Curve2D".
|
||||
# However, per IFC CT 4.1.7.1.1.2 and 3 the shape represenation with Horizontal, Vertical and Cant
|
||||
# is RepresentationIdentifier="FootPrint" and RepresentationType="Curve2D" for the horizontal and
|
||||
# RepresentationIdentifier="Axis" and RepresentationType="Curve3D" for the 2.5D curve.
|
||||
# Since the alignment is transitioning from horizontal only to horizontal+vertical, the
|
||||
# RepresentationIdentifier must change from "Axis" to "FootPrint"
|
||||
representations = ifcopenshell.util.representation.get_representations_iter(parent_alignment)
|
||||
for representation in representations:
|
||||
if representation.RepresentationIdentifier == "Axis" and representation.RepresentationType == "Curve2D":
|
||||
representation.RepresentationIdentifier = "FootPrint"
|
||||
break
|
||||
|
||||
# create the Axis,Curve3D representation
|
||||
axis_geom_subcontext = ifcopenshell.api.alignment.get_axis_subcontext(file)
|
||||
axis3d_shape_representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="Axis",
|
||||
RepresentationType="Curve3D",
|
||||
Items=(gradient_curve,),
|
||||
)
|
||||
|
||||
ifcopenshell.api.geometry.assign_representation(file, parent_alignment, axis3d_shape_representation)
|
||||
else:
|
||||
# there are multiple vertical reusing the horizontal (IFC CT 4.1.4.4.1.2)
|
||||
# this is the second or subsequent vertical reusing the horizontal
|
||||
|
||||
# create a new child alignment for the new vertical
|
||||
child_alignment = file.create_entity(
|
||||
type="IfcAlignment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=f"Child of {parent_alignment.Name}",
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
PredefinedType=None,
|
||||
)
|
||||
|
||||
# Aggregate the child alignment to the parent alignment
|
||||
ifcopenshell.api.aggregate.assign_object(file, (child_alignment,), parent_alignment)
|
||||
|
||||
# nest the vertical under the child alignment
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=[vertical_alignment], relating_object=child_alignment)
|
||||
|
||||
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
|
||||
if base_curve:
|
||||
child_alignment.ObjectPlacement = parent_alignment.ObjectPlacement
|
||||
|
||||
# the parent alignment has a Representation so create a representation for the vertical
|
||||
gradient_curve = file.create_entity(
|
||||
type="IfcGradientCurve", Segments=[], SelfIntersect=False, BaseCurve=base_curve, EndPoint=None
|
||||
)
|
||||
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, vertical_alignment, gradient_curve)
|
||||
|
||||
axis_geom_subcontext = ifcopenshell.api.alignment.get_axis_subcontext(file)
|
||||
|
||||
# create the Curve3D representation
|
||||
axis3d_shape_representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="Axis",
|
||||
RepresentationType="Curve3D",
|
||||
Items=(gradient_curve,),
|
||||
)
|
||||
|
||||
# add the representation to the child alignment
|
||||
ifcopenshell.api.geometry.assign_representation(file, child_alignment, axis3d_shape_representation)
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment.add_vertical_alignment
|
||||
from ifcopenshell import entity_instance
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def add_vertical_alignment_by_pi_method(
|
||||
file: ifcopenshell.file,
|
||||
parent_alignment: entity_instance,
|
||||
vpoints: Sequence[Sequence[float]],
|
||||
lengths: Sequence[float],
|
||||
) -> None:
|
||||
"""
|
||||
Adds a vertical alignment to a previously created alignment using the PI method.
|
||||
|
||||
If this is the first vertical alignment assigned to the parent_alignment the IFC CT 4.1.4.4.1.1 Alignment Layout - Horizontal, Vertical and Cant
|
||||
is followed. If this is the second or subsequent vertical alignment assigned to the parent_alignment the
|
||||
IFC CT 4.1.4.4.1.2 Alignment Layout - Reusing Horizontal Layout is followed.
|
||||
|
||||
When the second vertical alignment is added, the structure of the IFC model must transition from one concept template to the other.
|
||||
Specifically, the following occurs:
|
||||
|
||||
1) The first child IfcAlignment is created and is IfcRelAggregates with the parent alignment.
|
||||
2) The first vertical alignment is unassigned from the IfcRelNests of the parent alignment and assigned to the new child alignment IfcRelNests
|
||||
3) A second child IfcAlignment is created and it is IfcRelAggregates with the parent alignment.
|
||||
4) An IfcAlignmentVertical is created from vpoints and lengths and it is assigned to the second child alignment
|
||||
|
||||
For the third and subsequent vertical alignments, a new child alignment is created and aggregated to the parent alignment and an IfcAlignmentVertical is created
|
||||
from vpoints and lengths and assigned to the new child alignment.
|
||||
|
||||
If the parent_alignment has a geometric representation, a geometric representation will be created for the vertical alignment.
|
||||
|
||||
:param parent_alignment: The parent alignment
|
||||
:param vpoints: A sequence of (D,Z) points where D is distance along horizontal and Z is elevation
|
||||
:param: lengths: Lengths of parabolic vertical curves occuring at each VPI
|
||||
:return: None
|
||||
"""
|
||||
vertical_alignment = ifcopenshell.api.alignment.create_vertical_alignment_by_pi_method(
|
||||
file, parent_alignment.Name, vpoints, lengths
|
||||
)
|
||||
ifcopenshell.api.alignment.add_vertical_alignment(file, parent_alignment, vertical_alignment)
|
||||
@@ -0,0 +1,101 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def add_zero_length_segment(file: ifcopenshell.file, entity: entity_instance) -> None:
|
||||
"""
|
||||
Adds a zero length segment to the end of entity.
|
||||
|
||||
:param entity: An IfcAlignmentHorizontal, IfcAlignmentVertical, IfcAlignmentCant or IfcCompositeCurve (or subtype)
|
||||
:return: None
|
||||
"""
|
||||
expected_types = [
|
||||
"IfcAlignmentHorizontal",
|
||||
"IfcAlignmentVertical",
|
||||
"IfcAlignmentCant",
|
||||
"IfcCompositeCurve",
|
||||
"IfcGradientCurve",
|
||||
"IfcSegmentedReferenceCurve",
|
||||
]
|
||||
if not entity.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{entity.is_a()}"
|
||||
)
|
||||
|
||||
if entity.is_a("IfcCompositeCurve"):
|
||||
last_segment = entity.Segments[-1]
|
||||
settings = ifcopenshell.geom.settings()
|
||||
segment_fn = ifcopenshell_wrapper.map_shape(settings, last_segment.wrapped_data)
|
||||
segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, segment_fn)
|
||||
e = segment_evaluator.evaluate(segment_fn.end())
|
||||
end = np.array(e)
|
||||
x = float(end[0, 3])
|
||||
y = float(end[1, 3])
|
||||
dx = float(end[0, 0])
|
||||
dy = float(end[1, 0])
|
||||
|
||||
parent_curve = file.createIfcLine(
|
||||
Pnt=file.createIfcCartesianPoint(Coordinates=((0.0, 0.0))),
|
||||
Dir=file.createIfcVector(
|
||||
Orientation=file.createIfcDirection(DirectionRatios=((1.0, 0.0))),
|
||||
Magnitude=1.0,
|
||||
),
|
||||
)
|
||||
curve_segment = file.createIfcCurveSegment(
|
||||
Transition="DISCONTINUOUS",
|
||||
Placement=file.createIfcAxis2Placement2D(
|
||||
Location=file.createIfcCartesianPoint(Coordinates=((x, y))),
|
||||
RefDirection=file.createIfcDirection((dx, dy)),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(0.0),
|
||||
ParentCurve=parent_curve,
|
||||
)
|
||||
ifcopenshell.api.alignment.add_segment_to_curve(file, curve_segment, entity)
|
||||
else:
|
||||
for rel in entity.IsNestedBy:
|
||||
if 0 < len(rel.RelatedObjects):
|
||||
last_segment = rel.RelatedObjects[-1]
|
||||
if last_segment.is_a("IfcAlignmentSegment"):
|
||||
design_parameters = file.createIfcAlignmentHorizontalSegment(
|
||||
StartPoint=file.createIfcCartesianPoint(
|
||||
(0.0, 0.0)
|
||||
), # this is a little problematic. need to know the end point and tangent
|
||||
StartDirection=0.0, # of the previous segment, which requires geometry mapping
|
||||
SegmentLength=0.0,
|
||||
PredefinedType="LINE",
|
||||
)
|
||||
segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
ifcopenshell.api.nest.assign_object(
|
||||
file,
|
||||
related_objects=[
|
||||
segment,
|
||||
],
|
||||
relating_object=entity,
|
||||
)
|
||||
break
|
||||
@@ -0,0 +1,80 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell import entity_instance
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def create_alignment_by_pi_method(
|
||||
file: ifcopenshell.file,
|
||||
alignment_name: str,
|
||||
hpoints: Sequence[Sequence[float]],
|
||||
radii: Sequence[float],
|
||||
vpoints: Sequence[Sequence[float]] = None,
|
||||
lengths: Sequence[float] = None,
|
||||
alignment_description: str = None,
|
||||
) -> entity_instance:
|
||||
"""
|
||||
Create an alignment using the PI layout method for both horizontal and vertical alignments.
|
||||
If vpoints and lengths are omitted, only a horizontal alignment is created. Only the business logic
|
||||
entities are creaed. Use create_geometric_representation() to create the geometric entities.
|
||||
|
||||
:param alignment_name: value for Name attribute
|
||||
:param points: (X,Y) pairs denoting the location of the horizontal PIs, including start and end
|
||||
:param radii: radii values to use for transition
|
||||
:param vpoints: (distance_along, Z_height) pairs denoting the location of the vertical PIs, including start and end.
|
||||
:param lengths: parabolic vertical curve horizontal length values to use for transition
|
||||
:param alignment_description: value for Description attribute
|
||||
:return: Returns an IfcAlignment
|
||||
"""
|
||||
alignments = []
|
||||
|
||||
horizontal_alignment = ifcopenshell.api.alignment.create_horizontal_alignment_by_pi_method(
|
||||
file, alignment_name, hpoints, radii
|
||||
)
|
||||
alignments.append(horizontal_alignment)
|
||||
|
||||
if vpoints and lengths:
|
||||
vertical_alignment = ifcopenshell.api.alignment.create_vertical_alignment_by_pi_method(
|
||||
file, alignment_name, vpoints, lengths
|
||||
)
|
||||
alignments.append(vertical_alignment)
|
||||
|
||||
# create the alignment
|
||||
alignment = file.create_entity(
|
||||
type="IfcAlignment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=alignment_name,
|
||||
Description=alignment_description,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
PredefinedType=None,
|
||||
)
|
||||
|
||||
# nest the horizontal and vertical under the alignment
|
||||
ifcopenshell.api.nest.assign_object(file, related_objects=alignments, relating_object=alignment)
|
||||
|
||||
# IFC 4.1.4.1.1 Alignment Aggregation To Project
|
||||
project = file.by_type("IfcProject")[0]
|
||||
ifcopenshell.api.aggregate.assign_object(file, products=[alignment], relating_object=project)
|
||||
|
||||
return alignment
|
||||
@@ -0,0 +1,116 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.stationing
|
||||
import ifcopenshell.api
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell.api.alignment import get_axis_subcontext
|
||||
|
||||
import math
|
||||
from typing import Sequence
|
||||
|
||||
import csv
|
||||
|
||||
|
||||
def create_alignment_from_csv(file: ifcopenshell.file, filepath: str) -> entity_instance:
|
||||
"""
|
||||
Creates an alignment from PI data stored in a CSV file. Only the business logic
|
||||
entities are creaed. Use create_geometric_representation() to create the geometric entities.
|
||||
|
||||
The format of the file is:
|
||||
|
||||
X1,Y1,R1,X2,Y2,R2 ... Xn-1,Yn-1,Rn-1,Xn,Yn
|
||||
|
||||
D1,Z1,L1,D2,Z2,L2 ... Dn-1,Zn-1,Ln-1,Dn,Zn
|
||||
|
||||
D1,Z1,L1,D2,Z2,L2 ... Dn-1,Zn-1,Ln-1,Dn,Zn
|
||||
|
||||
...
|
||||
|
||||
where:
|
||||
X,Y are PI coordinates
|
||||
|
||||
R is the horizontal circular curve radius
|
||||
|
||||
D,Z are VPI coordinates as "Distance Along","Elevation"
|
||||
|
||||
L is the horizontal length of a parabolic vertical transition curve
|
||||
|
||||
R1 and Rn, as well as L1 and Ln are placeholders and not used. They are recommended to have values of 0.0.
|
||||
|
||||
R2 and Rn-2 are the radii of the first and last horizontal curves.
|
||||
|
||||
L2 and Ln-2 are the length of the first and last vertical curves.
|
||||
|
||||
The CSV file contains one horizontal alignment, zero, one, or more vertical alignments
|
||||
|
||||
:param filepath: path the to CSV file
|
||||
:return: IfcAlignment
|
||||
"""
|
||||
with open(filepath, newline="") as csvfile:
|
||||
reader = csv.reader(csvfile)
|
||||
row_count = 0
|
||||
for row in reader:
|
||||
data = list(map(float, row)) # Convert all values to float
|
||||
coordinates: list[list[float]] = (
|
||||
[]
|
||||
) # horizontal coordinates for first row, vertical coordinates for subsequent rows
|
||||
radii: list[float] = [] # horizontal curve radii for first row, vertical curve length for subsequent rows
|
||||
|
||||
row_count += 1
|
||||
|
||||
i = 0
|
||||
while i < len(data):
|
||||
if i + 1 < len(data):
|
||||
x, y = float(data[i]), float(data[i + 1])
|
||||
coordinates.append((x, y)) # Store (X, Y) pair
|
||||
i += 2
|
||||
if i < len(data) and (i + 1) % 3 == 0: # Every third element after an (X,Y) pair is R
|
||||
radii.append(data[i])
|
||||
i += 1
|
||||
|
||||
radii = radii[1:-1] # The first radius value is a placeholder, remove it
|
||||
|
||||
if row_count == 1:
|
||||
# create the alignment
|
||||
alignment = file.createIfcAlignment(GlobalId=ifcopenshell.guid.new())
|
||||
# create the horizontal alignment
|
||||
horizontal_alignment = ifcopenshell.api.alignment.create_horizontal_alignment_by_pi_method(
|
||||
file, "Alignment_from_CSV", coordinates, radii
|
||||
)
|
||||
# nest them together
|
||||
ifcopenshell.api.nest.assign_object(
|
||||
file, related_objects=(horizontal_alignment,), relating_object=alignment
|
||||
)
|
||||
else:
|
||||
# add all subsequent vertical alignments
|
||||
ifcopenshell.api.alignment.add_vertical_alignment_by_pi_method(file, alignment, coordinates, radii)
|
||||
|
||||
# IFC 4.1.4.1.1 Alignment Aggregation To Project
|
||||
project = file.by_type("IfcProject")[0]
|
||||
ifcopenshell.api.aggregate.assign_object(file, products=[alignment], relating_object=project)
|
||||
|
||||
return alignment
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
import math
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def create_geometric_representation(file: ifcopenshell.file, alignment: entity_instance) -> None:
|
||||
"""
|
||||
Create geometric representation for the alignment.
|
||||
|
||||
There are 5 different cases:
|
||||
|
||||
1) Horizontal only
|
||||
2) Horizontal + Vertical
|
||||
3) Horizontal + Vertical + Cant
|
||||
4) Vertical only (this occurs when horizontal is reused from a parent alignment)
|
||||
5) Vertical + Cant (this occurs when horizontal is reused from a parent alignment)
|
||||
|
||||
:param alignment: The alignment for which the representation is being created
|
||||
:return: None
|
||||
"""
|
||||
|
||||
expected_type = "IfcAlignment"
|
||||
if not alignment.is_a(expected_type):
|
||||
raise TypeError("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)
|
||||
|
||||
layouts = ifcopenshell.api.alignment.get_alignment_layouts(alignment)
|
||||
children = ifcopenshell.api.alignment.get_child_alignments(alignment)
|
||||
|
||||
if len(layouts) == 1 and len(children) == 0:
|
||||
assert layouts[0].is_a("IfcAlignmentHorizontal")
|
||||
# Horizontal only - IFC CT 4.1.7.1.1.1
|
||||
composite_curve = file.createIfcCompositeCurve()
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[0], composite_curve)
|
||||
representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="Axis",
|
||||
RepresentationType="Curve2D",
|
||||
Items=(composite_curve,),
|
||||
)
|
||||
ifcopenshell.api.geometry.assign_representation(file, alignment, representation)
|
||||
elif len(layouts) == 2 and len(children) == 0:
|
||||
# Horizontal and Vertical - IFC CT 4.1.7.1.1.1
|
||||
assert layouts[0].is_a("IfcAlignmentHorizontal")
|
||||
assert layouts[1].is_a("IfcAlignmentVertical")
|
||||
composite_curve = file.createIfcCompositeCurve()
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[0], composite_curve)
|
||||
representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="FootPrint",
|
||||
RepresentationType="Curve2D",
|
||||
Items=(composite_curve,),
|
||||
)
|
||||
ifcopenshell.api.geometry.assign_representation(file, alignment, representation)
|
||||
|
||||
gradient_curve = file.createIfcGradientCurve(BaseCurve=composite_curve)
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[1], gradient_curve)
|
||||
representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="Axis",
|
||||
RepresentationType="Curve3D",
|
||||
Items=(gradient_curve,),
|
||||
)
|
||||
ifcopenshell.api.geometry.assign_representation(file, alignment, representation)
|
||||
elif len(layouts) == 3 and len(children) == 0:
|
||||
# Horizontal, Vertical, and Cant - IFC CT 4.1.7.1.1.3
|
||||
assert layouts[0].is_a("IfcAlignmentHorizontal")
|
||||
assert layouts[1].is_a("IfcAlignmentVertical")
|
||||
assert layouts[2].is_a("IfcAlignmentCant")
|
||||
composite_curve = file.createIfcCompositeCurve()
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[0], composite_curve)
|
||||
representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="FootPrint",
|
||||
RepresentationType="Curve2D",
|
||||
Items=(composite_curve,),
|
||||
)
|
||||
ifcopenshell.api.geometry.assign_representation(file, alignment, representation)
|
||||
|
||||
gradient_curve = file.createIfcGradientCurve(BaseCurve=composite_curve)
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[1], gradient_curve)
|
||||
segmented_reference_curve = file.createIfcSegmentedReferenceCurve(BaseCurve=gradient_curve)
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[2], segmented_reference_curve)
|
||||
representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="Axis",
|
||||
RepresentationType="Curve3D",
|
||||
Items=(segmented_reference_curve,),
|
||||
)
|
||||
ifcopenshell.api.geometry.assign_representation(file, alignment, representation)
|
||||
else:
|
||||
# Reusing Horizontal - CT 4.1.4.4.1.2
|
||||
# Create a representation on the parent alignment
|
||||
composite_curve = file.createIfcCompositeCurve()
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[0], composite_curve)
|
||||
representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="FootPrint",
|
||||
RepresentationType="Curve2D",
|
||||
Items=(composite_curve,),
|
||||
)
|
||||
ifcopenshell.api.geometry.assign_representation(file, alignment, representation)
|
||||
|
||||
for child_alignment in children:
|
||||
child_alignment.ObjectPlacement = placement
|
||||
child_layouts = ifcopenshell.api.alignment.get_alignment_layouts(child_alignment)
|
||||
if len(child_layouts) == 1:
|
||||
assert child_layouts[0].is_a("IfcAlignmentVertical")
|
||||
base_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
gradient_curve = file.createIfcGradientCurve(BaseCurve=base_curve)
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, child_layouts[0], gradient_curve)
|
||||
representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="Axis",
|
||||
RepresentationType="Curve3D",
|
||||
Items=(gradient_curve,),
|
||||
)
|
||||
ifcopenshell.api.geometry.assign_representation(file, child_alignment, representation)
|
||||
elif len(child_layouts) == 2:
|
||||
assert child_layouts[0].is_a("IfcAlignmentVertical")
|
||||
assert child_layouts[1].is_a("IfcAlignmentCant")
|
||||
base_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
gradient_curve = file.createIfcGradientCurve(BaseCurve=base_curve)
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, child_layouts[0], gradient_curve)
|
||||
segmented_reference_curve = file.createIfcSegmentedReferenceCurve(BaseCurve=gradient_curve)
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, child_layouts[1], segmented_reference_curve)
|
||||
representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="Axis",
|
||||
RepresentationType="Curve3D",
|
||||
Items=(segmented_reference_curve,),
|
||||
)
|
||||
ifcopenshell.api.geometry.assign_representation(file, child_alignment, representation)
|
||||
else:
|
||||
assert False # should never get here - can't have more than one vertical and cant in a child alignment
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
import math
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def create_horizontal_alignment_by_pi_method(
|
||||
file: ifcopenshell.file, name: str, hpoints: Sequence[Sequence[float]], radii: Sequence[float]
|
||||
) -> entity_instance:
|
||||
"""
|
||||
Create a horizontal alignment using the PI layout method.
|
||||
|
||||
:param name: value for Name attribute
|
||||
:param hpoints: (X, Y) pairs denoting the location of the horizontal PIs, including start (POB) and end (POE).
|
||||
:param radii: radius values to use for transition
|
||||
:return: Returns a IfcAlignmentHorizontal
|
||||
"""
|
||||
if not (len(hpoints) - 2 == len(radii)):
|
||||
raise ValueError("radii should have two fewer elements that hpoints")
|
||||
|
||||
# Create the horizontal alignment (IfcAlignmentHorizontal) and nest alignment segments
|
||||
horizontal_alignment = file.create_entity(
|
||||
type="IfcAlignmentHorizontal",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=f"{name} - Horizontal",
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
)
|
||||
|
||||
xBT, yBT = hpoints[0]
|
||||
xPI, yPI = hpoints[1]
|
||||
|
||||
i = 1
|
||||
|
||||
for radius in radii:
|
||||
# back tangent
|
||||
dxBT = xPI - xBT
|
||||
dyBT = yPI - yBT
|
||||
angleBT = math.atan2(dyBT, dxBT)
|
||||
lengthBT = math.sqrt(dxBT * dxBT + dyBT * dyBT)
|
||||
|
||||
# forward tangent
|
||||
i += 1
|
||||
xFT, yFT = hpoints[i]
|
||||
dxFT = xFT - xPI
|
||||
dyFT = yFT - yPI
|
||||
angleFT = math.atan2(dyFT, dxFT)
|
||||
|
||||
delta = angleFT - angleBT
|
||||
|
||||
tangent = abs(radius * math.tan(delta / 2))
|
||||
|
||||
lc = abs(radius * delta)
|
||||
|
||||
radius *= delta / abs(delta)
|
||||
|
||||
xPC = xPI - tangent * math.cos(angleBT)
|
||||
yPC = yPI - tangent * math.sin(angleBT)
|
||||
|
||||
xPT = xPI + tangent * math.cos(angleFT)
|
||||
yPT = yPI + tangent * math.sin(angleFT)
|
||||
|
||||
tangent_run = lengthBT - tangent
|
||||
|
||||
# create back tangent run
|
||||
pt = file.create_entity(
|
||||
type="IfcCartesianPoint",
|
||||
Coordinates=(xBT, yBT),
|
||||
)
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentHorizontalSegment",
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartPoint=pt,
|
||||
StartDirection=angleBT,
|
||||
StartRadiusOfCurvature=0.0,
|
||||
EndRadiusOfCurvature=0.0,
|
||||
SegmentLength=tangent_run,
|
||||
GravityCenterLineHeight=None,
|
||||
PredefinedType="LINE",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, horizontal_alignment, alignment_segment)
|
||||
|
||||
# create circular curve
|
||||
if radius != 0.0:
|
||||
pc = file.create_entity(
|
||||
type="IfcCartesianPoint",
|
||||
Coordinates=(xPC, yPC),
|
||||
)
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentHorizontalSegment",
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartPoint=pc,
|
||||
StartDirection=angleBT,
|
||||
StartRadiusOfCurvature=float(radius),
|
||||
EndRadiusOfCurvature=float(radius),
|
||||
SegmentLength=lc,
|
||||
GravityCenterLineHeight=None,
|
||||
PredefinedType="CIRCULARARC",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, horizontal_alignment, alignment_segment)
|
||||
|
||||
xBT = xPT
|
||||
yBT = yPT
|
||||
xPI = xFT
|
||||
yPI = yFT
|
||||
|
||||
# done processing radii
|
||||
# create last tangent run
|
||||
dx = xPI - xBT
|
||||
dy = yPI - yBT
|
||||
angleBT = math.atan2(dy, dx)
|
||||
tangent_run = math.sqrt(dx * dx + dy * dy)
|
||||
pt = file.create_entity(type="IfcCartesianPoint", Coordinates=(xBT, yBT))
|
||||
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentHorizontalSegment",
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartPoint=pt,
|
||||
StartDirection=angleBT,
|
||||
StartRadiusOfCurvature=0.0,
|
||||
EndRadiusOfCurvature=0.0,
|
||||
SegmentLength=tangent_run,
|
||||
GravityCenterLineHeight=None,
|
||||
PredefinedType="LINE",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, horizontal_alignment, alignment_segment)
|
||||
|
||||
# create zero length terminator segment
|
||||
poe = file.create_entity(type="IfcCartesianPoint", Coordinates=(xPI, yPI))
|
||||
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentHorizontalSegment",
|
||||
StartTag="POE",
|
||||
EndTag="POE",
|
||||
StartPoint=poe,
|
||||
StartDirection=angleBT,
|
||||
StartRadiusOfCurvature=0.0,
|
||||
EndRadiusOfCurvature=0.0,
|
||||
SegmentLength=0.0,
|
||||
GravityCenterLineHeight=None,
|
||||
PredefinedType="LINE",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, horizontal_alignment, alignment_segment)
|
||||
|
||||
return horizontal_alignment
|
||||
@@ -0,0 +1,76 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell import ifcopenshell_wrapper
|
||||
import math
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def create_segment_representations(
|
||||
file: ifcopenshell.file,
|
||||
alignment: entity_instance,
|
||||
) -> None:
|
||||
"""
|
||||
Creates curve segment representations for the alignment for IFC CT 4.1.7.1.1.4. The alignment is expected to have representations
|
||||
for "Axis/Curve2D" (horizontal only) or "FootPrint/Curve2D" and "Axis/Curve3D" (horizontal + vertical/cant). There is the additional
|
||||
expectation that there is a 1-to-1 relationship between IfcAlignmentSegment and IfcCurveSegment.
|
||||
That is, no Helmert curves in the alignment which have a 1-to-2 relationship
|
||||
|
||||
:param alignment: The alignment to create segment representations.
|
||||
"""
|
||||
expected_type = "IfcAlignment"
|
||||
if not alignment.is_a(expected_type):
|
||||
raise TypeError(f"Expected to see type '{expected_type}', instead received '{alignment.is_a()}'.")
|
||||
|
||||
axis_geom_subcontext = ifcopenshell.api.alignment.get_axis_subcontext(file)
|
||||
representations = ifcopenshell.util.representation.get_representations_iter(alignment)
|
||||
for representation in representations:
|
||||
curve = None
|
||||
nested_alignment = None
|
||||
if (representation.RepresentationIdentifier == "Axis" and representation.RepresentationType == "Curve2D") or (
|
||||
representation.RepresentationIdentifier == "FootPrint" and representation.RepresentationType == "Curve2D"
|
||||
):
|
||||
curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
nested_alignment = [
|
||||
c for c in ifcopenshell.util.element.get_components(alignment) if c.is_a("IfcAlignmentHorizontal")
|
||||
][0]
|
||||
elif representation.RepresentationIdentifier == "Axis" and representation.RepresentationType == "Curve3D":
|
||||
curve = ifcopenshell.api.alignment.get_curve(alignment)
|
||||
nested_alignment = [
|
||||
c for c in ifcopenshell.util.element.get_components(alignment) if c.is_a("IfcAlignmentVertical")
|
||||
][0]
|
||||
|
||||
curve_segments = curve.Segments
|
||||
segments = nested_alignment.IsNestedBy[0].RelatingObjects
|
||||
|
||||
for curve_segment, alignment_segment in zip(curve_segments, segments):
|
||||
axis_representation = file.create_entity(
|
||||
type="IfcShapeRepresentation",
|
||||
ContextOfItems=axis_geom_subcontext,
|
||||
RepresentationIdentifier="Axis",
|
||||
RepresentationType="Segment",
|
||||
Items=(curve_segment,),
|
||||
)
|
||||
product = file.create_entity(
|
||||
type="IfcProductDefinitionShape", Name=None, Description=None, Representations=(axis_representation,)
|
||||
)
|
||||
alignment_segment.ObjectPlacement = alignment.ObjectPlacement
|
||||
alignment_segment.Representation = product
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
import math
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def create_vertical_alignment_by_pi_method(
|
||||
file: ifcopenshell.file, name: str, vpoints: Sequence[Sequence[float]], lengths: Sequence[float]
|
||||
) -> entity_instance:
|
||||
"""
|
||||
Create a vertical alignment using the PI layout method.
|
||||
|
||||
:param name: value for Name attribute
|
||||
:param base_curve: base curve representing the 2D projection of the gradient curve
|
||||
:param vpoints: (distance_along, Z_height) pairs denoting the location of the vertical PIs, including start and end.
|
||||
:param lengths: horizontal length of parabolic vertical curves
|
||||
:return: IfcAlignmentHorizontal
|
||||
"""
|
||||
if not (len(vpoints) - 2 == len(lengths)):
|
||||
raise ValueError("lengths should have two fewer elements that vpoints")
|
||||
|
||||
# Create the vertical alignment (IfcAlignmentVertical) and nest alignment segments
|
||||
vertical_alignment = file.create_entity(
|
||||
type="IfcAlignmentVertical",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=f"{name} - Vertical",
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
)
|
||||
|
||||
xPBG, yPBG = vpoints[0]
|
||||
xPVI, yPVI = vpoints[1]
|
||||
i = 1
|
||||
for length in lengths:
|
||||
# back gradient
|
||||
dxBG = xPVI - xPBG
|
||||
dyBG = yPVI - yPBG
|
||||
start_slope = math.tan(math.atan2(dyBG, dxBG))
|
||||
|
||||
# forward gradient
|
||||
i += 1
|
||||
xPFG, yPFG = vpoints[i]
|
||||
dxFG = xPFG - xPVI
|
||||
dyFG = yPFG - yPVI
|
||||
end_slope = math.tan(math.atan2(dyFG, dxFG))
|
||||
|
||||
xEVC = xPVI + length / 2.0
|
||||
yEVC = yPVI + end_slope * length / 2.0
|
||||
|
||||
# create gradient
|
||||
gradient_length = dxBG - length / 2.0
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentVerticalSegment",
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartDistAlong=xPBG,
|
||||
HorizontalLength=gradient_length,
|
||||
StartHeight=yPBG,
|
||||
StartGradient=start_slope,
|
||||
EndGradient=start_slope,
|
||||
RadiusOfCurvature=None,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, vertical_alignment, alignment_segment)
|
||||
|
||||
# create vertical curve
|
||||
if 0.0 < length:
|
||||
k = (end_slope - start_slope) / length
|
||||
xBVC = xPVI - length / 2.0
|
||||
yBVC = yPVI - start_slope * length / 2.0
|
||||
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentVerticalSegment",
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartDistAlong=xBVC,
|
||||
HorizontalLength=length,
|
||||
StartHeight=yBVC,
|
||||
StartGradient=start_slope,
|
||||
EndGradient=end_slope,
|
||||
RadiusOfCurvature=1 / k,
|
||||
PredefinedType="PARABOLICARC",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, vertical_alignment, alignment_segment)
|
||||
|
||||
# start of next curve is end of this curve
|
||||
xPBG = xEVC
|
||||
yPBG = yEVC
|
||||
xPVI = xPFG
|
||||
yPVI = yPFG
|
||||
|
||||
# create last gradient run
|
||||
dx = xPVI - xPBG
|
||||
dy = yPVI - yPBG
|
||||
slope = math.tan(math.atan2(dy, dx))
|
||||
gradient_length = dx
|
||||
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentVerticalSegment",
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartDistAlong=xPBG,
|
||||
HorizontalLength=gradient_length,
|
||||
StartHeight=yPBG,
|
||||
StartGradient=slope,
|
||||
EndGradient=slope,
|
||||
RadiusOfCurvature=None,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, vertical_alignment, alignment_segment)
|
||||
|
||||
# create zero length terminator segment
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentVerticalSegment",
|
||||
StartTag="VPOE",
|
||||
EndTag="VPOE",
|
||||
StartDistAlong=xPVI,
|
||||
HorizontalLength=0.0,
|
||||
StartHeight=yPVI,
|
||||
StartGradient=slope,
|
||||
EndGradient=slope,
|
||||
RadiusOfCurvature=None,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, vertical_alignment, alignment_segment)
|
||||
|
||||
return vertical_alignment
|
||||
@@ -0,0 +1,41 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util
|
||||
from ifcopenshell import entity_instance
|
||||
from typing import Sequence
|
||||
|
||||
import ifcopenshell.util.representation
|
||||
|
||||
|
||||
def get_alignment_layouts(alignment: entity_instance) -> Sequence[entity_instance]:
|
||||
"""
|
||||
Returns the layout alignments nested to this alignment
|
||||
"""
|
||||
layouts = []
|
||||
for rel in alignment.IsNestedBy:
|
||||
for layout in rel.RelatedObjects:
|
||||
if (
|
||||
layout.is_a("IfcAlignmentHorizontal")
|
||||
or layout.is_a("IfcAlignmentVertical")
|
||||
or layout.is_a("IfcAlignmentCant")
|
||||
):
|
||||
layouts.append(layout)
|
||||
|
||||
return layouts
|
||||
@@ -0,0 +1,40 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.api.context
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def get_axis_subcontext(file: ifcopenshell.file) -> entity_instance:
|
||||
"""
|
||||
Returns the IfcGeometricRepresentationSubContext for Model, Axis, MODEL_VIEW. If one does not exist, it is created.
|
||||
"""
|
||||
axis_geom_subcontext = ifcopenshell.util.representation.get_context(file, "Model", "Axis", "MODEL_VIEW")
|
||||
if axis_geom_subcontext == None:
|
||||
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
||||
axis_geom_subcontext = ifcopenshell.api.context.add_context(
|
||||
file,
|
||||
context_type="Model",
|
||||
context_identifier="Axis",
|
||||
target_view="MODEL_VIEW",
|
||||
parent=geometric_representation_context,
|
||||
)
|
||||
|
||||
return axis_geom_subcontext
|
||||
@@ -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/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util
|
||||
from ifcopenshell import entity_instance
|
||||
from typing import Sequence
|
||||
|
||||
import ifcopenshell.util.representation
|
||||
|
||||
|
||||
def get_basis_curve(alignment: entity_instance) -> entity_instance:
|
||||
"""
|
||||
Returns the basis curve for an alignment. This curve is the geometric representation that is used
|
||||
as the basis curve for vertical and cant alignments.
|
||||
|
||||
:param alignment: The alignment
|
||||
:return: The geometric representation that is used as a basis curve, typically an IfcCompositeCurve, or None if the alignment does not have a representation
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
alignment = model.by_type("IfcAlignment")[0]
|
||||
composite_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
"""
|
||||
axis = None
|
||||
|
||||
representations = ifcopenshell.util.representation.get_representations_iter(alignment)
|
||||
for representation in representations:
|
||||
if (representation.RepresentationIdentifier == "Axis" and representation.RepresentationType == "Curve2D") or (
|
||||
representation.RepresentationIdentifier == "FootPrint" and representation.RepresentationType == "Curve2D"
|
||||
):
|
||||
axis = representation
|
||||
break
|
||||
|
||||
return None if axis == None or axis.Items == None or len(axis.Items) == 0 else axis.Items[0]
|
||||
@@ -0,0 +1,44 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util
|
||||
from ifcopenshell import entity_instance
|
||||
from typing import Sequence
|
||||
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def get_child_alignments(alignment: entity_instance) -> Sequence[entity_instance]:
|
||||
"""
|
||||
Returns the aggregated child alignments to this alignment
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
alignment = model.by_type("IfcAlignment")[0]
|
||||
children = ifcopenshell.api.alignment.get_child_alignments(alignment)
|
||||
"""
|
||||
children = []
|
||||
for rel in alignment.IsDecomposedBy:
|
||||
for child in rel.RelatedObjects:
|
||||
if child.is_a("IfcAlignment"):
|
||||
children.append(child)
|
||||
|
||||
return children
|
||||
@@ -0,0 +1,52 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util
|
||||
from ifcopenshell import entity_instance
|
||||
from typing import Sequence
|
||||
|
||||
import ifcopenshell.util.representation
|
||||
|
||||
|
||||
def get_curve(alignment: entity_instance) -> entity_instance:
|
||||
"""
|
||||
Returns the geometric representation curve for an alignment.
|
||||
A horizontal only will have a curve of type IfcCompositeCurve
|
||||
A horizontal+vertical will have a curve of type IfcGradientCurve
|
||||
A horizontal+vertical+cant will have a curve of tyep IfcSegmentedReferenceCurve
|
||||
|
||||
:param alignment: The alignment
|
||||
:return: The geometric representation of the alignemnt or None if the alignment does not have a representation
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
alignment = model.by_type("IfcAlignment")[0]
|
||||
gradient_curve = ifcopenshell.api.alignment.get_curve(alignment)
|
||||
"""
|
||||
axis = None
|
||||
representations = ifcopenshell.util.representation.get_representations_iter(alignment)
|
||||
for representation in representations:
|
||||
if representation.RepresentationIdentifier == "Axis" and (
|
||||
representation.RepresentationType == "Curve2D" or representation.RepresentationType == "Curve3D"
|
||||
):
|
||||
axis = representation
|
||||
break
|
||||
|
||||
return None if axis == None or len(axis.Items) == 0 else axis.Items[0]
|
||||
@@ -0,0 +1,45 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util
|
||||
from ifcopenshell import entity_instance
|
||||
from typing import Sequence
|
||||
|
||||
import ifcopenshell.util.representation
|
||||
|
||||
|
||||
def get_parent_alignment(alignment: entity_instance) -> entity_instance:
|
||||
"""
|
||||
Returns the parent alignment. When multiple vertical alignments share a horizontal alignment
|
||||
the horizontal alignment is nested to the parent alignment, a child alignment is aggregated
|
||||
to the parent alignment for each vertical alignment, and the vertical alignment is nested with
|
||||
its child alignment.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
alignment = model.by_type("IfcAlignment")[0]
|
||||
parent = ifcopenshell.api.alignment.get_parent_alignment(alignment)
|
||||
"""
|
||||
|
||||
for rel in alignment.Decomposes:
|
||||
if rel.RelatingObject.is_a("IfcAlignment"):
|
||||
return rel.RelatingObject
|
||||
|
||||
return None
|
||||
@@ -0,0 +1,61 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.util.element
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def has_zero_length_segment(entity: entity_instance) -> bool:
|
||||
"""
|
||||
Returns true if the entity ends with a zero length segment. If the entity is an IfcCompositeCurve the IfcCurveSegment.Transition must be DISCONTINUOUS
|
||||
|
||||
:param entity: An IfcAlignmentHorizontal, IfcAlignmentVertical, IfcAlignmentCant or IfcCompositeCurve
|
||||
:return: True if the zero length segment is present
|
||||
"""
|
||||
expected_types = [
|
||||
"IfcAlignmentHorizontal",
|
||||
"IfcAlignmentVertical",
|
||||
"IfcAlignmentCant",
|
||||
"IfcCompositeCurve",
|
||||
"IfcGradientCurve",
|
||||
"IfcSegmentedReferenceCurve",
|
||||
]
|
||||
if not entity.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{entity.is_a()}"
|
||||
)
|
||||
|
||||
if entity.is_a("IfcCompositeCurve"):
|
||||
last_segment = entity.Segments[-1]
|
||||
return last_segment.Transition == "DISCONTINUOUS" and last_segment.SegmentLength.wrappedValue == 0.0
|
||||
else:
|
||||
segments = ifcopenshell.util.element.get_components(entity)
|
||||
for rel in entity.IsNestedBy:
|
||||
if 0 < len(rel.RelatedObjects):
|
||||
last_segment = rel.RelatedObjects[-1]
|
||||
if last_segment.is_a("IfcAlignmentSegment"):
|
||||
if last_segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
|
||||
return last_segment.DesignParameters.SegmentLength == 0.0
|
||||
elif last_segment.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
|
||||
return last_segment.DesignParameters.HorizontalLength == 0.0
|
||||
elif last_segment.DesignParameters.is_a("IfcAlignmentCantSegment"):
|
||||
return last_segment.DesignParameters.HorizontalLength == 0.0
|
||||
|
||||
return False
|
||||
@@ -0,0 +1,84 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell.api.alignment import get_axis_subcontext
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def _map_constant_cant(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
raise NotImplementedError("CONSTANTCANT not implemented")
|
||||
|
||||
|
||||
def _map_linear_transition(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
raise NotImplementedError("LINEARTRANSTION not implemented")
|
||||
|
||||
|
||||
def _map_helmert_curve(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
raise NotImplementedError("HELMERTCURVE not implemented")
|
||||
|
||||
|
||||
def _map_bloss_curve(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
raise NotImplementedError("BLOSSCURVE not implemented")
|
||||
|
||||
|
||||
def _map_cosine_curve(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
raise NotImplementedError("COSINECURVE not implemented")
|
||||
|
||||
|
||||
def _map_sine_curve(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
raise NotImplementedError("SINECURVE not implemented")
|
||||
|
||||
|
||||
def _map_viennese_bend(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
raise NotImplementedError("VIENNESEBEND not implemented")
|
||||
|
||||
|
||||
def map_alignment_cant_segment(
|
||||
file: ifcopenshell.file, design_parameters: entity_instance
|
||||
) -> Sequence[entity_instance]:
|
||||
"""
|
||||
Creates IfcCurveSegment entities for the represention of the supplied IfcAlignmentCantSegment business logic entity instance.
|
||||
A pair of entities is returned because a single business logic segment of type HELMERTCURVE maps to two representaiton entities.
|
||||
|
||||
The IfcCurveSegment.Transition transition code is set to DISCONTINUOUS.
|
||||
"""
|
||||
expected_type = "IfcAlignmentCantSegment"
|
||||
if not design_parameters.is_a(expected_type):
|
||||
raise TypeError(f"Expected to see type '{expected_type}', instead received '{design_parameters.is_a()}'.")
|
||||
|
||||
match design_parameters.PredefinedType:
|
||||
case "CONSTANTCANT":
|
||||
result = _map_constant_cant(file, design_parameters)
|
||||
case "LINEARTRANSITION":
|
||||
result = _map_linear_transition(file, design_parameters)
|
||||
case "HELMERTCURVE":
|
||||
result = _map_helmert_curve(file, design_parameters)
|
||||
case "BLOSSCURVE":
|
||||
result = _map_bloss_curve(file, design_parameters)
|
||||
case "COSINECURVE":
|
||||
result = _map_cosine_curve(file, design_parameters)
|
||||
case "SINECURVE":
|
||||
result = _map_sine_curve(file, design_parameters)
|
||||
case "VIENNESEBEND":
|
||||
result = _map_viennese_bend(file, design_parameters)
|
||||
case _:
|
||||
raise TypeError("Unexpected predefined type")
|
||||
|
||||
return result
|
||||
+439
@@ -0,0 +1,439 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
from ifcopenshell import entity_instance
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
from typing import Sequence
|
||||
import math
|
||||
|
||||
|
||||
def _get_curve_factor(design_parameters: entity_instance) -> float:
|
||||
start_radius = design_parameters.StartRadiusOfCurvature
|
||||
end_radius = design_parameters.EndRadiusOfCurvature
|
||||
length = design_parameters.SegmentLength
|
||||
|
||||
f = (0.0 if end_radius == 0.0 else length / end_radius) - (0.0 if start_radius == 0.0 else length / start_radius)
|
||||
return f
|
||||
|
||||
|
||||
def _map_line(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
start_point = design_parameters.StartPoint
|
||||
start_direction = design_parameters.StartDirection
|
||||
length = design_parameters.SegmentLength
|
||||
|
||||
transition = "DISCONTINUOUS"
|
||||
|
||||
parent_curve = file.create_entity(
|
||||
type="IfcLine",
|
||||
Pnt=file.create_entity(
|
||||
type="IfcCartesianPoint",
|
||||
Coordinates=(0.0, 0.0),
|
||||
),
|
||||
Dir=file.create_entity(
|
||||
type="IfcVector",
|
||||
Orientation=file.create_entity(
|
||||
type="IfcDirection",
|
||||
DirectionRatios=(1.0, 0.0),
|
||||
),
|
||||
Magnitude=1.0,
|
||||
),
|
||||
)
|
||||
curve_segment = file.create_entity(
|
||||
type="IfcCurveSegment",
|
||||
Transition=transition,
|
||||
Placement=file.create_entity(
|
||||
type="IfcAxis2Placement2D",
|
||||
Location=start_point,
|
||||
RefDirection=file.createIfcDirection(
|
||||
(math.cos(start_direction), math.sin(start_direction)),
|
||||
),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(length),
|
||||
ParentCurve=parent_curve,
|
||||
)
|
||||
return (curve_segment, None)
|
||||
|
||||
|
||||
def _map_circular_arc(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
start_point = design_parameters.StartPoint
|
||||
start_direction = design_parameters.StartDirection
|
||||
start_radius = design_parameters.StartRadiusOfCurvature
|
||||
length = design_parameters.SegmentLength
|
||||
|
||||
transition = "DISCONTINUOUS"
|
||||
|
||||
parent_curve = file.createIfcCircle(
|
||||
Position=file.createIfcAxis2Placement2D(
|
||||
Location=file.createIfcCartesianPoint((0.0, 0.0)),
|
||||
RefDirection=file.createIfcDirection((1.0, 0.0)),
|
||||
),
|
||||
Radius=math.fabs(start_radius),
|
||||
)
|
||||
|
||||
curve_segment = file.create_entity(
|
||||
type="IfcCurveSegment",
|
||||
Transition=transition,
|
||||
Placement=file.createIfcAxis2Placement2D(
|
||||
Location=start_point,
|
||||
RefDirection=file.createIfcDirection((math.cos(start_direction), math.sin(start_direction))),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(length * (start_radius / math.fabs(start_radius))),
|
||||
ParentCurve=parent_curve,
|
||||
)
|
||||
return (curve_segment, None)
|
||||
|
||||
|
||||
def _map_clothoid(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
start_point = design_parameters.StartPoint
|
||||
start_direction = design_parameters.StartDirection
|
||||
start_radius = design_parameters.StartRadiusOfCurvature
|
||||
end_radius = design_parameters.EndRadiusOfCurvature
|
||||
length = design_parameters.SegmentLength
|
||||
|
||||
transition = "DISCONTINUOUS"
|
||||
|
||||
f = _get_curve_factor(design_parameters)
|
||||
A = (length / math.sqrt(math.fabs(f))) * (f / math.fabs(f))
|
||||
parent_curve = file.createIfcClothoid(
|
||||
Position=file.createIfcAxis2Placement2D(
|
||||
Location=file.createIfcCartesianPoint((0.0, 0.0)),
|
||||
RefDirection=file.createIfcDirection((1.0, 0.0)),
|
||||
),
|
||||
ClothoidConstant=A,
|
||||
)
|
||||
|
||||
if (math.fabs(start_radius) < math.fabs(end_radius) and start_radius != 0.0) or end_radius == 0.0:
|
||||
offset = -length - (length * start_radius / (end_radius - start_radius) if end_radius != 0.0 else 0.0)
|
||||
else:
|
||||
offset = length * end_radius / (start_radius - end_radius) if start_radius != 0.0 else 0.0
|
||||
|
||||
curve_segment = file.create_entity(
|
||||
type="IfcCurveSegment",
|
||||
Transition=transition,
|
||||
Placement=file.create_entity(
|
||||
type="IfcAxis2Placement2D",
|
||||
Location=start_point,
|
||||
RefDirection=file.createIfcDirection((math.cos(start_direction), math.sin(start_direction))),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(offset),
|
||||
SegmentLength=file.createIfcLengthMeasure(length),
|
||||
ParentCurve=parent_curve,
|
||||
)
|
||||
return (curve_segment, None)
|
||||
|
||||
|
||||
def _map_cubic(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
start_point = design_parameters.StartPoint
|
||||
start_direction = design_parameters.StartDirection
|
||||
start_radius = design_parameters.StartRadiusOfCurvature
|
||||
end_radius = design_parameters.EndRadiusOfCurvature
|
||||
length = design_parameters.SegmentLength
|
||||
|
||||
transition = "DISCONTINUOUS"
|
||||
|
||||
offset = 0.0
|
||||
A0 = 0.0 # constant term
|
||||
A1 = 0.0 # linear term
|
||||
A2 = 0.0 # quadratic term
|
||||
A3 = 0.0 # cubic term
|
||||
|
||||
if end_radius != 0.0 and start_radius != 0.0 and end_radius != start_radius:
|
||||
f = (start_radius - end_radius) / end_radius # note, this "f" is different that _get_curve_factor computes
|
||||
A3 = f / (6.0 * start_radius * length)
|
||||
offset = length / f
|
||||
elif end_radius != 0.0:
|
||||
A3 = 1.0 / (6.0 * end_radius * length)
|
||||
offset = 0.0
|
||||
elif start_radius != 0.0:
|
||||
A3 = -1.0 / (6.0 * start_radius * length)
|
||||
offset = -length
|
||||
|
||||
parent_curve = file.createIfcPolynomialCurve(
|
||||
Position=file.createIfcAxis2Placement2D(
|
||||
Location=file.createIfcCartesianPoint((0.0, 0.0)),
|
||||
RefDirection=file.createIfcDirection((1.0, 0.0)),
|
||||
),
|
||||
CoefficientsX=(0.0, 1.0),
|
||||
CoefficientsY=(A0, A1, A2, A3),
|
||||
)
|
||||
|
||||
curve_segment = file.create_entity(
|
||||
type="IfcCurveSegment",
|
||||
Transition=transition,
|
||||
Placement=file.create_entity(
|
||||
type="IfcAxis2Placement2D",
|
||||
Location=start_point,
|
||||
RefDirection=file.createIfcDirection((math.cos(start_direction), math.sin(start_direction))),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(offset),
|
||||
SegmentLength=file.createIfcLengthMeasure(length),
|
||||
ParentCurve=parent_curve,
|
||||
)
|
||||
return (curve_segment, None)
|
||||
|
||||
|
||||
def _map_helmert_curve(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
start_point = design_parameters.StartPoint
|
||||
start_direction = design_parameters.StartDirection
|
||||
start_radius = design_parameters.StartRadiusOfCurvature
|
||||
end_radius = design_parameters.EndRadiusOfCurvature
|
||||
length = design_parameters.SegmentLength
|
||||
|
||||
transition = "DISCONTINUOUS"
|
||||
f = _get_curve_factor(design_parameters)
|
||||
|
||||
a0_1 = 0.0 * f + length / start_radius if start_radius != 0 else 0.0 # constant term, first half
|
||||
a1_1 = 0.0 * f # linear term, first half
|
||||
a2_1 = 2.0 * f # quadratic term, first half
|
||||
|
||||
A0_1 = length * math.pow(math.fabs(a0_1), -1.0 / 1.0) * a0_1 / math.fabs(a0_1) if a0_1 != 0.0 else 0.0
|
||||
A1_1 = length * math.pow(math.fabs(a1_1), -1.0 / 2.0) * a1_1 / math.fabs(a1_1) if a1_1 != 0.0 else 0.0
|
||||
A2_1 = length * math.pow(math.fabs(a2_1), -1.0 / 3.0) * a2_1 / math.fabs(a2_1) if a2_1 != 0.0 else 0.0
|
||||
|
||||
x1, y1, angle1 = ifcopenshell_wrapper.helmert_curve_point(A0_1, A1_1, A2_1, length / 2)
|
||||
|
||||
parent_curve1 = file.createIfcSecondOrderPolynomialSpiral(
|
||||
Position=file.createIfcAxis2Placement2D(
|
||||
Location=file.createIfcCartesianPoint((0.0, 0.0)), RefDirection=file.createIfcDirection((1.0, 0.0))
|
||||
),
|
||||
QuadraticTerm=A2_1,
|
||||
LinearTerm=A1_1 if A1_1 != 0.0 else None,
|
||||
ConstantTerm=A0_1 if A0_1 != 0.0 else None,
|
||||
)
|
||||
|
||||
curve_segment1 = file.create_entity(
|
||||
type="IfcCurveSegment",
|
||||
Transition=transition,
|
||||
Placement=file.create_entity(
|
||||
type="IfcAxis2Placement2D",
|
||||
Location=start_point,
|
||||
RefDirection=file.createIfcDirection((math.cos(start_direction), math.sin(start_direction))),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(length / 2),
|
||||
ParentCurve=parent_curve1,
|
||||
)
|
||||
|
||||
a0_2 = -1.0 * f + (length / start_radius if start_radius != 0.0 else 0.0) # constant term, second half
|
||||
a1_2 = 4.0 * f # linear term, second half
|
||||
a2_2 = -2.0 * f # quadratic term, second half
|
||||
|
||||
A0_2 = length * math.pow(math.fabs(a0_2), -1.0 / 1.0) * (a0_2 / math.fabs(a0_2)) if a0_2 != 0.0 else 0.0
|
||||
A1_2 = length * math.pow(math.fabs(a1_2), -1.0 / 2.0) * (a1_2 / math.fabs(a1_2)) if a1_2 != 0.0 else 0.0
|
||||
A2_2 = length * math.pow(math.fabs(a2_2), -1.0 / 3.0) * (a2_2 / math.fabs(a2_2)) if a2_2 != 0.0 else 0.0
|
||||
|
||||
x2, y2, angle2 = ifcopenshell_wrapper.helmert_curve_point(A0_2, A1_2, A2_2, length / 2)
|
||||
anglep = angle1 - angle2
|
||||
xp = x1 - x2 * math.cos(anglep) + y2 * math.sin(anglep)
|
||||
yp = y1 - x2 * math.sin(anglep) - y2 * math.cos(anglep)
|
||||
|
||||
parent_curve2 = file.createIfcSecondOrderPolynomialSpiral(
|
||||
Position=file.createIfcAxis2Placement2D(
|
||||
Location=file.createIfcCartesianPoint((xp, yp)),
|
||||
RefDirection=file.createIfcDirection((math.cos(anglep), math.sin(anglep))),
|
||||
),
|
||||
QuadraticTerm=A2_2,
|
||||
LinearTerm=A1_2 if A1_2 != 0.0 else None,
|
||||
ConstantTerm=A0_2 if A0_2 != 0.0 else None,
|
||||
)
|
||||
|
||||
curve_segment2 = file.create_entity(
|
||||
type="IfcCurveSegment",
|
||||
Transition=transition,
|
||||
Placement=file.create_entity(
|
||||
type="IfcAxis2Placement2D",
|
||||
Location=file.createIfcCartesianPoint((x1, y1)),
|
||||
RefDirection=file.createIfcDirection((math.cos(angle1), math.sin(angle1))),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(length / 2),
|
||||
SegmentLength=file.createIfcLengthMeasure(length / 2),
|
||||
ParentCurve=parent_curve2,
|
||||
)
|
||||
|
||||
return curve_segment1, curve_segment2
|
||||
|
||||
|
||||
def _map_bloss_curve(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
start_point = design_parameters.StartPoint
|
||||
start_direction = design_parameters.StartDirection
|
||||
start_radius = design_parameters.StartRadiusOfCurvature
|
||||
length = design_parameters.SegmentLength
|
||||
|
||||
transition = "DISCONTINUOUS"
|
||||
f = _get_curve_factor(design_parameters)
|
||||
|
||||
a0 = length / start_radius if start_radius != 0.0 else 0.0 # constant term
|
||||
a1 = 0.0 # linear term
|
||||
a2 = 3.0 * f # quadratic term
|
||||
a3 = -2.0 * f # cubic term
|
||||
|
||||
A0 = length * math.pow(math.fabs(a0), -1.0 / 1.0) * (a0 / math.fabs(a0)) if a0 != 0.0 else 0.0
|
||||
A1 = length * math.pow(math.fabs(a1), -1.0 / 2.0) * (a1 / math.fabs(a1)) if a1 != 0.0 else 0.0
|
||||
A2 = length * math.pow(math.fabs(a2), -1.0 / 3.0) * (a2 / math.fabs(a2)) if a2 != 0.0 else 0.0
|
||||
A3 = length * math.pow(math.fabs(a3), -1.0 / 4.0) * (a3 / math.fabs(a3)) if a3 != 0.0 else 0.0
|
||||
|
||||
parent_curve = file.createIfcThirdOrderPolynomialSpiral(
|
||||
Position=file.createIfcAxis2Placement2D(
|
||||
Location=file.createIfcCartesianPoint((0.0, 0.0)), RefDirection=file.createIfcDirection((1.0, 0.0))
|
||||
),
|
||||
CubicTerm=A3,
|
||||
QuadraticTerm=A2 if A2 != 0.0 else None,
|
||||
LinearTerm=A1 if A1 != 0.0 else None,
|
||||
ConstantTerm=A0 if A0 != 0.0 else None,
|
||||
)
|
||||
|
||||
curve_segment = file.create_entity(
|
||||
type="IfcCurveSegment",
|
||||
Transition=transition,
|
||||
Placement=file.create_entity(
|
||||
type="IfcAxis2Placement2D",
|
||||
Location=start_point,
|
||||
RefDirection=file.createIfcDirection((math.cos(start_direction), math.sin(start_direction))),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(length),
|
||||
ParentCurve=parent_curve,
|
||||
)
|
||||
return (curve_segment, None)
|
||||
|
||||
|
||||
def _map_cosine_curve(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
start_point = design_parameters.StartPoint
|
||||
start_direction = design_parameters.StartDirection
|
||||
start_radius = design_parameters.StartRadiusOfCurvature
|
||||
length = design_parameters.SegmentLength
|
||||
|
||||
transition = "DISCONTINUOUS"
|
||||
|
||||
f = _get_curve_factor(design_parameters)
|
||||
|
||||
a0 = 0.5 * f + (length / start_radius if start_radius != 0.0 else 0.0)
|
||||
a1 = -0.5 * f
|
||||
|
||||
A0 = length * math.pow(math.fabs(a0), -1.0 / 1.0) * (a0 / math.fabs(a0)) if a0 != 0.0 else 0.0
|
||||
A1 = length * math.pow(math.fabs(a1), -1.0 / 1.0) * (a1 / math.fabs(a1)) if a1 != 0.0 else 0.0
|
||||
|
||||
parent_curve = file.createIfcCosineSpiral(
|
||||
Position=file.createIfcAxis2Placement2D(
|
||||
Location=file.createIfcCartesianPoint((0.0, 0.0)),
|
||||
RefDirection=file.createIfcDirection((1.0, 0.0)),
|
||||
),
|
||||
CosineTerm=A1,
|
||||
ConstantTerm=(A0 if A0 != 0.0 else None),
|
||||
)
|
||||
|
||||
curve_segment = file.create_entity(
|
||||
type="IfcCurveSegment",
|
||||
Transition=transition,
|
||||
Placement=file.create_entity(
|
||||
type="IfcAxis2Placement2D",
|
||||
Location=start_point,
|
||||
RefDirection=file.createIfcDirection((math.cos(start_direction), math.sin(start_direction))),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(length),
|
||||
ParentCurve=parent_curve,
|
||||
)
|
||||
return (curve_segment, None)
|
||||
|
||||
|
||||
def _map_sine_curve(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
start_point = design_parameters.StartPoint
|
||||
start_direction = design_parameters.StartDirection
|
||||
start_radius = design_parameters.StartRadiusOfCurvature
|
||||
length = design_parameters.SegmentLength
|
||||
|
||||
transition = "DISCONTINUOUS"
|
||||
|
||||
f = _get_curve_factor(design_parameters)
|
||||
a0 = length / start_radius if start_radius != 0.0 else 0.0
|
||||
a1 = f
|
||||
a2 = -f / (2.0 * math.pi)
|
||||
|
||||
A0 = length * math.pow(math.fabs(a0), -1.0 / 1.0) * (a0 / math.fabs(a0)) if a0 != 0.0 else 0.0
|
||||
A1 = length * math.pow(math.fabs(a1), -1.0 / 2.0) * (a1 / math.fabs(a1)) if a1 != 0.0 else 0.0
|
||||
A2 = length * math.pow(math.fabs(a2), -1.0 / 1.0) * (a2 / math.fabs(a2)) if a2 != 0.0 else 0.0
|
||||
|
||||
parent_curve = file.createIfcSineSpiral(
|
||||
Position=file.createIfcAxis2Placement2D(
|
||||
Location=file.createIfcCartesianPoint((0.0, 0.0)),
|
||||
RefDirection=file.createIfcDirection((1.0, 0.0)),
|
||||
),
|
||||
SineTerm=A2,
|
||||
LinearTerm=(A1 if A1 != 0.0 else None),
|
||||
ConstantTerm=(A0 if A0 != 0.0 else None),
|
||||
)
|
||||
|
||||
curve_segment = file.create_entity(
|
||||
type="IfcCurveSegment",
|
||||
Transition=transition,
|
||||
Placement=file.create_entity(
|
||||
type="IfcAxis2Placement2D",
|
||||
Location=start_point,
|
||||
RefDirection=file.createIfcDirection((math.cos(start_direction), math.sin(start_direction))),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(length),
|
||||
ParentCurve=parent_curve,
|
||||
)
|
||||
return (curve_segment, None)
|
||||
|
||||
|
||||
def _map_viennese_bend(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
raise NotImplementedError("VIENNESEBEND not implemented")
|
||||
|
||||
|
||||
def map_alignment_horizontal_segment(
|
||||
file: ifcopenshell.file, design_parameters: entity_instance
|
||||
) -> Sequence[entity_instance]:
|
||||
"""
|
||||
Creates IfcCurveSegment entities for the represention of the supplied IfcAlignmentHorizontalSegment business logic entity instance.
|
||||
A pair of entities is returned because a single business logic segment of type HELMERTCURVE maps to two representaiton entities.
|
||||
|
||||
The IfcCurveSegment.Transition transition code is set to DISCONTINUOUS
|
||||
"""
|
||||
expected_type = "IfcAlignmentHorizontalSegment"
|
||||
if not design_parameters.is_a(expected_type):
|
||||
raise TypeError(f"Expected to see type '{expected_type}', instead received '{design_parameters.is_a()}'.")
|
||||
|
||||
match design_parameters.PredefinedType:
|
||||
case "LINE":
|
||||
result = _map_line(file, design_parameters)
|
||||
case "CIRCULARARC":
|
||||
result = _map_circular_arc(file, design_parameters)
|
||||
case "CLOTHOID":
|
||||
result = _map_clothoid(file, design_parameters)
|
||||
case "CUBIC":
|
||||
result = _map_cubic(file, design_parameters)
|
||||
case "HELMERTCURVE":
|
||||
result = _map_helmert_curve(file, design_parameters)
|
||||
case "BLOSSCURVE":
|
||||
result = _map_bloss_curve(file, design_parameters)
|
||||
case "COSINECURVE":
|
||||
result = _map_cosine_curve(file, design_parameters)
|
||||
case "SINECURVE":
|
||||
result = _map_sine_curve(file, design_parameters)
|
||||
case "VIENNESEBEND":
|
||||
result = _map_viennese_bend(file, design_parameters)
|
||||
case _:
|
||||
raise TypeError("Unexpected predefined type")
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,47 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
from ifcopenshell import entity_instance
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def map_alignment_segment(file: ifcopenshell.file, segment: entity_instance) -> Sequence[entity_instance]:
|
||||
"""
|
||||
Creates IfcCurveSegment entities for the represention of the supplied IfcAlignmentSegment business logic entity instance.
|
||||
A pair of entities is returned because a single business logic segment of type HELMERTCURVE maps to two representaiton entities.
|
||||
|
||||
The IfcCurveSegment.Transition transition code is set to DISCONTINUOUS, except for the transition between helmert curve segments.
|
||||
|
||||
This function will evaluate the IfcAlignmentSegment.DesignParameters type and call the correct lower level mapping function.
|
||||
"""
|
||||
expected_type = "IfcAlignmentSegment"
|
||||
if not segment.is_a(expected_type):
|
||||
raise TypeError(f"Expected to see type '{expected_type}', instead received '{segment.is_a()}'.")
|
||||
|
||||
if segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
|
||||
return ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, segment.DesignParameters)
|
||||
elif segment.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
|
||||
return ifcopenshell.api.alignment.map_alignment_vertical_segment(file, segment.DesignParameters)
|
||||
elif segment.DesignParameters.is_a("IfcAlignmentCantSegment"):
|
||||
return ifcopenshell.api.alignment.map_alignment_cant_segment(file, segment.DesignParameters)
|
||||
else:
|
||||
raise TypeError("Unexpected type for segment.DesignParameters")
|
||||
|
||||
return (None, None)
|
||||
@@ -0,0 +1,62 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell import entity_instance
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def map_alignment_segments(
|
||||
file: ifcopenshell.file, alignment: entity_instance, composite_curve: entity_instance
|
||||
) -> None:
|
||||
"""
|
||||
Creates IfcCurveSegment entities for the supplied alignment business logic entity instance and assigns them to the composite curve.
|
||||
End-Start points of adjacent segments are evaluated and the IfcCurveSegment.Transition is set.
|
||||
|
||||
This function does not create an IfcShapeRepresentation. Use create_geometric_representation to create all the representations
|
||||
for an alignment. This function only populates the composite curve with IfcCurveSegment entities.
|
||||
|
||||
:param alignment: The business logic alignment, expected to be IfcAlignmentHorizontal, IfcAlignmentVertical, or IfcAlignmentCant
|
||||
:param composite_curve: The IfcCompositeCurve (or subclass) which will receive the IfcCurveSegment
|
||||
:return: None
|
||||
"""
|
||||
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
|
||||
if not alignment.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{alignment.is_a()}"
|
||||
)
|
||||
|
||||
if alignment.is_a("IfcAlignmentHorizontal") and not composite_curve.is_a("IfcCompositeCurve"):
|
||||
raise TypeError(f"Expected to see IfcCompositeCurve, instead received '{composite_curve.is_a()}'.")
|
||||
elif alignment.is_a("IfcAlignmentVertical") and not composite_curve.is_a("IfcGradientCurve"):
|
||||
raise TypeError(f"Expected to see IfcGradientCurve, instead received '{composite_curve.is_a()}'.")
|
||||
elif alignment.is_a("IfcAlignmentCant") and not composite_curve.is_a("IfcSegmentedReferenceCurve"):
|
||||
raise TypeError(f"Expected to see IfcSegmentedReferenceCurve, instead received '{composite_curve.is_a()}'.")
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
|
||||
composite_curve.SelfIntersect = False
|
||||
|
||||
for rel_nests in alignment.IsNestedBy:
|
||||
for layout in rel_nests.RelatedObjects:
|
||||
if layout.is_a("IfcLinearElement"):
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, layout)
|
||||
for mapped_segment in mapped_segments:
|
||||
if mapped_segment:
|
||||
ifcopenshell.api.alignment.add_segment_to_curve(file, mapped_segment, composite_curve)
|
||||
@@ -0,0 +1,225 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
from ifcopenshell import ifcopenshell_wrapper
|
||||
from ifcopenshell import entity_instance
|
||||
from typing import Sequence
|
||||
import math
|
||||
|
||||
|
||||
def _polynomial_length(A: float, B: float, C: float, L: float) -> float:
|
||||
# closed form solultion for length of parabolic curve.
|
||||
# see https://www.integral-table.com, equation #37
|
||||
# Parabolic curve equation: y = A + Bx + Cx^2
|
||||
# y' = B + 2Cx
|
||||
# Length of a curve = Integral[0,L]( (y')^2 + 1) dx)
|
||||
# y'^2 = 4C^2x^2 + 4BCx + B^2
|
||||
# Substituting, Length of a curve = Integral[0,L]( (4C^2)x^2 + (4BC)x + (B^2 + 1)) dx)
|
||||
# for eq. #37 cited above, a = 4C^2, b = 4BC, c = B^2 + 1
|
||||
a = 4.0 * C * C
|
||||
b = 4.0 * B * C
|
||||
c = B * B + 1
|
||||
|
||||
v1 = lambda a, b, c, x: (b + 2.0 * a * x) / (4.0 * a)
|
||||
v2 = lambda a, b, c, x: math.sqrt(a * x * x + b * x + c)
|
||||
v3 = lambda a, b, c, x: (4.0 * a * c - b * b) / (8.0 * math.pow(a, 1.5))
|
||||
v4 = lambda a, b, c, x: math.log(math.fabs(2.0 * a * x + b + 2.0 * math.sqrt(a * (a * x * x + b * x + c))))
|
||||
|
||||
fn = lambda a, b, c, x: v1(a, b, c, x) * v2(a, b, c, x) + v3(a, b, c, x) * v4(a, b, c, x)
|
||||
|
||||
curve_length = fn(a, b, c, L) - fn(
|
||||
a, b, c, 0
|
||||
) # remember when evaluating an integral, it must be evaluated at end points (L and 0)
|
||||
return curve_length
|
||||
|
||||
|
||||
def _map_constant_gradient(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
start_distance_along = design_parameters.StartDistAlong
|
||||
horizontal_length = design_parameters.HorizontalLength
|
||||
start_height = design_parameters.StartHeight
|
||||
start_gradient = design_parameters.StartGradient
|
||||
end_gradient = design_parameters.EndGradient
|
||||
radius_of_curvature = design_parameters.RadiusOfCurvature
|
||||
transition = "DISCONTINUOUS"
|
||||
|
||||
parent_curve = file.create_entity(
|
||||
type="IfcLine",
|
||||
Pnt=file.create_entity(
|
||||
type="IfcCartesianPoint",
|
||||
Coordinates=(0.0, 0.0),
|
||||
),
|
||||
Dir=file.create_entity(
|
||||
type="IfcVector",
|
||||
Orientation=file.create_entity(
|
||||
type="IfcDirection",
|
||||
DirectionRatios=(1.0, 0.0),
|
||||
),
|
||||
Magnitude=1.0,
|
||||
),
|
||||
)
|
||||
|
||||
dx = math.cos(math.atan(start_gradient))
|
||||
dy = math.sin(math.atan(start_gradient))
|
||||
curve_segment_length = horizontal_length / dx
|
||||
|
||||
curve_segment = file.create_entity(
|
||||
type="IfcCurveSegment",
|
||||
Transition=transition,
|
||||
Placement=file.create_entity(
|
||||
type="IfcAxis2Placement2D",
|
||||
Location=file.create_entity(type="IfcCartesianPoint", Coordinates=(start_distance_along, start_height)),
|
||||
RefDirection=file.createIfcDirection((dx, dy)),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(curve_segment_length),
|
||||
ParentCurve=parent_curve,
|
||||
)
|
||||
return (curve_segment, None)
|
||||
|
||||
|
||||
def _map_parabolic_arc(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
start_distance_along = design_parameters.StartDistAlong
|
||||
horizontal_length = design_parameters.HorizontalLength
|
||||
start_height = design_parameters.StartHeight
|
||||
start_gradient = design_parameters.StartGradient
|
||||
end_gradient = design_parameters.EndGradient
|
||||
radius_of_curvature = design_parameters.RadiusOfCurvature
|
||||
transition = "DISCONTINUOUS"
|
||||
|
||||
A = start_height
|
||||
B = start_gradient
|
||||
C = (end_gradient - start_gradient) / (2.0 * horizontal_length)
|
||||
|
||||
parent_curve = file.create_entity(
|
||||
type="IfcPolynomialCurve",
|
||||
Position=file.create_entity(
|
||||
type="IfcAxis2Placement2D",
|
||||
Location=file.create_entity(type="IfcCartesianPoint", Coordinates=(0.0, 0.0)),
|
||||
RefDirection=file.createIfcDirection(
|
||||
(1.0, 0.0),
|
||||
),
|
||||
),
|
||||
CoefficientsX=(0.0, 1.0),
|
||||
CoefficientsY=(A, B, C),
|
||||
)
|
||||
|
||||
dx = math.cos(math.atan(start_gradient))
|
||||
dy = math.sin(math.atan(start_gradient))
|
||||
curve_segment_length = _polynomial_length(A, B, C, horizontal_length)
|
||||
|
||||
curve_segment = file.create_entity(
|
||||
type="IfcCurveSegment",
|
||||
Transition=transition,
|
||||
Placement=file.create_entity(
|
||||
type="IfcAxis2Placement2D",
|
||||
Location=file.create_entity(type="IfcCartesianPoint", Coordinates=(start_distance_along, start_height)),
|
||||
RefDirection=file.createIfcDirection((dx, dy)),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(curve_segment_length),
|
||||
ParentCurve=parent_curve,
|
||||
)
|
||||
return (curve_segment, None)
|
||||
|
||||
|
||||
def _map_circular_arc(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
start_distance_along = design_parameters.StartDistAlong
|
||||
horizontal_length = design_parameters.HorizontalLength
|
||||
start_height = design_parameters.StartHeight
|
||||
start_gradient = design_parameters.StartGradient
|
||||
end_gradient = design_parameters.EndGradient
|
||||
radius_of_curvature = design_parameters.RadiusOfCurvature
|
||||
transition = "DISCONTINUOUS"
|
||||
|
||||
start_angle = math.atan(start_gradient)
|
||||
end_angle = math.atan(end_gradient)
|
||||
dx = math.cos(start_angle)
|
||||
dy = math.sin(start_angle)
|
||||
if start_angle < end_angle:
|
||||
radius = horizontal_length / (math.sin(end_angle) - math.sin(start_angle))
|
||||
x = -radius * math.sin(start_angle)
|
||||
y = radius * math.cos(start_angle)
|
||||
start_angle += 3.0 * math.pi / 2.0
|
||||
end_angle += 3.0 * math.pi / 2.0
|
||||
else:
|
||||
radius = horizontal_length / (math.sin(start_angle) - math.sin(end_angle))
|
||||
x = radius * math.sin(start_angle)
|
||||
y = -radius * math.cos(start_angle)
|
||||
start_angle += math.pi / 2.0
|
||||
end_angle += math.pi / 2.0
|
||||
|
||||
parent_curve = file.createIfcCircle(
|
||||
Position=file.createIfcAxis2Placement2D(
|
||||
Location=file.createIfcCartesianPoint((x, y)),
|
||||
RefDirection=file.createIfcDirection((1.0, 0.0)),
|
||||
),
|
||||
Radius=radius,
|
||||
)
|
||||
|
||||
segment_curve_length = radius * math.fabs(end_angle - start_angle)
|
||||
|
||||
curve_segment = file.create_entity(
|
||||
type="IfcCurveSegment",
|
||||
Transition=transition,
|
||||
Placement=file.createIfcAxis2Placement2D(
|
||||
Location=file.createIfcCartesianPoint((start_distance_along, start_height)),
|
||||
RefDirection=file.createIfcDirection(
|
||||
(dx, dy),
|
||||
),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(radius * start_angle),
|
||||
SegmentLength=file.createIfcLengthMeasure(radius * (end_angle - start_angle)),
|
||||
ParentCurve=parent_curve,
|
||||
)
|
||||
return (curve_segment, None)
|
||||
|
||||
|
||||
def _map_clothoid(file: ifcopenshell.file, design_parameters: entity_instance) -> Sequence[entity_instance]:
|
||||
raise NotImplementedError("mapping for IfcVerticalSegment.CLOTHOID not implemented")
|
||||
|
||||
|
||||
def map_alignment_vertical_segment(
|
||||
file: ifcopenshell.file, design_parameters: entity_instance
|
||||
) -> Sequence[entity_instance]:
|
||||
"""
|
||||
Creates IfcCurveSegment entities for the represention of the supplied IfcAlignmentVerticalSegment business logic entity instance.
|
||||
A pair of entities is returned for consistency with map_alignment_horizontal_segment and map_alignment_cant_segment.
|
||||
|
||||
"""
|
||||
expected_type = "IfcAlignmentVerticalSegment"
|
||||
if not design_parameters.is_a(expected_type):
|
||||
raise TypeError(f"Expected to see type '{expected_type}', instead received '{design_parameters.is_a()}'.")
|
||||
|
||||
match design_parameters.PredefinedType:
|
||||
case "CONSTANTGRADIENT":
|
||||
result = _map_constant_gradient(file, design_parameters)
|
||||
|
||||
case "PARABOLICARC":
|
||||
result = _map_parabolic_arc(file, design_parameters)
|
||||
|
||||
case "CIRCULARARC":
|
||||
result = _map_circular_arc(file, design_parameters)
|
||||
|
||||
case "CLOTHOID":
|
||||
result = _map_clothoid(file, design_parameters)
|
||||
|
||||
case _:
|
||||
raise TypeError("Unexpected predefined type")
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,42 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
from ifcopenshell import entity_instance
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
def name_segments(prefix: str, alignment: entity_instance) -> None:
|
||||
"""
|
||||
Sets the segment name like ("H1" for horizontal, "V1" for vertical, "C1" for cant)
|
||||
|
||||
:param prefix: The naming prefix
|
||||
:param alignment: The alignment whose segments are to be named. This should be a IfcAlignmentHorizontal, IfcAlignmentVertical or IfcAlignmentCant
|
||||
"""
|
||||
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
|
||||
if not alignment.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{v.is_a()}"
|
||||
)
|
||||
|
||||
i = 1
|
||||
for rel in alignment.IsNestedBy:
|
||||
for segment in rel.RelatedObjects:
|
||||
if segment.is_a("IfcAlignmentSegment"):
|
||||
segment.Name = f"{prefix}{i}"
|
||||
i += 1
|
||||
@@ -0,0 +1,59 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
import ifcopenshell.util
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def remove_last_segment(file: ifcopenshell.file, entity: entity_instance) -> entity_instance:
|
||||
"""
|
||||
Removes the last segment from the end of entity.
|
||||
|
||||
:param entity: An IfcAlignmentHorizontal, IfcAlignmentVertical, IfcAlignmentCant or IfcCompositeCurve
|
||||
:return: The segment
|
||||
"""
|
||||
expected_types = [
|
||||
"IfcAlignmentHorizontal",
|
||||
"IfcAlignmentVertical",
|
||||
"IfcAlignmentCant",
|
||||
"IfcCompositeCurve",
|
||||
"IfcGradientCurve",
|
||||
"IfcSegmentedReferenceCurve",
|
||||
]
|
||||
if not entity.is_a() in expected_types:
|
||||
raise TypeError(
|
||||
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{entity.is_a()}"
|
||||
)
|
||||
|
||||
if entity.is_a("IfcCompositeCurve"):
|
||||
last_segment = entity.Segments[-1]
|
||||
entity.Segments = tuple(set(entity.Segments) - {last_segment})
|
||||
entity.Segments[-1].Transition = "DISCONTINUOUS"
|
||||
return last_segment
|
||||
else:
|
||||
components = ifcopenshell.util.element.get_components(entity)
|
||||
last_segment = components[-1]
|
||||
ifcopenshell.api.nest.unassign_object(file, (last_segment,))
|
||||
return last_segment
|
||||
@@ -0,0 +1,35 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell import entity_instance
|
||||
import ifcopenshell.api.alignment.remove_last_segment
|
||||
|
||||
|
||||
def remove_zero_length_segment(file: ifcopenshell.file, entity: entity_instance) -> entity_instance:
|
||||
"""
|
||||
Removes the zero length segment from the end of entity.
|
||||
|
||||
:param entity: An IfcAlignmentHorizontal, IfcAlignmentVertical, IfcAlignmentCant or IfcCompositeCurve
|
||||
:return: The zero length segment
|
||||
"""
|
||||
if not ifcopenshell.api.alignment.has_zero_length_segment(entity):
|
||||
return None
|
||||
|
||||
return ifcopenshell.api.alignment.remove_last_segment(file, entity)
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
from ifcopenshell import ifcopenshell_wrapper
|
||||
import ifcopenshell.geom
|
||||
from ifcopenshell import entity_instance
|
||||
from typing import Sequence
|
||||
import numpy as np
|
||||
import math
|
||||
|
||||
|
||||
def update_curve_segment_transition_code(prev_segment: entity_instance, segment: entity_instance) -> None:
|
||||
"""
|
||||
Updates IfcCurveSegment.Transition of prev_segment based on a comparison of
|
||||
the position, ref. direction, and curvature at the end of the prev_segment and the start of segment.
|
||||
"""
|
||||
expected_type = "IfcCurveSegment"
|
||||
if not prev_segment.is_a(expected_type):
|
||||
raise TypeError(f"Expected to see '{expected_type}', instead received '{prev_segment.is_a()}'.")
|
||||
|
||||
if not segment.is_a(expected_type):
|
||||
raise TypeError(f"Expected to see '{expected_type}', instead received '{segment.is_a()}'.")
|
||||
|
||||
if len(prev_segment.UsingCurves) != 1:
|
||||
raise TypeError("prev_segment must belong to exactly one curve")
|
||||
|
||||
if len(segment.UsingCurves) != 1:
|
||||
raise TypeError("segment must belong to exactly one curve")
|
||||
|
||||
if prev_segment.UsingCurves[0] != segment.UsingCurves[0]:
|
||||
raise TypeError("Both segments must belong to the same curve")
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set("COMPUTE_CURVATURE", True)
|
||||
|
||||
prev_segment_fn = ifcopenshell_wrapper.map_shape(settings, prev_segment.wrapped_data)
|
||||
prev_segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, prev_segment_fn)
|
||||
e = prev_segment_evaluator.evaluate(prev_segment_fn.end())
|
||||
end = np.array(e)
|
||||
|
||||
# must add the new segment to the container before mapping it, otherwise the segment doesn't
|
||||
# have enough context to know if it is for horizontal, vertical, cant
|
||||
|
||||
segment_fn = ifcopenshell_wrapper.map_shape(settings, segment.wrapped_data)
|
||||
segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, segment_fn)
|
||||
s = segment_evaluator.evaluate(segment_fn.start())
|
||||
start = np.array(s)
|
||||
|
||||
same_position = True if np.allclose(end[:3], start[:3]) else False
|
||||
same_gradient = True if np.allclose(end[:0], start[:0]) else False
|
||||
same_curvature = True if np.allclose(end[3:], start[3:]) else False
|
||||
|
||||
if same_position:
|
||||
prev_segment.Transition = "CONTINUOUS"
|
||||
if same_gradient:
|
||||
prev_segment.Transition = "CONTSAMEGRADIENT"
|
||||
if same_curvature:
|
||||
prev_segment.Transition = "CONTSAMEGRADIENTSAMECURVATURE"
|
||||
else:
|
||||
prev_segment.Transition = "DISCONTINUOUS"
|
||||
@@ -0,0 +1,134 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import math
|
||||
from typing import Sequence
|
||||
|
||||
import numpy as np
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.template
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell import ifcopenshell_wrapper
|
||||
import ifcopenshell.util
|
||||
import ifcopenshell.util.stationing
|
||||
|
||||
|
||||
def evaluate_representation(shape_rep: entity_instance, dist_along: float) -> np.ndarray:
|
||||
"""
|
||||
Calculate the 4x4 geometric transform at a point on an alignment segment
|
||||
|
||||
: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}"
|
||||
)
|
||||
|
||||
# TODO: confirm point is not beyond limits of alignment
|
||||
|
||||
s = ifcopenshell.geom.settings()
|
||||
function_item = ifcopenshell_wrapper.map_shape(s, shape_rep.wrapped_data)
|
||||
evaluator = ifcopenshell_wrapper.function_item_evaluator(s, function_item)
|
||||
|
||||
trans_matrix = evaluator.evaluate(dist_along)
|
||||
|
||||
return np.array(trans_matrix, dtype=np.float64).T
|
||||
|
||||
|
||||
def evaluate_segment(segment: entity_instance, dist_along: float) -> np.ndarray:
|
||||
"""
|
||||
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)
|
||||
"""
|
||||
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()
|
||||
function_item = ifcopenshell_wrapper.map_shape(s, segment.wrapped_data)
|
||||
evaluator = ifcopenshell_wrapper.function_item_evaluator(s, function_item)
|
||||
|
||||
trans_matrix = evaluator.evaluate(dist_along)
|
||||
|
||||
return np.array(trans_matrix, dtype=np.float64).T
|
||||
|
||||
|
||||
def generate_vertices(rep_curve: entity_instance, distance_interval: float = 5.0) -> np.ndarray:
|
||||
"""
|
||||
Generate vertices along an alignment
|
||||
|
||||
:param rep_curve: The alignment's representation curve to use to generate vertices.
|
||||
:param distance_interval: The distance between points along the alignment at which to generate the points
|
||||
"""
|
||||
if rep_curve is None:
|
||||
raise ValueError("Alignment representation not found.")
|
||||
|
||||
supported_rep_types = ["IFCCOMPOSITECURVE", "IFCGRADIENTCURVE", "IFCSEGMENTEDREFERENCECURVE"]
|
||||
shape_rep_type = rep_curve.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}"
|
||||
)
|
||||
|
||||
s = ifcopenshell.geom.settings()
|
||||
s.set("piecewise-step-type", 0) # 0 = step-size is maximum step size, 1 = step-size is mininimum number of steps
|
||||
s.set("piecewise-step-size", distance_interval)
|
||||
shape = ifcopenshell.geom.create_shape(s, rep_curve)
|
||||
vertices = shape.verts
|
||||
if len(vertices) == 0:
|
||||
msg = f"[ERROR] No vertices generated by ifcopenshell.geom.create_shape()."
|
||||
raise ValueError(msg)
|
||||
return np.array(vertices).reshape((-1, 3))
|
||||
|
||||
|
||||
def print_alignment(alignment, indent=0):
|
||||
"""
|
||||
Debugging function to print alignment decomposition
|
||||
"""
|
||||
print(" " * indent, alignment)
|
||||
|
||||
for rel in alignment.IsNestedBy:
|
||||
for child in rel.RelatedObjects:
|
||||
print_alignment(child, indent + 2)
|
||||
|
||||
for agg in alignment.IsDecomposedBy:
|
||||
for child in agg.RelatedObjects:
|
||||
print_alignment(child, indent + 2)
|
||||
|
||||
|
||||
def print_composite_curve(curve):
|
||||
"""
|
||||
Debugging function to print composite curve segments
|
||||
"""
|
||||
print(str(curve)[0:100])
|
||||
|
||||
for segment in curve.Segments:
|
||||
print(" " * 2, segment)
|
||||
@@ -68,6 +68,7 @@ SETTING = Literal[
|
||||
"building-local-placement",
|
||||
"cgal-original-edges",
|
||||
"circle-segments",
|
||||
"compute-curvature",
|
||||
"context-identifiers",
|
||||
"context-ids",
|
||||
"context-types",
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# 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 pytest
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.context
|
||||
|
||||
|
||||
def test_add_segment_to_curve():
|
||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||
project = file.createIfcProject(Name="Test")
|
||||
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,
|
||||
)
|
||||
|
||||
circular_arc = file.createIfcCurveSegment(
|
||||
Placement=file.createIfcAxis2Placement2d(
|
||||
file.createIfcCartesianPoint((4084.115884, 3889.462938)),
|
||||
file.createIfcDirection((0.224530986099614, 0.974466949814685)),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(-1848.115835),
|
||||
ParentCurve=file.createIfcCircle(
|
||||
Position=file.createIfcAxis2Placement2d(
|
||||
file.createIfcCartesianPoint((0.0, 0.0)), file.createIfcDirection((1.0, 0.0))
|
||||
),
|
||||
Radius=1250.0,
|
||||
),
|
||||
)
|
||||
|
||||
line = file.createIfcCurveSegment(
|
||||
Placement=file.createIfcAxis2Placement2d(
|
||||
file.createIfcCartesianPoint((5469.395067, 4847.56631)),
|
||||
file.createIfcDirection((0.991014275066766, -0.133756146078947)),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(1564.635765),
|
||||
ParentCurve=file.createIfcLine(
|
||||
Pnt=file.createIfcCartesianPoint((0.0, 0.0)),
|
||||
Dir=file.createIfcVector(Orientation=file.createIfcDirection((1.0, 0.0)), Magnitude=1.0),
|
||||
),
|
||||
)
|
||||
|
||||
composite_curve = file.createIfcCompositeCurve(SelfIntersect=False)
|
||||
|
||||
ifcopenshell.api.alignment.add_segment_to_curve(file, circular_arc, composite_curve)
|
||||
assert circular_arc.UsingCurves[0] == composite_curve
|
||||
assert composite_curve.Segments[-1] == circular_arc
|
||||
|
||||
ifcopenshell.api.alignment.add_segment_to_curve(file, line, composite_curve)
|
||||
assert line.UsingCurves[0] == composite_curve
|
||||
assert composite_curve.Segments[-1] == line
|
||||
@@ -0,0 +1,75 @@
|
||||
# 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 pytest
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.context
|
||||
|
||||
|
||||
def test_add_segment_to_layout():
|
||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||
project = file.createIfcProject(Name="Test")
|
||||
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,
|
||||
)
|
||||
|
||||
horizontal_alignment = file.create_entity(
|
||||
type="IfcAlignmentHorizontal",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
)
|
||||
|
||||
design_parameters = file.create_entity(
|
||||
type="IfcAlignmentHorizontalSegment",
|
||||
StartTag=None,
|
||||
EndTag=None,
|
||||
StartPoint=file.createIfcCartesianPoint(Coordinates=((0.0, 0.0))),
|
||||
StartDirection=0.0,
|
||||
StartRadiusOfCurvature=0.0,
|
||||
EndRadiusOfCurvature=0.0,
|
||||
SegmentLength=100.0,
|
||||
GravityCenterLineHeight=None,
|
||||
PredefinedType="LINE",
|
||||
)
|
||||
alignment_segment = file.create_entity(
|
||||
type="IfcAlignmentSegment",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=None,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=None,
|
||||
Representation=None,
|
||||
DesignParameters=design_parameters,
|
||||
)
|
||||
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, horizontal_alignment, alignment_segment)
|
||||
|
||||
assert len(horizontal_alignment.IsNestedBy) == 1
|
||||
assert len(horizontal_alignment.IsNestedBy[0].RelatedObjects) == 1
|
||||
assert horizontal_alignment.IsNestedBy[0].RelatedObjects[0] == alignment_segment
|
||||
@@ -0,0 +1,56 @@
|
||||
# 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 pytest
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.context
|
||||
|
||||
|
||||
def test_add_stationing_to_alignment():
|
||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||
project = file.createIfcProject(Name="Test")
|
||||
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,
|
||||
)
|
||||
|
||||
coordinates = [(500.0, 2500.0), (3340.0, 660.0), (4340.0, 5000.0), (7600.0, 4560.0), (8480.0, 2010.0)]
|
||||
radii = [(1000.0), (1250.0), (950.0)]
|
||||
vpoints = [(0.0, 100.0), (2000.0, 135.0), (5000.0, 105.0), (7400.0, 153.0), (9800.0, 105.0), (12800.0, 90.0)]
|
||||
lengths = [(1600.0), (1200.0), (2000.0), (800.0)]
|
||||
|
||||
alignment = ifcopenshell.api.alignment.create_alignment_by_pi_method(
|
||||
file, "TestAlignment", coordinates, radii, vpoints, lengths
|
||||
)
|
||||
|
||||
ifcopenshell.api.alignment.add_stationing_to_alignment(file, alignment, 2000.0)
|
||||
|
||||
for rel in alignment.IsNestedBy:
|
||||
for referent in rel.RelatedObjects:
|
||||
if referent.is_a("IfcReferent"):
|
||||
assert referent.PredefinedType == "STATION"
|
||||
assert referent.Name == "2+000.000"
|
||||
assert ifcopenshell.util.element.get_pset(element=referent, name="Pset_Stationing")
|
||||
assert (
|
||||
ifcopenshell.util.element.get_pset(element=referent, name="Pset_Stationing", prop="Station")
|
||||
== 2000.0
|
||||
)
|
||||
@@ -0,0 +1,74 @@
|
||||
# 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 pytest
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.context
|
||||
|
||||
|
||||
def test_add_vertical_by_pi_method():
|
||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||
project = file.createIfcProject(Name="Test")
|
||||
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,
|
||||
)
|
||||
|
||||
coordinates = [(500.0, 2500.0), (3340.0, 660.0), (4340.0, 5000.0), (7600.0, 4560.0), (8480.0, 2010.0)]
|
||||
radii = [(1000.0), (1250.0), (950.0)]
|
||||
vpoints = [(0.0, 100.0), (2000.0, 135.0), (5000.0, 105.0), (7400.0, 153.0), (9800.0, 105.0), (12800.0, 90.0)]
|
||||
lengths = [(1600.0), (1200.0), (2000.0), (800.0)]
|
||||
|
||||
# single horizontal alignment
|
||||
alignment = ifcopenshell.api.alignment.create_alignment_by_pi_method(file, "TestAlignment", coordinates, radii)
|
||||
|
||||
assert len(alignment.IsDecomposedBy) == 0 # no child alignments
|
||||
assert len(alignment.IsNestedBy) == 1 # nesting IfcAlignemtHorizontal
|
||||
assert len(alignment.IsNestedBy[0].RelatedObjects) == 1 # nesting one IfcAlignmentHorizontal
|
||||
assert alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcAlignmentHorizontal")
|
||||
assert (
|
||||
len(alignment.IsNestedBy[0].RelatedObjects[0].IsNestedBy) == 1
|
||||
) # nesting of segments beneath IfcAlignmentHorizontal
|
||||
assert len(alignment.IsNestedBy[0].RelatedObjects[0].IsNestedBy[0].RelatedObjects) == 8 # segments
|
||||
|
||||
# add first vertical
|
||||
ifcopenshell.api.alignment.add_vertical_alignment_by_pi_method(file, alignment, vpoints, lengths)
|
||||
assert len(alignment.IsDecomposedBy) == 0 # no child alignments
|
||||
assert len(alignment.IsNestedBy) == 1 # 1 nesting relationsip for the alignments
|
||||
assert len(alignment.IsNestedBy[0].RelatedObjects) == 2 # nesting IfcAlignmentHorizontal and IfcAlignmentVertical
|
||||
assert alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcAlignmentHorizontal")
|
||||
assert alignment.IsNestedBy[0].RelatedObjects[1].is_a("IfcAlignmentVertical")
|
||||
|
||||
# add second vertical
|
||||
ifcopenshell.api.alignment.add_vertical_alignment_by_pi_method(file, alignment, vpoints, lengths)
|
||||
assert len(alignment.IsDecomposedBy) == 1 # 1 IfcRelAggreates relationship for the child algiments
|
||||
assert (
|
||||
len(alignment.IsDecomposedBy[0].RelatedObjects) == 2
|
||||
) # two child alignments, one for the first vertical and one for the vertical just added
|
||||
for child_alignment in alignment.IsDecomposedBy[0].RelatedObjects:
|
||||
assert child_alignment.is_a("IfcAlignment")
|
||||
assert len(child_alignment.IsNestedBy) == 1 # one nesting relationship for the IfcAlignmentVertical
|
||||
assert len(child_alignment.IsNestedBy[0].RelatedObjects) == 1 # The IfcAlignmentVertical
|
||||
assert child_alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcAlignmentVertical")
|
||||
assert len(alignment.IsNestedBy) == 1 # 1 nesting relationsip for the alignments
|
||||
assert len(alignment.IsNestedBy[0].RelatedObjects) == 1 # nesting one IfcAlignmentHorizontal
|
||||
assert alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcAlignmentHorizontal")
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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 pytest
|
||||
|
||||
# import test.bootstrap
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.context
|
||||
|
||||
|
||||
# class TestGetBasisCurve(test.bootstrap.IFC4X3):
|
||||
def test_horizontal():
|
||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||
project = file.createIfcProject(Name="Test")
|
||||
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,
|
||||
)
|
||||
|
||||
coordinates = [(500.0, 2500.0), (3340.0, 660.0), (4340.0, 5000.0), (7600.0, 4560.0), (8480.0, 2010.0)]
|
||||
radii = [(1000.0), (1250.0), (950.0)]
|
||||
|
||||
alignment = ifcopenshell.api.alignment.create_alignment_by_pi_method(file, "TestAlignment", coordinates, radii)
|
||||
ifcopenshell.api.alignment.create_geometric_representation(file, alignment)
|
||||
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
assert basis_curve.is_a("IfcCompositeCurve")
|
||||
|
||||
|
||||
def test_horizontal_and_vertical():
|
||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||
project = file.createIfcProject(Name="Test")
|
||||
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,
|
||||
)
|
||||
|
||||
coordinates = [(500.0, 2500.0), (3340.0, 660.0), (4340.0, 5000.0), (7600.0, 4560.0), (8480.0, 2010.0)]
|
||||
radii = [(1000.0), (1250.0), (950.0)]
|
||||
vpoints = [(0.0, 100.0), (2000.0, 135.0), (5000.0, 105.0), (7400.0, 153.0), (9800.0, 105.0), (12800.0, 90.0)]
|
||||
lengths = [(1600.0), (1200.0), (2000.0), (800.0)]
|
||||
|
||||
alignment = ifcopenshell.api.alignment.create_alignment_by_pi_method(
|
||||
file, "TestAlignment", coordinates, radii, vpoints, lengths
|
||||
)
|
||||
ifcopenshell.api.alignment.create_geometric_representation(file, alignment)
|
||||
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
assert basis_curve.is_a("IfcCompositeCurve")
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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 pytest
|
||||
|
||||
# import test.bootstrap
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.context
|
||||
|
||||
|
||||
# class TestGetCurve(test.bootstrap.IFC4X3):
|
||||
def test_horizontal():
|
||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||
project = file.createIfcProject(Name="Test")
|
||||
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,
|
||||
)
|
||||
|
||||
coordinates = [(500.0, 2500.0), (3340.0, 660.0), (4340.0, 5000.0), (7600.0, 4560.0), (8480.0, 2010.0)]
|
||||
radii = [(1000.0), (1250.0), (950.0)]
|
||||
|
||||
alignment = ifcopenshell.api.alignment.create_alignment_by_pi_method(file, "TestAlignment", coordinates, radii)
|
||||
ifcopenshell.api.alignment.create_geometric_representation(file, alignment)
|
||||
curve = ifcopenshell.api.alignment.get_curve(alignment)
|
||||
assert curve.is_a("IfcCompositeCurve")
|
||||
|
||||
|
||||
def test_horizontal_and_vertical():
|
||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||
project = file.createIfcProject(Name="Test")
|
||||
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,
|
||||
)
|
||||
|
||||
coordinates = [(500.0, 2500.0), (3340.0, 660.0), (4340.0, 5000.0), (7600.0, 4560.0), (8480.0, 2010.0)]
|
||||
radii = [(1000.0), (1250.0), (950.0)]
|
||||
vpoints = [(0.0, 100.0), (2000.0, 135.0), (5000.0, 105.0), (7400.0, 153.0), (9800.0, 105.0), (12800.0, 90.0)]
|
||||
lengths = [(1600.0), (1200.0), (2000.0), (800.0)]
|
||||
|
||||
alignment = ifcopenshell.api.alignment.create_alignment_by_pi_method(
|
||||
file, "TestAlignment", coordinates, radii, vpoints, lengths
|
||||
)
|
||||
ifcopenshell.api.alignment.create_geometric_representation(file, alignment)
|
||||
curve = ifcopenshell.api.alignment.get_curve(alignment)
|
||||
assert curve.is_a("IfcGradientCurve")
|
||||
@@ -0,0 +1,123 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import pytest
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.alignment.has_zero_length_segment
|
||||
import ifcopenshell.api.alignment.remove_zero_length_segment
|
||||
import ifcopenshell.api.context
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.api.nest
|
||||
|
||||
|
||||
def _test_business_definition():
|
||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||
project = file.createIfcProject(Name="Test")
|
||||
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,
|
||||
)
|
||||
|
||||
horizontal = file.createIfcAlignmentHorizontal("Horizontal Alignment")
|
||||
design_parameters = file.createIfcAlignmentHorizontalSegment(
|
||||
StartPoint=file.createIfcCartesianPoint((0.0, 0.0)),
|
||||
StartDirection=0.0,
|
||||
SegmentLength=100.0,
|
||||
PredefinedType="LINE",
|
||||
)
|
||||
segment = file.createIfcAlignmentSegment(GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters)
|
||||
ifcopenshell.api.nest.assign_object(
|
||||
file,
|
||||
related_objects=[
|
||||
segment,
|
||||
],
|
||||
relating_object=horizontal,
|
||||
)
|
||||
|
||||
assert False == ifcopenshell.api.alignment.has_zero_length_segment(horizontal)
|
||||
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(file, horizontal)
|
||||
assert len(horizontal.IsNestedBy[0].RelatedObjects) == 2
|
||||
|
||||
assert True == ifcopenshell.api.alignment.has_zero_length_segment(horizontal)
|
||||
|
||||
zero_length_segment = ifcopenshell.api.alignment.remove_zero_length_segment(file, horizontal)
|
||||
assert len(horizontal.IsNestedBy[0].RelatedObjects) == 1
|
||||
assert False == ifcopenshell.api.alignment.has_zero_length_segment(horizontal)
|
||||
|
||||
ifcopenshell.api.alignment.add_segment_to_layout(file, horizontal, zero_length_segment)
|
||||
assert len(horizontal.IsNestedBy[0].RelatedObjects) == 2
|
||||
assert True == ifcopenshell.api.alignment.has_zero_length_segment(horizontal)
|
||||
|
||||
|
||||
def _test_geometric_definition():
|
||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||
project = file.createIfcProject(Name="Test")
|
||||
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,
|
||||
)
|
||||
circular_arc = file.createIfcCurveSegment(
|
||||
Placement=file.createIfcAxis2Placement2d(
|
||||
file.createIfcCartesianPoint((4084.115884, 3889.462938)),
|
||||
file.createIfcDirection((0.224530986099614, 0.974466949814685)),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(-1848.115835),
|
||||
ParentCurve=file.createIfcCircle(
|
||||
Position=file.createIfcAxis2Placement2d(
|
||||
file.createIfcCartesianPoint((0.0, 0.0)), file.createIfcDirection((1.0, 0.0))
|
||||
),
|
||||
Radius=1250.0,
|
||||
),
|
||||
)
|
||||
|
||||
composite_curve = file.createIfcCompositeCurve(Segments=(circular_arc,), SelfIntersect=False)
|
||||
|
||||
assert False == ifcopenshell.api.alignment.has_zero_length_segment(composite_curve)
|
||||
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(file, composite_curve)
|
||||
|
||||
assert True == ifcopenshell.api.alignment.has_zero_length_segment(composite_curve)
|
||||
|
||||
assert len(composite_curve.Segments) == 2
|
||||
|
||||
zero_length_segment = ifcopenshell.api.alignment.remove_zero_length_segment(file, composite_curve)
|
||||
assert len(composite_curve.Segments) == 1
|
||||
assert False == ifcopenshell.api.alignment.has_zero_length_segment(composite_curve)
|
||||
|
||||
ifcopenshell.api.alignment.add_segment_to_curve(file, zero_length_segment, composite_curve)
|
||||
assert len(composite_curve.Segments) == 2
|
||||
assert True == ifcopenshell.api.alignment.has_zero_length_segment(composite_curve)
|
||||
|
||||
segment = composite_curve.Segments[-1]
|
||||
assert segment.Placement.Location.Coordinates == (5469.394535876198, 4847.567078630914)
|
||||
assert segment.Placement.RefDirection.DirectionRatios == (0.9910142986043448, -0.13375597168627318)
|
||||
|
||||
|
||||
def test_has_zero_length_segment():
|
||||
_test_business_definition()
|
||||
_test_geometric_definition()
|
||||
@@ -0,0 +1,26 @@
|
||||
# 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 pytest
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.context
|
||||
|
||||
|
||||
def test_map_alignment_cant_segment():
|
||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||
# create tests as the mapping functions are implemented
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,102 @@
|
||||
# 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 pytest
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.context
|
||||
|
||||
|
||||
def test_map_alignment_horizontal_segment():
|
||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||
project = file.createIfcProject(Name="Test")
|
||||
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,
|
||||
)
|
||||
|
||||
coordinates = [(500.0, 2500.0), (3340.0, 660.0), (4340.0, 5000.0), (7600.0, 4560.0), (8480.0, 2010.0)]
|
||||
radii = [(1000.0), (1250.0), (950.0)]
|
||||
vpoints = [(0.0, 100.0), (2000.0, 135.0), (5000.0, 105.0), (7400.0, 153.0), (9800.0, 105.0), (12800.0, 90.0)]
|
||||
lengths = [(1600.0), (1200.0), (2000.0), (800.0)]
|
||||
|
||||
alignment = ifcopenshell.api.alignment.create_alignment_by_pi_method(
|
||||
file, "TestAlignment", coordinates, radii, vpoints, lengths
|
||||
)
|
||||
|
||||
horizontal_alignment = alignment.IsNestedBy[0].RelatedObjects[0]
|
||||
assert horizontal_alignment.is_a("IfcAlignmentHorizontal")
|
||||
|
||||
composite_curve = file.create_entity(
|
||||
type="IfcCompositeCurve",
|
||||
Segments=[],
|
||||
SelfIntersect=False,
|
||||
)
|
||||
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, horizontal_alignment, composite_curve)
|
||||
assert len(composite_curve.Segments) == 8
|
||||
assert composite_curve.Segments[0].ParentCurve.is_a("IfcLine")
|
||||
assert composite_curve.Segments[0].Transition == "CONTSAMEGRADIENT"
|
||||
assert composite_curve.Segments[1].ParentCurve.is_a("IfcCircle")
|
||||
assert composite_curve.Segments[1].Transition == "CONTSAMEGRADIENT"
|
||||
assert composite_curve.Segments[2].ParentCurve.is_a("IfcLine")
|
||||
assert composite_curve.Segments[2].Transition == "CONTSAMEGRADIENT"
|
||||
assert composite_curve.Segments[3].ParentCurve.is_a("IfcCircle")
|
||||
assert composite_curve.Segments[3].Transition == "CONTSAMEGRADIENT"
|
||||
assert composite_curve.Segments[4].ParentCurve.is_a("IfcLine")
|
||||
assert composite_curve.Segments[4].Transition == "CONTSAMEGRADIENT"
|
||||
assert composite_curve.Segments[5].ParentCurve.is_a("IfcCircle")
|
||||
assert composite_curve.Segments[5].Transition == "CONTSAMEGRADIENT"
|
||||
assert composite_curve.Segments[6].ParentCurve.is_a("IfcLine")
|
||||
assert composite_curve.Segments[6].Transition == "CONTSAMEGRADIENTSAMECURVATURE"
|
||||
assert composite_curve.Segments[7].ParentCurve.is_a("IfcLine")
|
||||
assert composite_curve.Segments[7].Transition == "DISCONTINUOUS"
|
||||
|
||||
vertical_alignment = alignment.IsNestedBy[0].RelatedObjects[1]
|
||||
assert vertical_alignment.is_a("IfcAlignmentVertical")
|
||||
|
||||
gradient_curve = file.create_entity(
|
||||
type="IfcGradientCurve", Segments=[], SelfIntersect=False, BaseCurve=composite_curve, EndPoint=None
|
||||
)
|
||||
|
||||
ifcopenshell.api.alignment.map_alignment_segments(file, vertical_alignment, gradient_curve)
|
||||
assert len(gradient_curve.Segments) == 10
|
||||
|
||||
assert gradient_curve.Segments[0].ParentCurve.is_a("IfcLine")
|
||||
assert gradient_curve.Segments[0].Transition == "CONTSAMEGRADIENT"
|
||||
assert gradient_curve.Segments[1].ParentCurve.is_a("IfcPolynomialCurve")
|
||||
assert gradient_curve.Segments[1].Transition == "CONTSAMEGRADIENT"
|
||||
assert gradient_curve.Segments[2].ParentCurve.is_a("IfcLine")
|
||||
assert gradient_curve.Segments[2].Transition == "CONTSAMEGRADIENT"
|
||||
assert gradient_curve.Segments[3].ParentCurve.is_a("IfcPolynomialCurve")
|
||||
assert gradient_curve.Segments[3].Transition == "CONTSAMEGRADIENT"
|
||||
assert gradient_curve.Segments[4].ParentCurve.is_a("IfcLine")
|
||||
assert gradient_curve.Segments[4].Transition == "CONTSAMEGRADIENT"
|
||||
assert gradient_curve.Segments[5].ParentCurve.is_a("IfcPolynomialCurve")
|
||||
assert gradient_curve.Segments[5].Transition == "CONTSAMEGRADIENT"
|
||||
assert gradient_curve.Segments[6].ParentCurve.is_a("IfcLine")
|
||||
assert gradient_curve.Segments[6].Transition == "CONTSAMEGRADIENT"
|
||||
assert gradient_curve.Segments[7].ParentCurve.is_a("IfcPolynomialCurve")
|
||||
assert gradient_curve.Segments[7].Transition == "CONTSAMEGRADIENT"
|
||||
assert gradient_curve.Segments[8].ParentCurve.is_a("IfcLine")
|
||||
assert gradient_curve.Segments[8].Transition == "CONTSAMEGRADIENTSAMECURVATURE"
|
||||
assert gradient_curve.Segments[9].ParentCurve.is_a("IfcLine")
|
||||
assert gradient_curve.Segments[9].Transition == "DISCONTINUOUS"
|
||||
@@ -0,0 +1,795 @@
|
||||
# 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/>.
|
||||
|
||||
# These are test cases generated from https://github.com/bSI-RailwayRoom/IFC-Rail-Unit-Test-Reference-Code/tree/master/alignment_testset/IFC-WithGeneratedGeometry
|
||||
# for vertical alignment.
|
||||
|
||||
import pytest
|
||||
import ifcopenshell.api.alignment
|
||||
|
||||
|
||||
def _CircularArc_100_0_10_0_0_0_0_5_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=0.0,
|
||||
EndGradient=0.5,
|
||||
PredefinedType="CIRCULARARC",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(1053.72220965611)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(103.674757133105)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcCircle")
|
||||
assert mapped_segment.ParentCurve.Position.Location.Coordinates == pytest.approx((-0.0, 223.606797749979))
|
||||
assert mapped_segment.ParentCurve.Position.RefDirection.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Radius == pytest.approx(223.606797749979)
|
||||
|
||||
|
||||
def _CircularArc_100_0_10_0_0_0__0_5_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=0.0,
|
||||
EndGradient=-0.5,
|
||||
PredefinedType="CIRCULARARC",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(351.240736552036)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(-103.674757133105)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcCircle")
|
||||
assert mapped_segment.ParentCurve.Position.Location.Coordinates == pytest.approx(
|
||||
(-1.36919674566051e-14, -223.606797749979)
|
||||
)
|
||||
assert mapped_segment.ParentCurve.Position.RefDirection.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Radius == pytest.approx(223.606797749979)
|
||||
|
||||
|
||||
def _CircularArc_100_0_10_0_0_5_0_0_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=0.5,
|
||||
EndGradient=0.0,
|
||||
PredefinedType="CIRCULARARC",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.894427190999916, 0.447213595499958)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(454.915493685141)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(-103.674757133105)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcCircle")
|
||||
assert mapped_segment.ParentCurve.Position.Location.Coordinates == pytest.approx((100.0, -200.0))
|
||||
assert mapped_segment.ParentCurve.Position.RefDirection.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Radius == pytest.approx(223.606797749979)
|
||||
|
||||
|
||||
def _CircularArc_100_0_10_0__0_5_0_0_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=-0.5,
|
||||
EndGradient=0.0,
|
||||
PredefinedType="CIRCULARARC",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.894427190999916, -0.447213595499958)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(950.047452523004)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(103.674757133105)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcCircle")
|
||||
assert mapped_segment.ParentCurve.Position.Location.Coordinates == pytest.approx((100.0, 200.0))
|
||||
assert mapped_segment.ParentCurve.Position.RefDirection.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Radius == pytest.approx(223.606797749979)
|
||||
|
||||
|
||||
def _CircularArc_100_0_10_0_0_5_1_0_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=0.5,
|
||||
EndGradient=1.0,
|
||||
PredefinedType="CIRCULARARC",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.894427190999916, 0.447213595499958)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(1991.60150186753)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(123.801073716741)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcCircle")
|
||||
assert mapped_segment.ParentCurve.Position.Location.Coordinates == pytest.approx(
|
||||
(-172.075922005613, 344.151844011225)
|
||||
)
|
||||
assert mapped_segment.ParentCurve.Position.RefDirection.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Radius == pytest.approx(384.773458895502)
|
||||
|
||||
|
||||
def _CircularArc_100_0_10_0__0_5__1_0_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=-0.5,
|
||||
EndGradient=-1.0,
|
||||
PredefinedType="CIRCULARARC",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.894427190999916, -0.447213595499958)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(426.001441657352)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(-123.801073716741)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcCircle")
|
||||
assert mapped_segment.ParentCurve.Position.Location.Coordinates == pytest.approx(
|
||||
(-172.075922005613, -344.151844011225)
|
||||
)
|
||||
assert mapped_segment.ParentCurve.Position.RefDirection.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Radius == pytest.approx(384.773458895502)
|
||||
|
||||
|
||||
def _CircularArc_100_0_10_0_1_0_0_5_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=1.0,
|
||||
EndGradient=0.5,
|
||||
PredefinedType="CIRCULARARC",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.707106781186547, 0.707106781186547)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(906.601103821832)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(-123.801073716741)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcCircle")
|
||||
assert mapped_segment.ParentCurve.Position.Location.Coordinates == pytest.approx(
|
||||
(272.075922005613, -272.075922005613)
|
||||
)
|
||||
assert mapped_segment.ParentCurve.Position.RefDirection.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Radius == pytest.approx(384.773458895502)
|
||||
|
||||
|
||||
def _CircularArc_100_0_10_0__1_0__0_5_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=-1.0,
|
||||
EndGradient=-0.5,
|
||||
PredefinedType="CIRCULARARC",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.707106781186547, -0.707106781186547)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(1511.00183970305)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(123.801073716741)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcCircle")
|
||||
assert mapped_segment.ParentCurve.Position.Location.Coordinates == pytest.approx(
|
||||
(272.075922005613, 272.075922005613)
|
||||
)
|
||||
assert mapped_segment.ParentCurve.Position.RefDirection.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Radius == pytest.approx(384.773458895502)
|
||||
|
||||
|
||||
def _ConstantGradient_100_0_10_0_0_0_0_5_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=0.0,
|
||||
EndGradient=0.5,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(0.0)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(100.0)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcLine")
|
||||
assert mapped_segment.ParentCurve.Pnt.Coordinates == pytest.approx((0.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Dir.Orientation.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Dir.Magnitude == pytest.approx(1.0)
|
||||
|
||||
|
||||
def _ConstantGradient_100_0_10_0_0_0__0_5_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=0.0,
|
||||
EndGradient=-0.5,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(0.0)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(100.0)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcLine")
|
||||
assert mapped_segment.ParentCurve.Pnt.Coordinates == pytest.approx((0.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Dir.Orientation.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Dir.Magnitude == pytest.approx(1.0)
|
||||
|
||||
|
||||
def _ConstantGradient_100_0_10_0_0_5_0_0_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=0.5,
|
||||
EndGradient=0.0,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.894427190999916, 0.447213595499958)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(0.0)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(111.803398874989)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcLine")
|
||||
assert mapped_segment.ParentCurve.Pnt.Coordinates == pytest.approx((0.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Dir.Orientation.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Dir.Magnitude == pytest.approx(1.0)
|
||||
|
||||
|
||||
def _ConstantGradient_100_0_10_0__0_5_0_0_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=-0.5,
|
||||
EndGradient=0.0,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.894427190999916, -0.447213595499958)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(0.0)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(111.803398874989)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcLine")
|
||||
assert mapped_segment.ParentCurve.Pnt.Coordinates == pytest.approx((0.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Dir.Orientation.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Dir.Magnitude == pytest.approx(1.0)
|
||||
|
||||
|
||||
def _ConstantGradient_100_0_10_0_0_5_1_0_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=0.5,
|
||||
EndGradient=1.0,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.894427190999916, 0.447213595499958)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(0.0)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(111.803398874989)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcLine")
|
||||
assert mapped_segment.ParentCurve.Pnt.Coordinates == pytest.approx((0.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Dir.Orientation.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Dir.Magnitude == pytest.approx(1.0)
|
||||
|
||||
|
||||
def _ConstantGradient_100_0_10_0__0_5__1_0_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=-0.5,
|
||||
EndGradient=-1.0,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.894427190999916, -0.447213595499958)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(0.0)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(111.803398874989)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcLine")
|
||||
assert mapped_segment.ParentCurve.Pnt.Coordinates == pytest.approx((0.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Dir.Orientation.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Dir.Magnitude == pytest.approx(1.0)
|
||||
|
||||
|
||||
def _ConstantGradient_100_0_10_0_1_0_0_5_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=1.0,
|
||||
EndGradient=0.5,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.707106781186547, 0.707106781186547)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(0.0)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(141.42135623731)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcLine")
|
||||
assert mapped_segment.ParentCurve.Pnt.Coordinates == pytest.approx((0.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Dir.Orientation.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Dir.Magnitude == pytest.approx(1.0)
|
||||
|
||||
|
||||
def _ConstantGradient_100_0_10_0__1_0__0_5_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=-1.0,
|
||||
EndGradient=-0.5,
|
||||
PredefinedType="CONSTANTGRADIENT",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.707106781186547, -0.707106781186547)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(0.0)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(141.42135623731)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcLine")
|
||||
assert mapped_segment.ParentCurve.Pnt.Coordinates == pytest.approx((0.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Dir.Orientation.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.Dir.Magnitude == pytest.approx(1.0)
|
||||
|
||||
|
||||
def _ParabolicArc_100_0_10_0_0_0_0_5_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=0.0,
|
||||
EndGradient=0.5,
|
||||
PredefinedType="PARABOLICARC",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(0.0)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(104.02288238772185)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcPolynomialCurve")
|
||||
assert mapped_segment.ParentCurve.Position.Location.Coordinates == pytest.approx((0.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.CoefficientsX == pytest.approx((0.0, 1.0))
|
||||
assert mapped_segment.ParentCurve.CoefficientsY == pytest.approx((10.0, 0.0, 0.0025))
|
||||
|
||||
|
||||
def _ParabolicArc_100_0_10_0_0_0__0_5_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=0.0,
|
||||
EndGradient=-0.5,
|
||||
PredefinedType="PARABOLICARC",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx((1.0, 0.0))
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(0.0)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(104.02288238772185)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcPolynomialCurve")
|
||||
assert mapped_segment.ParentCurve.Position.Location.Coordinates == pytest.approx((0.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.CoefficientsX == pytest.approx((0.0, 1.0))
|
||||
assert mapped_segment.ParentCurve.CoefficientsY == pytest.approx((10.0, 0.0, -0.0025))
|
||||
|
||||
|
||||
def _ParabolicArc_100_0_10_0_0_5_0_0_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=0.5,
|
||||
EndGradient=0.0,
|
||||
PredefinedType="PARABOLICARC",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.894427190999916, 0.447213595499958)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(0.0)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(104.02288238772185)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcPolynomialCurve")
|
||||
assert mapped_segment.ParentCurve.Position.Location.Coordinates == pytest.approx((0.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.CoefficientsX == pytest.approx((0.0, 1.0))
|
||||
assert mapped_segment.ParentCurve.CoefficientsY == pytest.approx((10.0, 0.5, -0.0025))
|
||||
|
||||
|
||||
def _ParabolicArc_100_0_10_0__0_5_0_0_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=-0.5,
|
||||
EndGradient=0.0,
|
||||
PredefinedType="PARABOLICARC",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.894427190999916, -0.447213595499958)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(0.0)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(104.02288238772185)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcPolynomialCurve")
|
||||
assert mapped_segment.ParentCurve.Position.Location.Coordinates == pytest.approx((0.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.CoefficientsX == pytest.approx((0.0, 1.0))
|
||||
assert mapped_segment.ParentCurve.CoefficientsY == pytest.approx((10.0, -0.5, 0.0025))
|
||||
|
||||
|
||||
def _ParabolicArc_100_0_10_0_0_5_1_0_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=0.5,
|
||||
EndGradient=1.0,
|
||||
PredefinedType="PARABOLICARC",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.894427190999916, 0.447213595499958)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(0.0)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(125.53583325398947)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcPolynomialCurve")
|
||||
assert mapped_segment.ParentCurve.Position.Location.Coordinates == pytest.approx((0.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.CoefficientsX == pytest.approx((0.0, 1.0))
|
||||
assert mapped_segment.ParentCurve.CoefficientsY == pytest.approx((10.0, 0.5, 0.0025))
|
||||
|
||||
|
||||
def _ParabolicArc_100_0_10_0__0_5__1_0_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=-0.5,
|
||||
EndGradient=-1.0,
|
||||
PredefinedType="PARABOLICARC",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.894427190999916, -0.447213595499958)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(0.0)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(125.53583325398947)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcPolynomialCurve")
|
||||
assert mapped_segment.ParentCurve.Position.Location.Coordinates == pytest.approx((0.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.CoefficientsX == pytest.approx((0.0, 1.0))
|
||||
assert mapped_segment.ParentCurve.CoefficientsY == pytest.approx((10.0, -0.5, -0.0025))
|
||||
|
||||
|
||||
def _ParabolicArc_100_0_10_0_1_0_0_5_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=1.0,
|
||||
EndGradient=0.5,
|
||||
PredefinedType="PARABOLICARC",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.707106781186547, 0.707106781186547)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(0.0)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(125.53583325398947)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcPolynomialCurve")
|
||||
assert mapped_segment.ParentCurve.Position.Location.Coordinates == pytest.approx((0.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.CoefficientsX == pytest.approx((0.0, 1.0))
|
||||
assert mapped_segment.ParentCurve.CoefficientsY == pytest.approx((10.0, 1.0, -0.0025))
|
||||
|
||||
|
||||
def _ParabolicArc_100_0_10_0__1_0__0_5_1_Meter(file):
|
||||
design_parameters = file.createIfcAlignmentVerticalSegment(
|
||||
StartDistAlong=0.0,
|
||||
HorizontalLength=100.0,
|
||||
StartHeight=10.0,
|
||||
StartGradient=-1.0,
|
||||
EndGradient=-0.5,
|
||||
PredefinedType="PARABOLICARC",
|
||||
)
|
||||
|
||||
alignment_segment = file.createIfcAlignmentSegment(
|
||||
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
|
||||
)
|
||||
|
||||
mapped_segments = ifcopenshell.api.alignment.map_alignment_segment(file, alignment_segment)
|
||||
mapped_segment = mapped_segments[0]
|
||||
assert len(mapped_segments) == 2
|
||||
assert mapped_segments[1] == None
|
||||
assert "DISCONTINUOUS" == mapped_segment.Transition
|
||||
assert mapped_segment.Placement.Location.Coordinates == pytest.approx((0.0, 10.0))
|
||||
assert mapped_segment.Placement.RefDirection.DirectionRatios == pytest.approx(
|
||||
(0.707106781186547, -0.707106781186547)
|
||||
)
|
||||
assert mapped_segment.SegmentStart.wrappedValue == pytest.approx(0.0)
|
||||
assert mapped_segment.SegmentLength.wrappedValue == pytest.approx(125.53583325398947)
|
||||
assert mapped_segment.ParentCurve.is_a("IfcPolynomialCurve")
|
||||
assert mapped_segment.ParentCurve.Position.Location.Coordinates == pytest.approx((0.0, 0.0))
|
||||
assert mapped_segment.ParentCurve.CoefficientsX == pytest.approx((0.0, 1.0))
|
||||
assert mapped_segment.ParentCurve.CoefficientsY == pytest.approx((10.0, -1.0, 0.0025))
|
||||
|
||||
|
||||
def test_map_alignment_vertical_segment():
|
||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||
_CircularArc_100_0_10_0_0_0_0_5_1_Meter(file)
|
||||
_CircularArc_100_0_10_0_0_0__0_5_1_Meter(file)
|
||||
_CircularArc_100_0_10_0_0_5_0_0_1_Meter(file)
|
||||
_CircularArc_100_0_10_0__0_5_0_0_1_Meter(file)
|
||||
_CircularArc_100_0_10_0_0_5_1_0_1_Meter(file)
|
||||
_CircularArc_100_0_10_0__0_5__1_0_1_Meter(file)
|
||||
_CircularArc_100_0_10_0_1_0_0_5_1_Meter(file)
|
||||
_CircularArc_100_0_10_0__1_0__0_5_1_Meter(file)
|
||||
_ConstantGradient_100_0_10_0_0_0_0_5_1_Meter(file)
|
||||
_ConstantGradient_100_0_10_0_0_0__0_5_1_Meter(file)
|
||||
_ConstantGradient_100_0_10_0_0_5_0_0_1_Meter(file)
|
||||
_ConstantGradient_100_0_10_0__0_5_0_0_1_Meter(file)
|
||||
_ConstantGradient_100_0_10_0_0_5_1_0_1_Meter(file)
|
||||
_ConstantGradient_100_0_10_0__0_5__1_0_1_Meter(file)
|
||||
_ConstantGradient_100_0_10_0_1_0_0_5_1_Meter(file)
|
||||
_ConstantGradient_100_0_10_0__1_0__0_5_1_Meter(file)
|
||||
_ParabolicArc_100_0_10_0_0_0_0_5_1_Meter(file)
|
||||
_ParabolicArc_100_0_10_0_0_0__0_5_1_Meter(file)
|
||||
_ParabolicArc_100_0_10_0_0_5_0_0_1_Meter(file)
|
||||
_ParabolicArc_100_0_10_0__0_5_0_0_1_Meter(file)
|
||||
_ParabolicArc_100_0_10_0_0_5_1_0_1_Meter(file)
|
||||
_ParabolicArc_100_0_10_0__0_5__1_0_1_Meter(file)
|
||||
_ParabolicArc_100_0_10_0_1_0_0_5_1_Meter(file)
|
||||
_ParabolicArc_100_0_10_0__1_0__0_5_1_Meter(file)
|
||||
|
||||
# VERTICAL CLOTHOID NOT IMPLEMENTED
|
||||
@@ -0,0 +1,53 @@
|
||||
# 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 pytest
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.context
|
||||
|
||||
|
||||
def test_name_segments():
|
||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||
project = file.createIfcProject(Name="Test")
|
||||
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,
|
||||
)
|
||||
|
||||
coordinates = [(500.0, 2500.0), (3340.0, 660.0), (4340.0, 5000.0), (7600.0, 4560.0), (8480.0, 2010.0)]
|
||||
radii = [(1000.0), (1250.0), (950.0)]
|
||||
vpoints = [(0.0, 100.0), (2000.0, 135.0), (5000.0, 105.0), (7400.0, 153.0), (9800.0, 105.0), (12800.0, 90.0)]
|
||||
lengths = [(1600.0), (1200.0), (2000.0), (800.0)]
|
||||
|
||||
alignment = ifcopenshell.api.alignment.create_alignment_by_pi_method(
|
||||
file, "TestAlignment", coordinates, radii, vpoints, lengths
|
||||
)
|
||||
|
||||
for rel in alignment.IsNestedBy:
|
||||
for a in rel.RelatedObjects:
|
||||
if a.is_a("IfcLinearElement"):
|
||||
ifcopenshell.api.alignment.name_segments("Q", a)
|
||||
i = 1
|
||||
for sr in a.IsNestedBy:
|
||||
for s in sr.RelatedObjects:
|
||||
assert f"Q{i}" == s.Name
|
||||
i += 1
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
# 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 pytest
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.context
|
||||
|
||||
|
||||
def _test1():
|
||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||
project = file.createIfcProject(Name="Test")
|
||||
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,
|
||||
)
|
||||
|
||||
# 26=IFCCARTESIANPOINT((4084.115884,3889.462938));
|
||||
# 70=IFCDIRECTION((0.224530986099614,0.974466949814685));
|
||||
# 71=IFCAXIS2PLACEMENT2D(#26,#70);
|
||||
# 72=IFCCARTESIANPOINT((0.,0.));
|
||||
# 73=IFCDIRECTION((1.,0.));
|
||||
# 74=IFCAXIS2PLACEMENT2D(#72,#73);
|
||||
# 75=IFCCIRCLE(#74,1250.);
|
||||
# 76=IFCCURVESEGMENT(.CONTSAMEGRADIENT.,#71,IFCLENGTHMEASURE(0.),IFCLENGTHMEASURE(-1848.115835),#75);
|
||||
circular_arc = file.createIfcCurveSegment(
|
||||
Placement=file.createIfcAxis2Placement2d(
|
||||
file.createIfcCartesianPoint((4084.115884, 3889.462938)),
|
||||
file.createIfcDirection((0.224530986099614, 0.974466949814685)),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(-1848.115835),
|
||||
ParentCurve=file.createIfcCircle(
|
||||
Position=file.createIfcAxis2Placement2d(
|
||||
file.createIfcCartesianPoint((0.0, 0.0)), file.createIfcDirection((1.0, 0.0))
|
||||
),
|
||||
Radius=1250.0,
|
||||
),
|
||||
)
|
||||
|
||||
# 27=IFCCARTESIANPOINT((5469.395067,4847.56631));
|
||||
# 77=IFCDIRECTION((0.991014275066766,-0.133756146078947));
|
||||
# 78=IFCAXIS2PLACEMENT2D(#27,#77);
|
||||
# 79=IFCCARTESIANPOINT((0.,0.));
|
||||
# 80=IFCDIRECTION((1.,0.));
|
||||
# 81=IFCVECTOR(#80,1.);
|
||||
# 82=IFCLINE(#79,#81);
|
||||
# 83=IFCCURVESEGMENT(.CONTSAMEGRADIENT.,#78,IFCLENGTHMEASURE(0.),IFCLENGTHMEASURE(1564.635765),#82);
|
||||
line = file.createIfcCurveSegment(
|
||||
Placement=file.createIfcAxis2Placement2d(
|
||||
file.createIfcCartesianPoint((5469.395067, 4847.56631)),
|
||||
file.createIfcDirection((0.991014275066766, -0.133756146078947)),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(1564.635765),
|
||||
ParentCurve=file.createIfcLine(
|
||||
Pnt=file.createIfcCartesianPoint((0.0, 0.0)),
|
||||
Dir=file.createIfcVector(Orientation=file.createIfcDirection((1.0, 0.0)), Magnitude=1.0),
|
||||
),
|
||||
)
|
||||
|
||||
composite_curve = file.createIfcCompositeCurve(Segments=(circular_arc, line), SelfIntersect=False)
|
||||
|
||||
ifcopenshell.api.alignment.update_curve_segment_transition_code(circular_arc, line)
|
||||
assert circular_arc.Transition == "CONTSAMEGRADIENT"
|
||||
|
||||
|
||||
def _test2():
|
||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||
project = file.createIfcProject(Name="Test")
|
||||
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,
|
||||
)
|
||||
# 30=IFCCARTESIANPOINT((0.,0.));
|
||||
# 31=IFCALIGNMENTHORIZONTALSEGMENT($,$,#30,0.523598775598299,0.,0.,27.8843513637174,$,.LINE.);
|
||||
# 32=IFCALIGNMENTSEGMENT('3$jiMaOgfAoujgvRyMLw0X',$,'H1',$,$,#111,#113,#31);
|
||||
# 33=IFCDIRECTION((0.866025403784439,0.5));
|
||||
# 34=IFCAXIS2PLACEMENT2D(#30,#33);
|
||||
# 35=IFCCARTESIANPOINT((0.,0.));
|
||||
# 36=IFCDIRECTION((1.,0.));
|
||||
# 37=IFCVECTOR(#36,1.);
|
||||
# 38=IFCLINE(#35,#37);
|
||||
# 39=IFCCURVESEGMENT(.CONTSAMEGRADIENT.,#34,IFCLENGTHMEASURE(0.),IFCLENGTHMEASURE(27.8843513637174),#38);
|
||||
|
||||
line1 = file.createIfcCurveSegment(
|
||||
Placement=file.createIfcAxis2Placement2d(
|
||||
file.createIfcCartesianPoint((0.0, 0.0)),
|
||||
file.createIfcDirection((0.866025403784439, 0.5)),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(27.8843513637174),
|
||||
ParentCurve=file.createIfcLine(
|
||||
Pnt=file.createIfcCartesianPoint((0.0, 0.0)),
|
||||
Dir=file.createIfcVector(Orientation=file.createIfcDirection((1.0, 0.0)), Magnitude=1.0),
|
||||
),
|
||||
)
|
||||
|
||||
# 40=IFCCARTESIANPOINT((24.1485566490305,13.9421756818587));
|
||||
# 41=IFCALIGNMENTHORIZONTALSEGMENT($,$,#40,0.523598775598299,0.,1524.,152.4,$,.CLOTHOID.);
|
||||
# 42=IFCALIGNMENTSEGMENT('0Rd38fCkHF1Q11ppiqdMP6',$,'H2',$,$,#111,#115,#41);
|
||||
# 43=IFCDIRECTION((0.866025403784439,0.5));
|
||||
# 44=IFCAXIS2PLACEMENT2D(#40,#43);
|
||||
# 45=IFCCARTESIANPOINT((0.,0.));
|
||||
# 46=IFCDIRECTION((1.,0.));
|
||||
# 47=IFCAXIS2PLACEMENT2D(#45,#46);
|
||||
# 48=IFCCLOTHOID(#47,481.931115409661);
|
||||
# 49=IFCCURVESEGMENT(.CONTSAMEGRADIENT.,#44,IFCLENGTHMEASURE(0.),IFCLENGTHMEASURE(152.4),#48);
|
||||
|
||||
clothoid1 = file.createIfcCurveSegment(
|
||||
Placement=file.createIfcAxis2Placement2d(
|
||||
file.createIfcCartesianPoint((24.1485566490305, 13.9421756818587)),
|
||||
file.createIfcDirection((0.866025403784439, 0.5)),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(152.4),
|
||||
ParentCurve=file.createIfcClothoid(
|
||||
Position=file.createIfcAxis2Placement2d(
|
||||
file.createIfcCartesianPoint((0.0, 0.0)), RefDirection=file.createIfcDirection((1.0, 0.0))
|
||||
),
|
||||
ClothoidConstant=481.931115409661,
|
||||
),
|
||||
)
|
||||
|
||||
# 50=IFCCARTESIANPOINT((154.828063204281,92.32243963907));
|
||||
# 51=IFCALIGNMENTHORIZONTALSEGMENT($,$,#50,0.573598775598299,1524.,1524.,246.582267005904,$,.CIRCULARARC.);
|
||||
# 52=IFCALIGNMENTSEGMENT('2OGYY2lQjCnRlzxKU9vtdu',$,'H3',$,$,#111,#117,#51);
|
||||
# 53=IFCDIRECTION((0.839953512903025,0.542658360445933));
|
||||
# 54=IFCAXIS2PLACEMENT2D(#50,#53);
|
||||
# 55=IFCCARTESIANPOINT((0.,0.));
|
||||
# 56=IFCDIRECTION((1.,0.));
|
||||
# 57=IFCAXIS2PLACEMENT2D(#55,#56);
|
||||
# 58=IFCCIRCLE(#57,1524.);
|
||||
# 59=IFCCURVESEGMENT(.CONTSAMEGRADIENT.,#54,IFCLENGTHMEASURE(0.),IFCLENGTHMEASURE(246.582267005904),#58);
|
||||
|
||||
circular_arc = file.createIfcCurveSegment(
|
||||
Placement=file.createIfcAxis2Placement2d(
|
||||
file.createIfcCartesianPoint((154.828063204281, 92.32243963907)),
|
||||
file.createIfcDirection((0.839953512903025, 0.542658360445933)),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(246.582267005904),
|
||||
ParentCurve=file.createIfcCircle(
|
||||
Position=file.createIfcAxis2Placement2d(
|
||||
file.createIfcCartesianPoint((0.0, 0.0)), file.createIfcDirection((1.0, 0.0))
|
||||
),
|
||||
Radius=1524.0,
|
||||
),
|
||||
)
|
||||
|
||||
# 60=IFCCARTESIANPOINT((350.24160971216,242.268527691248));
|
||||
# 61=IFCALIGNMENTHORIZONTALSEGMENT($,$,#60,0.735398163397447,1524.,0.,152.4,$,.CLOTHOID.);
|
||||
# 62=IFCALIGNMENTSEGMENT('13CGRzUN9CAfNVKnf0Pxza',$,'H4',$,$,#111,#119,#61);
|
||||
# 63=IFCDIRECTION((0.741563691346478,0.670882472327743));
|
||||
# 64=IFCAXIS2PLACEMENT2D(#60,#63);
|
||||
# 65=IFCCARTESIANPOINT((0.,0.));
|
||||
# 66=IFCDIRECTION((1.,0.));
|
||||
# 67=IFCAXIS2PLACEMENT2D(#65,#66);
|
||||
# 68=IFCCLOTHOID(#67,-481.931115409661);
|
||||
# 69=IFCCURVESEGMENT(.CONTSAMEGRADIENT.,#64,IFCLENGTHMEASURE(-152.4),IFCLENGTHMEASURE(152.4),#68);
|
||||
|
||||
clothoid2 = file.createIfcCurveSegment(
|
||||
Placement=file.createIfcAxis2Placement2d(
|
||||
file.createIfcCartesianPoint((350.24160971216, 242.268527691248)),
|
||||
file.createIfcDirection((0.741563691346478, 0.670882472327743)),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(-152.4),
|
||||
SegmentLength=file.createIfcLengthMeasure(152.4),
|
||||
ParentCurve=file.createIfcClothoid(
|
||||
Position=file.createIfcAxis2Placement2d(
|
||||
file.createIfcCartesianPoint((0.0, 0.0)), RefDirection=file.createIfcDirection((1.0, 0.0))
|
||||
),
|
||||
ClothoidConstant=-481.931115409661,
|
||||
),
|
||||
)
|
||||
|
||||
# 70=IFCCARTESIANPOINT((459.773476040884,348.208932967387));
|
||||
# 71=IFCALIGNMENTHORIZONTALSEGMENT($,$,#70,0.785398163397448,0.,0.,0.,$,.LINE.);
|
||||
# 72=IFCALIGNMENTSEGMENT('0FlcTrOfT5YBi0fqVhTMyc',$,'H5',$,$,#111,#121,#71);
|
||||
# 73=IFCDIRECTION((0.707106781186548,0.707106781186548));
|
||||
# 74=IFCAXIS2PLACEMENT2D(#70,#73);
|
||||
# 75=IFCCARTESIANPOINT((0.,0.));
|
||||
# 76=IFCDIRECTION((1.,0.));
|
||||
# 77=IFCVECTOR(#76,1.);
|
||||
# 78=IFCLINE(#75,#77);
|
||||
# 79=IFCCURVESEGMENT(.DISCONTINUOUS.,#74,IFCLENGTHMEASURE(0.),IFCLENGTHMEASURE(0.),#78);
|
||||
|
||||
line2 = file.createIfcCurveSegment(
|
||||
Placement=file.createIfcAxis2Placement2d(
|
||||
file.createIfcCartesianPoint((459.773476040884, 348.208932967387)),
|
||||
file.createIfcDirection((0.707106781186548, 0.707106781186548)),
|
||||
),
|
||||
SegmentStart=file.createIfcLengthMeasure(0.0),
|
||||
SegmentLength=file.createIfcLengthMeasure(0.0),
|
||||
ParentCurve=file.createIfcLine(
|
||||
Pnt=file.createIfcCartesianPoint((0.0, 0.0)),
|
||||
Dir=file.createIfcVector(Orientation=file.createIfcDirection((1.0, 0.0)), Magnitude=1.0),
|
||||
),
|
||||
)
|
||||
|
||||
composite_curve = file.createIfcCompositeCurve(Segments=[], SelfIntersect=False)
|
||||
|
||||
# add_segment_to_curve calls update_curve_segment_transition_code
|
||||
ifcopenshell.api.alignment.add_segment_to_curve(file, line1, composite_curve)
|
||||
assert line1.Transition == "DISCONTINUOUS"
|
||||
|
||||
ifcopenshell.api.alignment.add_segment_to_curve(file, clothoid1, composite_curve)
|
||||
assert line1.Transition == "CONTSAMEGRADIENTSAMECURVATURE"
|
||||
assert clothoid1.Transition == "DISCONTINUOUS"
|
||||
|
||||
ifcopenshell.api.alignment.add_segment_to_curve(file, circular_arc, composite_curve)
|
||||
assert clothoid1.Transition == "CONTSAMEGRADIENTSAMECURVATURE"
|
||||
assert circular_arc.Transition == "DISCONTINUOUS"
|
||||
|
||||
ifcopenshell.api.alignment.add_segment_to_curve(file, clothoid2, composite_curve)
|
||||
assert circular_arc.Transition == "CONTSAMEGRADIENTSAMECURVATURE"
|
||||
assert clothoid2.Transition == "DISCONTINUOUS"
|
||||
|
||||
ifcopenshell.api.alignment.add_segment_to_curve(file, line2, composite_curve)
|
||||
assert clothoid2.Transition == "CONTSAMEGRADIENTSAMECURVATURE"
|
||||
assert line2.Transition == "DISCONTINUOUS"
|
||||
|
||||
|
||||
def test_update_curve_segment_transition_code():
|
||||
_test1()
|
||||
_test2()
|
||||
@@ -718,7 +718,7 @@ std::pair<Ifc4x3_add2::IfcCurveSegment*, Ifc4x3_add2::IfcCurveSegment*> mapAlign
|
||||
// dy/dx = B + 2Cx
|
||||
auto dx = cos(atan(start_gradient));
|
||||
auto dy = sin(atan(start_gradient));
|
||||
auto curve_length_fn = [B, C](double x) { return sqrt(1 + pow(B + C * x, 2)); };
|
||||
auto curve_length_fn = [B, C](double x) { return sqrt(1 + pow(B + 2*C * x, 2)); };
|
||||
auto segment_curve_length = boost::math::quadrature::trapezoidal(curve_length_fn, 0.0, horizontal_length);
|
||||
|
||||
auto curve_segment = new Ifc4x3_add2::IfcCurveSegment(
|
||||
|
||||
Reference in New Issue
Block a user