Schema dispatch in helpers

This commit is contained in:
Thomas Krijnen
2026-07-03 13:26:30 +02:00
parent 55e97e5379
commit 971cef0170
9 changed files with 1690 additions and 477 deletions
+14
View File
@@ -30,6 +30,8 @@ message("Running CMakeLists.txt in /src/helpers")
# (ifcopenshell.util.geolocation)
# * Placement — IfcLocalPlacement / IfcAxis2Placement reduction
# (ifcopenshell.util.placement)
# * Pset — property and quantity retrieval
# (ifcopenshell.util.element)
find_package(Eigen3 REQUIRED)
@@ -38,6 +40,16 @@ file(GLOB HELPER_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
add_library(helpers STATIC ${HELPER_CPP_FILES} ${HELPER_H_FILES})
# The installed headers stay schema-agnostic. Implementations are compiled
# against only the schema plugins selected for this build and dispatch from an
# untyped entry point to generated, compiler-checked entity accessors.
foreach(schema ${SCHEMA_VERSIONS})
target_compile_definitions(helpers PRIVATE
IFCOPENSHELL_HELPER_SCHEMA_${schema}
)
list(APPEND HELPER_SCHEMA_LIBRARIES parse_schema_ifc${schema})
endforeach()
set_target_properties(helpers PROPERTIES
VERSION "${PROJECT_VERSION}"
SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}"
@@ -51,6 +63,8 @@ target_include_directories(helpers PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(helpers PUBLIC
IfcParse
Eigen3::Eigen
PRIVATE
${HELPER_SCHEMA_LIBRARIES}
)
install(TARGETS helpers EXPORT ${IFCOPENSHELL_EXPORT_TARGETS})
+223 -149
View File
@@ -18,166 +18,235 @@
********************************************************************************/
#include "geolocation.h"
#include "placement.h"
#include "../ifcparse/express.h"
#include "../ifcparse/exception.h"
#include "../ifcparse/file.h"
#include "../ifcparse/instance_data.h"
#include "../ifcparse/schema.h"
#include "placement.h"
#include "pset.h"
#include "schema_dispatch.i"
#include <cmath>
#include <string>
#include <type_traits>
#include <vector>
namespace {
// Read a numeric NominalValue out of an IfcPropertySingleValue. IFC2X3
// ePSet_MapConversion stores eastings/northings/scale as IfcLengthMeasure or
// IfcReal wrapped inside IfcValue (a SELECT) — get_attribute_value(0) peels
// the wrapper. Returns nullopt if the value is missing or non-numeric.
std::optional<double> read_property_value_double(const express::Base& property) {
if (!property.declaration().is("IfcPropertySingleValue")) return std::nullopt;
auto pe = property.as<express::Entity>();
auto nv = pe.get("NominalValue");
if (nv.isNull()) return std::nullopt;
express::Base wrapper = nv;
auto inner = wrapper.get_attribute_value(0);
if (inner.isNull()) return std::nullopt;
switch (inner.type()) {
case ifcopenshell::Argument_DOUBLE: return (double) inner;
case ifcopenshell::Argument_INT: return (double)(int) inner;
default: return std::nullopt;
template <typename T>
struct is_optional : std::false_type {};
template <typename T>
struct is_optional<std::optional<T>> : std::true_type {};
template <typename Schema, typename = void>
struct is_ifc4_or_higher : std::false_type {};
template <typename Schema>
struct is_ifc4_or_higher<Schema, std::void_t<typename Schema::IfcCoordinateOperation>> : std::true_type {};
template <typename Schema, typename = void>
struct has_map_conversion_scaled : std::false_type {};
template <typename Schema>
struct has_map_conversion_scaled<Schema, std::void_t<typename Schema::IfcMapConversionScaled>> : std::true_type {};
template <typename T, typename = void>
struct has_factor_x : std::false_type {};
template <typename T>
struct has_factor_x<T, std::void_t<decltype(std::declval<T>().FactorX())>> : std::true_type {};
template <typename Schema, typename = void>
struct has_rigid_operation : std::false_type {};
template <typename Schema>
struct has_rigid_operation<Schema, std::void_t<typename Schema::IfcRigidOperation>> : std::true_type {};
[[noreturn]] void unsupported_schema(const std::string& name) {
throw ifcopenshell::exception("No helper implementation was built for schema " + name);
}
double numeric_property(const property_map& properties,
const std::string& name,
double fallback) {
const auto found = properties.find(name);
if (found == properties.end()) {
return fallback;
}
if (const auto value = found->second.get_if<double>()) {
return *value;
}
if (const auto value = found->second.get_if<std::int64_t>()) {
return static_cast<double>(*value);
}
return fallback;
}
double selected_number(const express::Base& selected, double fallback) {
if (!selected) {
return fallback;
}
const auto value = selected.get_attribute_value(0);
if (value.isNull()) {
return fallback;
}
if (value.type() == ifcopenshell::Argument_DOUBLE) {
return static_cast<double>(value);
}
if (value.type() == ifcopenshell::Argument_INT) {
return static_cast<double>(static_cast<int>(value));
}
return fallback;
}
template <typename T>
double optional_number(const T& value, double fallback) {
if constexpr (is_optional<T>::value) {
return value.value_or(fallback);
} else {
return value;
}
}
} // namespace
std::optional<HelmertTransformation>
get_helmert_transformation_parameters(ifcopenshell::file* ifc_file) {
HelmertTransformation p;
const std::string schema_name = ifc_file->schema()->name();
if (schema_name == "IFC2X3") {
auto projects = ifc_file->instances_by_type("IfcProject");
if (projects.empty()) return std::nullopt;
const auto& project = projects[0];
bool found = false;
auto rels = project.as<express::Entity>().get_inverse("IsDefinedBy");
for (const auto& rel : rels) {
if (!rel.declaration().is("IfcRelDefinesByProperties")) continue;
express::Base pset_base = rel.get("RelatingPropertyDefinition");
if (!pset_base.declaration().is("IfcPropertySet")) continue;
auto pset = pset_base.as<express::Entity>();
auto name_attr = pset.get("Name");
if (name_attr.isNull()) continue;
std::string pset_name = name_attr;
if (pset_name != "ePSet_MapConversion") continue;
std::vector<express::Base> props = pset.get("HasProperties");
for (const auto& prop : props) {
if (!prop.declaration().is("IfcPropertySingleValue")) continue;
auto pe = prop.as<express::Entity>();
auto pname_attr = pe.get("Name");
if (pname_attr.isNull()) continue;
std::string pname = pname_attr;
auto value = read_property_value_double(prop);
if (!value) continue;
if (pname == "Eastings") p.e = *value;
else if (pname == "Northings") p.n = *value;
else if (pname == "OrthogonalHeight") p.h = *value;
else if (pname == "XAxisAbscissa") p.xaa = *value;
else if (pname == "XAxisOrdinate") p.xao = *value;
else if (pname == "Scale") p.scale = *value;
}
found = true;
break;
}
if (!found) return std::nullopt;
// Python: `conversion.get("Scale", None) or 1` — 0 falls back to 1.
if (p.scale == 0.0) p.scale = 1.0;
p.factor_x = p.factor_y = p.factor_z = 1.0;
} else {
std::vector<express::Base> conversions;
try {
conversions = ifc_file->instances_by_type("IfcCoordinateOperation");
} catch (...) {
// Schema doesn't know IfcCoordinateOperation.
template <typename Schema>
std::optional<HelmertTransformation> get_helmert_transformation_parameters_s(ifcopenshell::file* ifc_file) {
HelmertTransformation result;
if constexpr (!is_ifc4_or_higher<Schema>::value) {
const auto projects = ifc_file->template instances_by_type<typename Schema::IfcProject>();
if (projects.empty()) {
return std::nullopt;
}
if (conversions.empty()) return std::nullopt;
const auto& conversion = conversions[0];
auto entity = conversion.as<express::Entity>();
const std::string type_name = conversion.declaration().name();
auto get_or = [&](const std::string& name, double fallback) {
auto a = entity.get(name);
return a.isNull() ? fallback : (double) a;
};
if (conversion.declaration().is("IfcMapConversion")) {
p.e = get_or("Eastings", 0.0);
p.n = get_or("Northings", 0.0);
p.h = get_or("OrthogonalHeight", 0.0);
p.xaa = get_or("XAxisAbscissa", 0.0);
p.xao = get_or("XAxisOrdinate", 0.0);
p.scale = get_or("Scale", 1.0);
if (p.scale == 0.0) p.scale = 1.0;
if (type_name == "IfcMapConversionScaled") {
p.factor_x = entity.get("FactorX");
p.factor_y = entity.get("FactorY");
p.factor_z = entity.get("FactorZ");
} else {
p.factor_x = p.factor_y = p.factor_z = 1.0;
const auto conversion = get_pset(projects.front(), "ePSet_MapConversion");
if (!conversion) {
return std::nullopt;
}
const auto* properties = conversion->template get_if<property_map>();
if (!properties) {
return std::nullopt;
}
result.e = numeric_property(*properties, "Eastings", 0.0);
result.n = numeric_property(*properties, "Northings", 0.0);
result.h = numeric_property(*properties, "OrthogonalHeight", 0.0);
result.xaa = numeric_property(*properties, "XAxisAbscissa", 0.0);
result.xao = numeric_property(*properties, "XAxisOrdinate", 0.0);
result.scale = numeric_property(*properties, "Scale", 1.0);
} else {
const auto conversions =
ifc_file->template instances_by_type<typename Schema::IfcCoordinateOperation>();
if (conversions.empty()) {
return std::nullopt;
}
const auto& conversion = conversions.front();
if (auto map_conversion = conversion.template as<typename Schema::IfcMapConversion>()) {
result.e = map_conversion.Eastings();
result.n = map_conversion.Northings();
result.h = map_conversion.OrthogonalHeight();
result.xaa = map_conversion.XAxisAbscissa().value_or(0.0);
result.xao = map_conversion.XAxisOrdinate().value_or(0.0);
result.scale = map_conversion.Scale().value_or(1.0);
if constexpr (has_map_conversion_scaled<Schema>::value) {
if (auto scaled = conversion.template as<typename Schema::IfcMapConversionScaled>()) {
if constexpr (has_factor_x<decltype(scaled)>::value) {
result.factor_x = scaled.FactorX();
result.factor_y = scaled.FactorY();
result.factor_z = scaled.FactorZ();
} else {
result.factor_x = scaled.ScaleX();
result.factor_y = scaled.ScaleY();
result.factor_z = scaled.ScaleZ();
}
}
}
} else if constexpr (has_rigid_operation<Schema>::value) {
if (auto rigid = conversion.template as<typename Schema::IfcRigidOperation>()) {
result.e = selected_number(rigid.FirstCoordinate().concrete(), 0.0);
result.n = selected_number(rigid.SecondCoordinate().concrete(), 0.0);
result.h = optional_number(rigid.Height(), 0.0);
} else {
return std::nullopt;
}
} else if (type_name == "IfcRigidOperation") {
// FirstCoordinate / SecondCoordinate are IfcLengthMeasure-typed
// values; the C++ binding auto-unwraps defined types of REAL.
p.e = get_or("FirstCoordinate", 0.0);
p.n = get_or("SecondCoordinate", 0.0);
p.h = get_or("Height", 0.0);
p.xaa = 1.0;
p.xao = 0.0;
p.scale = p.factor_x = p.factor_y = p.factor_z = 1.0;
} else {
return std::nullopt;
}
}
if (p.xaa == 0.0 && p.xao == 0.0) {
p.xaa = 1.0;
p.xao = 0.0;
if (result.scale == 0.0) {
result.scale = 1.0;
}
return p;
if (result.xaa == 0.0 && result.xao == 0.0) {
result.xaa = 1.0;
}
return result;
}
std::optional<Eigen::Matrix4d> get_wcs(ifcopenshell::file* ifc_file) {
auto contexts = ifc_file->instances_by_type_excl_subtypes(
"IfcGeometricRepresentationContext");
template <typename Schema>
std::optional<Eigen::Matrix4d> get_wcs_s(ifcopenshell::file* ifc_file) {
const auto contexts =
ifc_file->template instances_by_type_excl_subtypes<typename Schema::IfcGeometricRepresentationContext>();
express::Base wcs;
bool found = false;
for (const auto& ctx : contexts) {
auto entity = ctx.as<express::Entity>();
auto wcs_attr = entity.get("WorldCoordinateSystem");
if (wcs_attr.isNull()) continue;
wcs = (express::Base) wcs_attr;
found = true;
auto ctype_attr = entity.get("ContextType");
if (!ctype_attr.isNull()) {
std::string ctype = ctype_attr;
if (ctype == "Model") break;
for (const auto& context : contexts) {
const auto placement = context.WorldCoordinateSystem();
if (!placement) {
continue;
}
wcs = placement.concrete();
if (context.ContextType() == std::optional<std::string>("Model")) {
break;
}
}
if (!found) return std::nullopt;
const auto& decl = wcs.declaration();
if (!(decl.is("IfcAxis2Placement3D") || decl.is("IfcAxis2PlacementLinear"))) {
if (!wcs) {
return std::nullopt;
}
return get_axis2_placement(wcs);
}
template <typename Schema>
std::optional<express::Base> get_map_unit_s(ifcopenshell::file* ifc_file) {
if constexpr (!is_ifc4_or_higher<Schema>::value) {
return std::nullopt;
} else {
const auto operations =
ifc_file->template instances_by_type<typename Schema::IfcCoordinateOperation>();
if (operations.empty()) {
return std::nullopt;
}
const auto target = operations.front().TargetCRS();
const auto projected = target.template as<typename Schema::IfcProjectedCRS>();
if (!projected) {
return std::nullopt;
}
const auto unit = projected.MapUnit();
if (!unit) {
return std::nullopt;
}
return unit;
}
}
} // namespace
std::optional<HelmertTransformation>
get_helmert_transformation_parameters(ifcopenshell::file* ifc_file) {
const auto name = ifc_file->schema()->name();
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (name == Identifier) \
return get_helmert_transformation_parameters_s<Schema>(ifc_file);
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(name);
}
std::optional<Eigen::Matrix4d> get_wcs(ifcopenshell::file* ifc_file) {
const auto name = ifc_file->schema()->name();
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (name == Identifier) \
return get_wcs_s<Schema>(ifc_file);
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(name);
}
Eigen::Matrix4d local_to_global(const Eigen::Matrix4d& matrix,
const HelmertTransformation& p) {
const double theta = std::atan2(p.xao, p.xaa);
@@ -190,8 +259,10 @@ Eigen::Matrix4d local_to_global(const Eigen::Matrix4d& matrix,
S(2, 2) = p.scale * p.factor_z;
Eigen::Matrix4d R = Eigen::Matrix4d::Identity();
R(0, 0) = c; R(0, 1) = -s;
R(1, 0) = s; R(1, 1) = c;
R(0, 0) = c;
R(0, 1) = -s;
R(1, 0) = s;
R(1, 1) = c;
Eigen::Matrix4d result = R * S * matrix;
// The scale was baked into the rotation+scale matrix so each axis column
@@ -200,7 +271,9 @@ Eigen::Matrix4d local_to_global(const Eigen::Matrix4d& matrix,
for (int col = 0; col < 3; ++col) {
Eigen::Vector3d v = result.block<3, 1>(0, col);
const double n = v.norm();
if (n > 0.0) result.block<3, 1>(0, col) = v / n;
if (n > 0.0) {
result.block<3, 1>(0, col) = v / n;
}
}
result(0, 3) += p.e;
result(1, 3) += p.n;
@@ -212,7 +285,9 @@ Eigen::Matrix4d auto_local_to_global(ifcopenshell::file* ifc_file,
const Eigen::Matrix4d& matrix,
bool should_return_in_map_units) {
auto params = get_helmert_transformation_parameters(ifc_file);
if (!params) return matrix;
if (!params) {
return matrix;
}
Eigen::Matrix4d m = matrix;
if (auto wcs = get_wcs(ifc_file)) {
@@ -238,9 +313,15 @@ Eigen::Matrix4d helmert_meters_from_parameters(const HelmertTransformation& p,
// they apply to placement translations on compose; this is the behaviour
// IfcMapConversionScaled actually wants ("grid distance ≠ ground
// distance" — buildings on the grid should appear scaled by f).
M(0, 0) = c * p.factor_x; M(0, 1) = -s * p.factor_y; M(0, 2) = 0.0;
M(1, 0) = s * p.factor_x; M(1, 1) = c * p.factor_y; M(1, 2) = 0.0;
M(2, 0) = 0.0; M(2, 1) = 0.0; M(2, 2) = p.factor_z;
M(0, 0) = c * p.factor_x;
M(0, 1) = -s * p.factor_y;
M(0, 2) = 0.0;
M(1, 0) = s * p.factor_x;
M(1, 1) = c * p.factor_y;
M(1, 2) = 0.0;
M(2, 0) = 0.0;
M(2, 1) = 0.0;
M(2, 2) = p.factor_z;
M(0, 3) = p.e * map_unit_to_meters;
M(1, 3) = p.n * map_unit_to_meters;
M(2, 3) = p.h * map_unit_to_meters;
@@ -248,20 +329,13 @@ Eigen::Matrix4d helmert_meters_from_parameters(const HelmertTransformation& p,
}
std::optional<express::Base> get_map_unit(ifcopenshell::file* ifc_file) {
std::vector<express::Base> coordops;
try {
coordops = ifc_file->instances_by_type("IfcCoordinateOperation");
} catch (...) {
return std::nullopt;
}
if (coordops.empty()) return std::nullopt;
auto target_attr = coordops[0].as<express::Entity>().get("TargetCRS");
if (target_attr.isNull()) return std::nullopt;
express::Base target = target_attr;
if (!target.declaration().is("IfcProjectedCRS")) return std::nullopt;
auto mu_attr = target.as<express::Entity>().get("MapUnit");
if (mu_attr.isNull()) return std::nullopt;
return (express::Base) mu_attr;
const auto name = ifc_file->schema()->name();
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (name == Identifier) \
return get_map_unit_s<Schema>(ifc_file);
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(name);
}
double x_axis_to_angle_deg(double xaa, double xao) {
+13 -16
View File
@@ -18,11 +18,7 @@
********************************************************************************/
// Port of selected helpers from
// src/ifcopenshell-python/ifcopenshell/util/geolocation.py — primarily
// auto_local_to_global, which builds a 4x4 matrix that lifts an element's local
// transform into the model's global (georeferenced) frame. The python utils
// are expected to be ported to C++ in their own module later; this file is
// the temporary home until that lands.
// src/ifcopenshell-python/ifcopenshell/util/geolocation.py.
#ifndef GEOLOCATION_H
#define GEOLOCATION_H
@@ -30,21 +26,22 @@
#include "../ifcparse/express.h"
#include <Eigen/Dense>
#include <optional>
namespace ifcopenshell { class file; }
namespace ifcopenshell {
class file;
}
struct HelmertTransformation {
double e = 0.0; // eastings offset
double n = 0.0; // northings offset
double h = 0.0; // orthogonal-height offset
double xaa = 1.0; // X-axis abscissa (cos of grid-rotation angle)
double xao = 0.0; // X-axis ordinate (sin of grid-rotation angle)
double scale = 1.0; // unit scale (project unit -> map unit)
double factor_x = 1.0; // combined scale factor along X
double factor_y = 1.0; // combined scale factor along Y
double factor_z = 1.0; // combined scale factor along Z
double e = 0.0; // eastings offset
double n = 0.0; // northings offset
double h = 0.0; // orthogonal-height offset
double xaa = 1.0; // X-axis abscissa (cos of grid-rotation angle)
double xao = 0.0; // X-axis ordinate (sin of grid-rotation angle)
double scale = 1.0; // unit scale (project unit -> map unit)
double factor_x = 1.0; // combined scale factor along X
double factor_y = 1.0; // combined scale factor along Y
double factor_z = 1.0; // combined scale factor along Z
};
// Detect a Helmert transformation in the IFC model. Reads IfcMapConversion /
+111 -86
View File
@@ -19,9 +19,10 @@
#include "placement.h"
#include "../ifcparse/instance_data.h"
#include "../ifcparse/schema.h"
#include "../ifcparse/exception.h"
#include "schema_dispatch.i"
#include <type_traits>
#include <vector>
namespace {
@@ -32,14 +33,99 @@ Eigen::Vector3d safe_normalize(const Eigen::Vector3d& v,
return (n > 0.0) ? Eigen::Vector3d(v / n) : fallback;
}
std::vector<double> read_direction_ratios(const express::Base& dir) {
if (!dir) return {};
auto attr = dir.as<express::Entity>().get("DirectionRatios");
if (attr.isNull()) return {};
return attr;
template <typename Schema, typename = void>
struct has_axis2_placement_linear : std::false_type {};
template <typename Schema>
struct has_axis2_placement_linear<Schema, std::void_t<typename Schema::IfcAxis2PlacementLinear>> : std::true_type {};
[[noreturn]] void unsupported_schema(const std::string& name) {
throw ifcopenshell::exception("No helper implementation was built for schema " + name);
}
} // namespace
template <typename Schema>
Eigen::Vector3d direction_or(const typename Schema::IfcDirection& direction,
const Eigen::Vector3d& fallback) {
if (!direction) {
return fallback;
}
const auto ratios = direction.DirectionRatios();
if (ratios.size() < 2) {
return fallback;
}
return Eigen::Vector3d(ratios[0], ratios[1], ratios.size() > 2 ? ratios[2] : 0.0);
}
template <typename Schema>
std::optional<Eigen::Vector3d> cartesian_point(const typename Schema::IfcPoint& point) {
const auto cartesian = point.template as<typename Schema::IfcCartesianPoint>();
if (!cartesian) {
return std::nullopt;
}
const auto coordinates = cartesian.Coordinates();
if (coordinates.size() < 2) {
return std::nullopt;
}
return Eigen::Vector3d(coordinates[0], coordinates[1], coordinates.size() > 2 ? coordinates[2] : 0.0);
}
template <typename Schema, typename Placement>
Eigen::Matrix4d axis2_placement_3d_s(const Placement& placement) {
const auto origin = cartesian_point<Schema>(placement.Location());
if (!origin) {
return Eigen::Matrix4d::Identity();
}
const auto z = direction_or<Schema>(placement.Axis(), Eigen::Vector3d::UnitZ());
const auto x = direction_or<Schema>(placement.RefDirection(), Eigen::Vector3d::UnitX());
return axes_to_placement(*origin, z, x);
}
template <typename Schema>
Eigen::Matrix4d get_axis2_placement_s(const express::Base& placement) {
if (auto axis3 = placement.template as<typename Schema::IfcAxis2Placement3D>()) {
return axis2_placement_3d_s<Schema>(axis3);
}
if constexpr (has_axis2_placement_linear<Schema>::value) {
if (auto linear = placement.template as<typename Schema::IfcAxis2PlacementLinear>()) {
return axis2_placement_3d_s<Schema>(linear);
}
}
if (auto axis2 = placement.template as<typename Schema::IfcAxis2Placement2D>()) {
const auto origin = cartesian_point<Schema>(axis2.Location());
if (!origin) {
return Eigen::Matrix4d::Identity();
}
const auto x = direction_or<Schema>(axis2.RefDirection(), Eigen::Vector3d::UnitX());
return axes_to_placement(*origin, Eigen::Vector3d::UnitZ(), x);
}
if (auto axis1 = placement.template as<typename Schema::IfcAxis1Placement>()) {
const auto origin = cartesian_point<Schema>(axis1.Location());
if (!origin) {
return Eigen::Matrix4d::Identity();
}
const auto z = direction_or<Schema>(axis1.Axis(), Eigen::Vector3d::UnitZ());
return axes_to_placement(*origin, z, Eigen::Vector3d::UnitX());
}
return Eigen::Matrix4d::Identity();
}
template <typename Schema>
Eigen::Matrix4d get_local_placement_s(const express::Base& placement) {
if (auto local = placement.template as<typename Schema::IfcLocalPlacement>()) {
Eigen::Matrix4d parent = Eigen::Matrix4d::Identity();
if (const auto relative_to = local.PlacementRelTo()) {
parent = get_local_placement_s<Schema>(relative_to);
}
const auto relative = local.RelativePlacement();
if (!relative) {
return parent;
}
return parent * get_axis2_placement_s<Schema>(relative.concrete());
}
return get_axis2_placement_s<Schema>(placement);
}
} // namespace
Eigen::Matrix4d axes_to_placement(const Eigen::Vector3d& origin,
const Eigen::Vector3d& z,
@@ -57,88 +143,27 @@ Eigen::Matrix4d axes_to_placement(const Eigen::Vector3d& origin,
}
Eigen::Matrix4d get_axis2_placement(const express::Base& placement) {
if (!placement) return Eigen::Matrix4d::Identity();
const auto& decl = placement.declaration();
auto entity = placement.as<express::Entity>();
Eigen::Vector3d z(0.0, 0.0, 1.0);
Eigen::Vector3d x(1.0, 0.0, 0.0);
Eigen::Vector3d o(0.0, 0.0, 0.0);
if (decl.is("IfcAxis2Placement3D") || decl.is("IfcAxis2PlacementLinear")) {
auto axis_attr = entity.get("Axis");
if (!axis_attr.isNull()) {
auto dr = read_direction_ratios((express::Base) axis_attr);
if (dr.size() >= 3) z = Eigen::Vector3d(dr[0], dr[1], dr[2]);
}
auto refdir_attr = entity.get("RefDirection");
if (!refdir_attr.isNull()) {
auto dr = read_direction_ratios((express::Base) refdir_attr);
if (dr.size() >= 3) x = Eigen::Vector3d(dr[0], dr[1], dr[2]);
}
auto loc_attr = entity.get("Location");
if (loc_attr.isNull()) return Eigen::Matrix4d::Identity();
express::Base location = loc_attr;
auto coords_attr = location.as<express::Entity>().get("Coordinates");
if (coords_attr.isNull()) return Eigen::Matrix4d::Identity();
std::vector<double> coords = coords_attr;
if (coords.size() >= 3) o = Eigen::Vector3d(coords[0], coords[1], coords[2]);
} else if (decl.is("IfcAxis2Placement2D")) {
auto refdir_attr = entity.get("RefDirection");
if (!refdir_attr.isNull()) {
auto dr = read_direction_ratios((express::Base) refdir_attr);
if (dr.size() >= 1) {
x = Eigen::Vector3d(dr.size() > 0 ? dr[0] : 1.0,
dr.size() > 1 ? dr[1] : 0.0,
0.0);
}
}
auto loc_attr = entity.get("Location");
if (loc_attr.isNull()) return Eigen::Matrix4d::Identity();
express::Base location = loc_attr;
auto coords_attr = location.as<express::Entity>().get("Coordinates");
if (coords_attr.isNull()) return Eigen::Matrix4d::Identity();
std::vector<double> coords = coords_attr;
if (coords.size() >= 2) {
o = Eigen::Vector3d(coords[0], coords[1],
coords.size() >= 3 ? coords[2] : 0.0);
}
} else if (decl.is("IfcAxis1Placement")) {
auto axis_attr = entity.get("Axis");
if (!axis_attr.isNull()) {
auto dr = read_direction_ratios((express::Base) axis_attr);
if (dr.size() >= 3) z = Eigen::Vector3d(dr[0], dr[1], dr[2]);
}
auto loc_attr = entity.get("Location");
if (loc_attr.isNull()) return Eigen::Matrix4d::Identity();
express::Base location = loc_attr;
auto coords_attr = location.as<express::Entity>().get("Coordinates");
if (coords_attr.isNull()) return Eigen::Matrix4d::Identity();
std::vector<double> coords = coords_attr;
if (coords.size() >= 3) o = Eigen::Vector3d(coords[0], coords[1], coords[2]);
} else {
if (!placement) {
return Eigen::Matrix4d::Identity();
}
return axes_to_placement(o, z, x);
const auto name = placement.declaration().schema()->name();
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (name == Identifier) \
return get_axis2_placement_s<Schema>(placement);
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(name);
}
Eigen::Matrix4d get_local_placement(const express::Base& placement) {
if (!placement) return Eigen::Matrix4d::Identity();
const auto& decl = placement.declaration();
if (decl.is("IfcLocalPlacement")) {
auto entity = placement.as<express::Entity>();
Eigen::Matrix4d parent = Eigen::Matrix4d::Identity();
auto rel_attr = entity.get("PlacementRelTo");
if (!rel_attr.isNull()) {
parent = get_local_placement((express::Base) rel_attr);
}
auto rp_attr = entity.get("RelativePlacement");
if (rp_attr.isNull()) return parent;
return parent * get_axis2_placement((express::Base) rp_attr);
if (!placement) {
return Eigen::Matrix4d::Identity();
}
// IfcAxis2Placement* / IfcAxis1Placement passed in directly.
return get_axis2_placement(placement);
const auto name = placement.declaration().schema()->name();
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (name == Identifier) \
return get_local_placement_s<Schema>(placement);
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(name);
}
+801
View File
@@ -0,0 +1,801 @@
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 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 this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
// This file was generated with the assistance of an AI coding tool.
#include "pset.h"
#include "../ifcparse/exception.h"
#include "../ifcparse/file.h"
#include "../ifcparse/instance_data.h"
#include "schema_dispatch.i"
#include <boost/logic/tribool.hpp>
#include <type_traits>
namespace {
template <typename T>
struct is_optional : std::false_type {};
template <typename T>
struct is_optional<std::optional<T>> : std::true_type {};
template <typename Schema, typename = void>
struct is_ifc4_or_higher : std::false_type {};
template <typename Schema>
struct is_ifc4_or_higher<Schema, std::void_t<typename Schema::IfcMaterialDefinition>> : std::true_type {};
template <typename Schema, typename = void>
struct has_predefined_property_set : std::false_type {};
template <typename Schema>
struct has_predefined_property_set<Schema, std::void_t<typename Schema::IfcPreDefinedPropertySet>> : std::true_type {};
template <typename T, typename = void>
struct has_set_point_value : std::false_type {};
template <typename T>
struct has_set_point_value<T, std::void_t<decltype(std::declval<T>().SetPointValue())>> : std::true_type {};
template <typename T, typename = void>
struct has_curve_interpolation : std::false_type {};
template <typename T>
struct has_curve_interpolation<T, std::void_t<decltype(std::declval<T>().CurveInterpolation())>> : std::true_type {};
std::string schema_name(const express::Base& instance) {
return instance.declaration().schema()->name();
}
[[noreturn]] void unsupported_schema(const std::string& name) {
throw ifcopenshell::exception("No helper implementation was built for schema " + name);
}
property_value from_attribute(const attribute_value& value);
template <typename T>
property_list scalar_list(const std::vector<T>& values) {
property_list result;
result.reserve(values.size());
for (const auto& value : values) {
result.emplace_back(value);
}
return result;
}
property_value from_attribute(const attribute_value& value) {
if (value.isNull()) {
return {};
}
switch (value.type()) {
case ifcopenshell::Argument_INT:
return static_cast<int>(value);
case ifcopenshell::Argument_BOOL:
return static_cast<bool>(value);
case ifcopenshell::Argument_LOGICAL: {
const boost::logic::tribool logical = value;
if (boost::logic::indeterminate(logical)) {
return "UNKNOWN";
}
return static_cast<bool>(logical);
}
case ifcopenshell::Argument_DOUBLE:
return static_cast<double>(value);
case ifcopenshell::Argument_STRING:
return static_cast<std::string>(value);
case ifcopenshell::Argument_ENUMERATION: {
const enumeration_reference enumeration = value;
return enumeration.value() ? enumeration.value() : "";
}
case ifcopenshell::Argument_BINARY: {
const boost::dynamic_bitset<> bits = value;
std::string result;
boost::to_string(bits, result);
return result;
}
case ifcopenshell::Argument_ENTITY_INSTANCE: {
const express::Base instance = value;
if (instance && !instance.declaration().as_entity()) {
return from_attribute(instance.get_attribute_value(0));
}
return instance;
}
case ifcopenshell::Argument_AGGREGATE_OF_INT:
return scalar_list(static_cast<std::vector<int>>(value));
case ifcopenshell::Argument_AGGREGATE_OF_DOUBLE:
return scalar_list(static_cast<std::vector<double>>(value));
case ifcopenshell::Argument_AGGREGATE_OF_STRING:
return scalar_list(static_cast<std::vector<std::string>>(value));
case ifcopenshell::Argument_AGGREGATE_OF_ENTITY_INSTANCE: {
property_list result;
for (const auto& instance : static_cast<std::vector<express::Base>>(value)) {
if (instance && !instance.declaration().as_entity()) {
result.push_back(from_attribute(instance.get_attribute_value(0)));
} else {
result.emplace_back(instance);
}
}
return result;
}
case ifcopenshell::Argument_EMPTY_AGGREGATE:
return property_list{};
default:
return {};
}
}
template <typename Select>
property_value from_select(const Select& value) {
if (!value) {
return {};
}
return from_attribute(value.concrete().get_attribute_value(0));
}
template <typename Values>
property_value from_select_values(const Values& maybe_values) {
if constexpr (is_optional<Values>::value) {
if (!maybe_values || maybe_values->empty()) {
return {};
}
return from_select_values(*maybe_values);
} else {
if (maybe_values.empty()) {
return {};
}
property_list result;
result.reserve(maybe_values.size());
for (const auto& value : maybe_values) {
result.push_back(from_select(value));
}
return result;
}
}
property_value verbose_value(const express::Base& instance,
property_value value,
const std::optional<std::string>& value_type = std::nullopt) {
property_map result = {
{"id", instance.id()},
{"class", instance.declaration().name()},
{"value", std::move(value)},
};
if (value_type) {
result["value_type"] = *value_type;
}
return result;
}
template <typename Schema>
property_map get_properties_s(const std::vector<typename Schema::IfcProperty>& properties, bool verbose);
template <typename Schema>
property_map get_quantities_s(const std::vector<typename Schema::IfcPhysicalQuantity>& quantities, bool verbose);
template <typename Schema>
std::optional<property_value> property_value_s(const typename Schema::IfcProperty& property, bool verbose) {
property_value result;
std::optional<std::string> value_type;
if (auto single = property.template as<typename Schema::IfcPropertySingleValue>()) {
const auto nominal = single.NominalValue();
result = from_select(nominal);
if (nominal) {
value_type = nominal.concrete().declaration().name();
}
} else if (auto enumerated = property.template as<typename Schema::IfcPropertyEnumeratedValue>()) {
result = from_select_values(enumerated.EnumerationValues());
} else if (auto list = property.template as<typename Schema::IfcPropertyListValue>()) {
result = from_select_values(list.ListValues());
} else if (auto bounded = property.template as<typename Schema::IfcPropertyBoundedValue>()) {
property_map data = {
{"id", bounded.id()},
{"type", bounded.declaration().name()},
{"UpperBoundValue", from_select(bounded.UpperBoundValue())},
{"LowerBoundValue", from_select(bounded.LowerBoundValue())},
};
if constexpr (has_set_point_value<decltype(bounded)>::value) {
data["SetPointValue"] = from_select(bounded.SetPointValue());
}
result = std::move(data);
} else if (auto table = property.template as<typename Schema::IfcPropertyTableValue>()) {
property_map data = {
{"id", table.id()},
{"type", table.declaration().name()},
{"DefiningValues", from_select_values(table.DefiningValues())},
{"DefinedValues", from_select_values(table.DefinedValues())},
};
if (const auto expression = table.Expression()) {
data["Expression"] = *expression;
}
if (const auto defining_unit = table.DefiningUnit()) {
data["DefiningUnit"] = defining_unit.concrete();
}
if (const auto defined_unit = table.DefinedUnit()) {
data["DefinedUnit"] = defined_unit.concrete();
}
if constexpr (has_curve_interpolation<decltype(table)>::value) {
if (const auto interpolation = table.CurveInterpolation()) {
data["CurveInterpolation"] =
Schema::IfcCurveInterpolationEnum::ToString(*interpolation);
}
}
result = std::move(data);
} else if (auto complex = property.template as<typename Schema::IfcComplexProperty>()) {
result = property_map{
{"id", complex.id()},
{"type", complex.declaration().name()},
{"UsageName", complex.UsageName()},
{"properties", get_properties_s<Schema>(complex.HasProperties(), verbose)},
};
} else {
return std::nullopt;
}
if (verbose) {
return verbose_value(property, std::move(result), value_type);
}
return result;
}
template <typename Schema>
std::optional<property_value> get_property_s(const std::vector<typename Schema::IfcProperty>& properties,
const std::string& name,
bool verbose) {
for (const auto& property : properties) {
if (property.Name() == name) {
return property_value_s<Schema>(property, verbose);
}
}
return std::nullopt;
}
template <typename Schema>
property_map get_properties_s(const std::vector<typename Schema::IfcProperty>& properties, bool verbose) {
property_map result;
for (const auto& property : properties) {
if (auto value = property_value_s<Schema>(property, verbose)) {
result[property.Name()] = std::move(*value);
}
}
return result;
}
template <typename Schema>
std::optional<property_value> quantity_value_s(const typename Schema::IfcPhysicalQuantity& quantity,
bool verbose) {
property_value result;
if (quantity.template as<typename Schema::IfcPhysicalSimpleQuantity>()) {
result = from_attribute(quantity.get_attribute_value(3));
} else if (auto complex = quantity.template as<typename Schema::IfcPhysicalComplexQuantity>()) {
result = property_map{
{"id", complex.id()},
{"type", complex.declaration().name()},
{"Discrimination", complex.Discrimination()},
{"properties", get_quantities_s<Schema>(complex.HasQuantities(), verbose)},
};
} else {
return std::nullopt;
}
if (verbose) {
return verbose_value(quantity, std::move(result));
}
return result;
}
template <typename Schema>
std::optional<property_value> get_quantity_s(
const std::vector<typename Schema::IfcPhysicalQuantity>& quantities,
const std::string& name,
bool verbose) {
for (const auto& quantity : quantities) {
if (quantity.Name() == name) {
return quantity_value_s<Schema>(quantity, verbose);
}
}
return std::nullopt;
}
template <typename Schema>
property_map get_quantities_s(const std::vector<typename Schema::IfcPhysicalQuantity>& quantities, bool verbose) {
property_map result;
for (const auto& quantity : quantities) {
if (auto value = quantity_value_s<Schema>(quantity, verbose)) {
result[quantity.Name()] = std::move(*value);
}
}
return result;
}
template <typename Schema>
std::optional<property_value> predefined_properties_s(
const typename Schema::IfcPreDefinedPropertySet& definition,
const std::optional<std::string>& property_name) {
property_map result;
const auto* declaration = definition.declaration().as_entity();
for (std::size_t index = 4; index < declaration->attribute_count(); ++index) {
const auto* attribute = declaration->attribute_by_index(index);
if (property_name && attribute->name() != *property_name) {
continue;
}
const auto value = definition.get_attribute_value(index);
if (value.isNull()) {
continue;
}
if (property_name) {
return from_attribute(value);
}
result[attribute->name()] = from_attribute(value);
}
if (property_name) {
return std::nullopt;
}
result["id"] = definition.id();
return result;
}
template <typename Schema>
std::optional<property_value> get_property_definition_s(
const express::Base& definition,
const std::optional<std::string>& property_name,
bool verbose) {
if (!definition) {
return std::nullopt;
}
if (auto quantity = definition.template as<typename Schema::IfcElementQuantity>()) {
if (property_name) {
return get_quantity_s<Schema>(quantity.Quantities(), *property_name, verbose);
}
auto result = get_quantities_s<Schema>(quantity.Quantities(), verbose);
result["id"] = definition.id();
return result;
}
if (auto pset = definition.template as<typename Schema::IfcPropertySet>()) {
if (property_name) {
return get_property_s<Schema>(pset.HasProperties(), *property_name, verbose);
}
auto result = get_properties_s<Schema>(pset.HasProperties(), verbose);
result["id"] = definition.id();
return result;
}
if constexpr (is_ifc4_or_higher<Schema>::value) {
if (auto extended = definition.template as<typename Schema::IfcExtendedProperties>()) {
if (property_name) {
return get_property_s<Schema>(extended.Properties(), *property_name, verbose);
}
auto result = get_properties_s<Schema>(extended.Properties(), verbose);
result["id"] = definition.id();
return result;
}
} else {
if (auto extended = definition.template as<typename Schema::IfcExtendedMaterialProperties>()) {
if (property_name) {
return get_property_s<Schema>(extended.ExtendedProperties(), *property_name, verbose);
}
auto result = get_properties_s<Schema>(extended.ExtendedProperties(), verbose);
result["id"] = definition.id();
return result;
}
}
if constexpr (has_predefined_property_set<Schema>::value) {
if (auto predefined = definition.template as<typename Schema::IfcPreDefinedPropertySet>()) {
return predefined_properties_s<Schema>(predefined, property_name);
}
}
return std::nullopt;
}
template <typename Definition>
express::Base concrete_definition(const Definition& definition) {
if constexpr (std::is_base_of_v<express::Select, Definition>) {
return definition.concrete();
} else {
return definition;
}
}
template <typename Schema>
bool definition_is_pset_s(const express::Base& definition) {
if (definition.template as<typename Schema::IfcPropertySet>()) {
return true;
}
if constexpr (has_predefined_property_set<Schema>::value) {
if (definition.template as<typename Schema::IfcPreDefinedPropertySet>()) {
return true;
}
}
if constexpr (!is_ifc4_or_higher<Schema>::value) {
if (definition.template as<typename Schema::IfcExtendedMaterialProperties>()) {
return true;
}
}
return false;
}
template <typename Schema>
std::optional<std::string> definition_name_s(const express::Base& definition) {
if (auto pset = definition.template as<typename Schema::IfcPropertySet>()) {
return pset.Name();
}
if (auto quantity = definition.template as<typename Schema::IfcElementQuantity>()) {
return quantity.Name();
}
if constexpr (is_ifc4_or_higher<Schema>::value) {
if (auto extended = definition.template as<typename Schema::IfcExtendedProperties>()) {
return extended.Name();
}
} else {
if (auto extended = definition.template as<typename Schema::IfcExtendedMaterialProperties>()) {
return extended.Name();
}
}
if constexpr (has_predefined_property_set<Schema>::value) {
if (auto predefined = definition.template as<typename Schema::IfcPreDefinedPropertySet>()) {
return predefined.Name();
}
}
return std::nullopt;
}
template <typename Schema>
void merge_definition_s(element_properties& result,
const express::Base& definition,
bool psets_only,
bool qtos_only,
bool verbose) {
const bool is_quantity = static_cast<bool>(definition.template as<typename Schema::IfcElementQuantity>());
if (psets_only && !definition_is_pset_s<Schema>(definition)) {
return;
}
if (qtos_only && !is_quantity) {
return;
}
const auto name = definition_name_s<Schema>(definition);
if (!name) {
return;
}
auto values = get_property_definition_s<Schema>(definition, std::nullopt, verbose);
if (!values) {
return;
}
const auto* properties = values->template get_if<property_map>();
if (!properties) {
return;
}
auto& destination = result[*name];
for (const auto& [property_name, value] : *properties) {
destination.insert_or_assign(property_name, value);
}
}
template <typename Schema>
typename Schema::IfcTypeObject get_type_s(const typename Schema::IfcObject& object) {
if constexpr (is_ifc4_or_higher<Schema>::value) {
const auto relationships = object.IsTypedBy();
if (!relationships.empty()) {
return relationships.front().RelatingType();
}
} else {
for (const auto& relationship : object.IsDefinedBy()) {
if (auto by_type = relationship.template as<typename Schema::IfcRelDefinesByType>()) {
return by_type.RelatingType();
}
}
}
return {};
}
template <typename Schema>
element_properties get_psets_s(const express::Base& element,
bool psets_only,
bool qtos_only,
bool should_inherit,
bool verbose) {
element_properties result;
if (auto type = element.template as<typename Schema::IfcTypeObject>()) {
if (const auto definitions = type.HasPropertySets()) {
for (const auto& definition : *definitions) {
merge_definition_s<Schema>(result, definition, psets_only, qtos_only, verbose);
}
}
return result;
}
if constexpr (is_ifc4_or_higher<Schema>::value) {
if (auto material = element.template as<typename Schema::IfcMaterialDefinition>()) {
if (qtos_only) {
return result;
}
for (const auto& definition : material.HasProperties()) {
merge_definition_s<Schema>(result, definition, false, false, verbose);
}
return result;
}
if (auto profile = element.template as<typename Schema::IfcProfileDef>()) {
if (qtos_only) {
return result;
}
for (const auto& definition : profile.HasProperties()) {
merge_definition_s<Schema>(result, definition, false, false, verbose);
}
return result;
}
} else {
if (auto material = element.template as<typename Schema::IfcMaterial>()) {
if (qtos_only) {
return result;
}
for (const auto& definition :
element.file()->template instances_by_type<typename Schema::IfcExtendedMaterialProperties>()) {
if (definition.Material() == material) {
merge_definition_s<Schema>(result, definition, false, false, verbose);
}
}
return result;
}
if (element.template as<typename Schema::IfcProfileDef>()) {
return result;
}
}
if (auto object = element.template as<typename Schema::IfcObject>()) {
if (should_inherit) {
if (const auto type = get_type_s<Schema>(object)) {
result = get_psets_s<Schema>(type, psets_only, qtos_only, false, verbose);
}
}
for (const auto& relationship : object.IsDefinedBy()) {
if (auto by_properties = relationship.template as<typename Schema::IfcRelDefinesByProperties>()) {
merge_definition_s<Schema>(result,
concrete_definition(by_properties.RelatingPropertyDefinition()),
psets_only,
qtos_only,
verbose);
}
}
}
return result;
}
template <typename Schema>
std::optional<property_value> get_inherited_property_s(const express::Base& element,
const std::string& pset_name,
const std::string& property_name,
bool psets_only,
bool qtos_only,
bool verbose) {
const auto object = element.template as<typename Schema::IfcObject>();
if (!object) {
return std::nullopt;
}
const auto type = get_type_s<Schema>(object);
if (!type) {
return std::nullopt;
}
const auto psets = get_psets_s<Schema>(type, psets_only, qtos_only, false, verbose);
const auto pset = psets.find(pset_name);
if (pset == psets.end()) {
return std::nullopt;
}
const auto property = pset->second.find(property_name);
if (property == pset->second.end()) {
return std::nullopt;
}
return property->second;
}
template <typename Schema, typename Entity>
std::vector<Entity> cast_entities(const std::vector<express::Base>& values) {
std::vector<Entity> result;
result.reserve(values.size());
for (const auto& value : values) {
if (auto typed = value.template as<Entity>()) {
result.push_back(typed);
}
}
return result;
}
void print_value(std::ostream& stream, const property_value& value) {
std::visit(
[&stream](const auto& item) {
using T = std::decay_t<decltype(item)>;
if constexpr (std::is_same_v<T, std::monostate>) {
stream << "null";
} else if constexpr (std::is_same_v<T, bool>) {
stream << (item ? "true" : "false");
} else if constexpr (std::is_same_v<T, express::Base>) {
if (item) {
item.to_string(stream);
} else {
stream << "null";
}
} else if constexpr (std::is_same_v<T, property_list>) {
stream << '[';
bool first = true;
for (const auto& child : item) {
if (!first) {
stream << ", ";
}
first = false;
print_value(stream, child);
}
stream << ']';
} else if constexpr (std::is_same_v<T, property_map>) {
stream << '{';
bool first = true;
for (const auto& [name, child] : item) {
if (!first) {
stream << ", ";
}
first = false;
stream << name << ": ";
print_value(stream, child);
}
stream << '}';
} else {
stream << item;
}
},
value.value);
}
} // namespace
std::ostream& operator<<(std::ostream& stream, const property_value& value) {
print_value(stream, value);
return stream;
}
element_properties get_psets(const express::Base& element,
bool psets_only,
bool qtos_only,
bool should_inherit,
bool verbose) {
if (!element) {
return {};
}
const auto name = schema_name(element);
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (name == Identifier) { \
return get_psets_s<Schema>(element, psets_only, qtos_only, should_inherit, verbose); \
}
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(name);
}
std::optional<property_value> get_pset(const express::Base& element,
const std::string& name,
const std::optional<std::string>& property_name,
bool psets_only,
bool qtos_only,
bool should_inherit,
bool verbose) {
auto psets = get_psets(element, psets_only, qtos_only, should_inherit, verbose);
const auto set = psets.find(name);
if (set == psets.end()) {
return std::nullopt;
}
if (!property_name) {
return set->second;
}
const auto property = set->second.find(*property_name);
if (property == set->second.end()) {
return std::nullopt;
}
if (property->second.is_null() && should_inherit) {
const auto schema = schema_name(element);
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (schema == Identifier) { \
if (auto inherited = get_inherited_property_s<Schema>(element, name, *property_name, psets_only, qtos_only, verbose)) { \
return inherited; \
} \
}
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
}
return property->second;
}
std::optional<property_value> get_property_definition(
const express::Base& definition,
const std::optional<std::string>& property_name,
bool verbose) {
if (!definition) {
return std::nullopt;
}
const auto name = schema_name(definition);
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (name == Identifier) \
return get_property_definition_s<Schema>(definition, property_name, verbose);
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(name);
}
std::optional<property_value> get_quantity(const std::vector<express::Base>& quantities,
const std::string& name,
bool verbose) {
if (quantities.empty()) {
return std::nullopt;
}
const auto schema = schema_name(quantities.front());
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (schema == Identifier) { \
return get_quantity_s<Schema>(cast_entities<Schema, typename Schema::IfcPhysicalQuantity>(quantities), \
name, \
verbose); \
}
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(schema);
}
property_map get_quantities(const std::vector<express::Base>& quantities, bool verbose) {
if (quantities.empty()) {
return {};
}
const auto schema = schema_name(quantities.front());
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (schema == Identifier) { \
return get_quantities_s<Schema>(cast_entities<Schema, typename Schema::IfcPhysicalQuantity>(quantities), \
verbose); \
}
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(schema);
}
std::optional<property_value> get_property(const std::vector<express::Base>& properties,
const std::string& name,
bool verbose) {
if (properties.empty()) {
return std::nullopt;
}
const auto schema = schema_name(properties.front());
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (schema == Identifier) { \
return get_property_s<Schema>(cast_entities<Schema, typename Schema::IfcProperty>(properties), \
name, \
verbose); \
}
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(schema);
}
property_map get_properties(const std::vector<express::Base>& properties, bool verbose) {
if (properties.empty()) {
return {};
}
const auto schema = schema_name(properties.front());
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (schema == Identifier) { \
return get_properties_s<Schema>(cast_entities<Schema, typename Schema::IfcProperty>(properties), \
verbose); \
}
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(schema);
}
+109
View File
@@ -0,0 +1,109 @@
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 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 this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
// This file was generated with the assistance of an AI coding tool.
#ifndef PSET_H
#define PSET_H
#include "../ifcparse/express.h"
#include <cstdint>
#include <map>
#include <optional>
#include <ostream>
#include <string>
#include <utility>
#include <variant>
#include <vector>
struct property_value;
using property_list = std::vector<property_value>;
using property_map = std::map<std::string, property_value>;
using element_properties = std::map<std::string, property_map>;
// Recursive value used by the pset helpers. A default-constructed value is
// IFC null. Entity values are retained for compound property metadata that
// cannot be flattened to a scalar.
struct property_value {
using storage_type = std::variant<std::monostate, bool, std::int64_t, double, std::string, express::Base, property_list, property_map>;
storage_type value;
property_value() = default;
property_value(bool value) : value(value) {}
property_value(int value) : value(static_cast<std::int64_t>(value)) {}
property_value(unsigned value) : value(static_cast<std::int64_t>(value)) {}
property_value(std::int64_t value) : value(value) {}
property_value(double value) : value(value) {}
property_value(const char* value) : value(std::string(value)) {}
property_value(std::string value) : value(std::move(value)) {}
property_value(express::Base value) : value(std::move(value)) {}
property_value(property_list value) : value(std::move(value)) {}
property_value(property_map value) : value(std::move(value)) {}
bool is_null() const { return std::holds_alternative<std::monostate>(value); }
template <typename T>
const T* get_if() const {
return std::get_if<T>(&value);
}
};
std::ostream& operator<<(std::ostream& stream, const property_value& value);
// Mirrors ifcopenshell.util.element.get_pset. When property_name is unset,
// the returned value contains a property_map. A selected IFC null property is
// represented by an engaged optional containing a null property_value.
std::optional<property_value> get_pset(
const express::Base& element,
const std::string& name,
const std::optional<std::string>& property_name = std::nullopt,
bool psets_only = false,
bool qtos_only = false,
bool should_inherit = true,
bool verbose = false);
// Mirrors ifcopenshell.util.element.get_psets, including occurrence-over-type
// precedence and the definition instance id in each returned property map.
element_properties get_psets(const express::Base& element,
bool psets_only = false,
bool qtos_only = false,
bool should_inherit = true,
bool verbose = false);
std::optional<property_value> get_property_definition(
const express::Base& definition,
const std::optional<std::string>& property_name = std::nullopt,
bool verbose = false);
std::optional<property_value> get_quantity(const std::vector<express::Base>& quantities,
const std::string& name,
bool verbose = false);
property_map get_quantities(const std::vector<express::Base>& quantities,
bool verbose = false);
std::optional<property_value> get_property(const std::vector<express::Base>& properties,
const std::string& name,
bool verbose = false);
property_map get_properties(const std::vector<express::Base>& properties,
bool verbose = false);
#endif // PSET_H
+69
View File
@@ -0,0 +1,69 @@
// This file was generated with the assistance of an AI coding tool.
#ifdef IFCOPENSHELL_HELPER_SCHEMA_2x3
#include "../ifcparse/schemas/Ifc2x3.h"
#define IFCOPENSHELL_HELPER_SCHEMA_CASE_2x3(M) M(Ifc2x3, "IFC2X3")
#else
#define IFCOPENSHELL_HELPER_SCHEMA_CASE_2x3(M)
#endif
#ifdef IFCOPENSHELL_HELPER_SCHEMA_4
#include "../ifcparse/schemas/Ifc4.h"
#define IFCOPENSHELL_HELPER_SCHEMA_CASE_4(M) M(Ifc4, "IFC4")
#else
#define IFCOPENSHELL_HELPER_SCHEMA_CASE_4(M)
#endif
#ifdef IFCOPENSHELL_HELPER_SCHEMA_4x1
#include "../ifcparse/schemas/Ifc4x1.h"
#define IFCOPENSHELL_HELPER_SCHEMA_CASE_4x1(M) M(Ifc4x1, "IFC4X1")
#else
#define IFCOPENSHELL_HELPER_SCHEMA_CASE_4x1(M)
#endif
#ifdef IFCOPENSHELL_HELPER_SCHEMA_4x2
#include "../ifcparse/schemas/Ifc4x2.h"
#define IFCOPENSHELL_HELPER_SCHEMA_CASE_4x2(M) M(Ifc4x2, "IFC4X2")
#else
#define IFCOPENSHELL_HELPER_SCHEMA_CASE_4x2(M)
#endif
#ifdef IFCOPENSHELL_HELPER_SCHEMA_4x3
#include "../ifcparse/schemas/Ifc4x3.h"
#define IFCOPENSHELL_HELPER_SCHEMA_CASE_4x3(M) M(Ifc4x3, "IFC4X3")
#else
#define IFCOPENSHELL_HELPER_SCHEMA_CASE_4x3(M)
#endif
#ifdef IFCOPENSHELL_HELPER_SCHEMA_4x3_tc1
#include "../ifcparse/schemas/Ifc4x3_tc1.h"
#define IFCOPENSHELL_HELPER_SCHEMA_CASE_4x3_tc1(M) M(Ifc4x3_tc1, "IFC4X3_TC1")
#else
#define IFCOPENSHELL_HELPER_SCHEMA_CASE_4x3_tc1(M)
#endif
#ifdef IFCOPENSHELL_HELPER_SCHEMA_4x3_add1
#include "../ifcparse/schemas/Ifc4x3_add1.h"
#define IFCOPENSHELL_HELPER_SCHEMA_CASE_4x3_add1(M) M(Ifc4x3_add1, "IFC4X3_ADD1")
#else
#define IFCOPENSHELL_HELPER_SCHEMA_CASE_4x3_add1(M)
#endif
#ifdef IFCOPENSHELL_HELPER_SCHEMA_4x3_add2
#include "../ifcparse/schemas/Ifc4x3_add2.h"
#define IFCOPENSHELL_HELPER_SCHEMA_CASE_4x3_add2(M) M(Ifc4x3_add2, "IFC4X3_ADD2")
#else
#define IFCOPENSHELL_HELPER_SCHEMA_CASE_4x3_add2(M)
#endif
// clang-format off
#define IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(M) \
IFCOPENSHELL_HELPER_SCHEMA_CASE_2x3(M) \
IFCOPENSHELL_HELPER_SCHEMA_CASE_4(M) \
IFCOPENSHELL_HELPER_SCHEMA_CASE_4x1(M) \
IFCOPENSHELL_HELPER_SCHEMA_CASE_4x2(M) \
IFCOPENSHELL_HELPER_SCHEMA_CASE_4x3(M) \
IFCOPENSHELL_HELPER_SCHEMA_CASE_4x3_tc1(M) \
IFCOPENSHELL_HELPER_SCHEMA_CASE_4x3_add1(M) \
IFCOPENSHELL_HELPER_SCHEMA_CASE_4x3_add2(M)
// clang-format on
+341 -219
View File
@@ -19,145 +19,184 @@
#include "unit.h"
#include "../ifcparse/exception.h"
#include "../ifcparse/file.h"
#include "../ifcparse/instance_data.h"
#include "../ifcparse/schema.h"
#include "schema_dispatch.i"
#include <algorithm>
#include <cctype>
const std::unordered_map<std::string, double> SI_PREFIXES = {
{ "EXA", 1e18 },
{ "PETA", 1e15 },
{ "TERA", 1e12 },
{ "GIGA", 1e9 },
{ "MEGA", 1e6 },
{ "KILO", 1e3 },
{ "HECTO", 1e2 },
{ "DECA", 1e1 },
{ "DECI", 1e-1 },
{ "CENTI", 1e-2 },
{ "MILLI", 1e-3 },
{ "MICRO", 1e-6 },
{ "NANO", 1e-9 },
{ "PICO", 1e-12 },
{ "FEMTO", 1e-15 },
{ "ATTO", 1e-18 },
{"EXA", 1e18},
{"PETA", 1e15},
{"TERA", 1e12},
{"GIGA", 1e9},
{"MEGA", 1e6},
{"KILO", 1e3},
{"HECTO", 1e2},
{"DECA", 1e1},
{"DECI", 1e-1},
{"CENTI", 1e-2},
{"MILLI", 1e-3},
{"MICRO", 1e-6},
{"NANO", 1e-9},
{"PICO", 1e-12},
{"FEMTO", 1e-15},
{"ATTO", 1e-18},
};
const std::unordered_map<std::string, std::string> SI_PREFIX_SYMBOLS = {
{ "EXA", "E" },
{ "PETA", "P" },
{ "TERA", "T" },
{ "GIGA", "G" },
{ "MEGA", "M" },
{ "KILO", "k" },
{ "HECTO", "h" },
{ "DECA", "da" },
{ "DECI", "d" },
{ "CENTI", "c" },
{ "MILLI", "m" },
{ "MICRO", "\xCE\xBC" }, // μ (UTF-8)
{ "NANO", "n" },
{ "PICO", "p" },
{ "FEMTO", "f" },
{ "ATTO", "a" },
{"EXA", "E"},
{"PETA", "P"},
{"TERA", "T"},
{"GIGA", "G"},
{"MEGA", "M"},
{"KILO", "k"},
{"HECTO", "h"},
{"DECA", "da"},
{"DECI", "d"},
{"CENTI", "c"},
{"MILLI", "m"},
{"MICRO", "\xCE\xBC"}, // μ (UTF-8)
{"NANO", "n"},
{"PICO", "p"},
{"FEMTO", "f"},
{"ATTO", "a"},
};
const std::unordered_map<std::string, double> SI_CONVERSIONS = {
{ "thou", 0.0000254 },
{ "inch", 0.0254 },
{ "foot", 0.3048 },
{ "yard", 0.914 },
{ "mile", 1609.0 },
{ "square thou", 6.4516e-10 },
{ "square inch", 0.0006452 },
{ "square foot", 0.09290304 },
{ "square yard", 0.83612736 },
{ "acre", 4046.86 },
{ "square mile", 2588881.0 },
{ "cubic thou", 1.6387064e-14 },
{ "cubic inch", 0.00001639 },
{ "cubic foot", 0.02831684671168849 },
{ "cubic yard", 0.7636 },
{ "cubic mile", 4165509529.0 },
{ "litre", 0.001 },
{ "fluid ounce uk", 0.0000284130625 },
{ "fluid ounce us", 0.00002957353 },
{ "pint uk", 0.000568 },
{ "pint us", 0.000473 },
{ "gallon uk", 0.004546 },
{ "gallon us", 0.003785 },
{ "degree", 0.0174532925199433 }, // pi / 180
{ "ounce", 0.02835 },
{ "pound", 0.454 },
{ "ton uk", 1016.0469088 },
{ "ton us", 907.18474 },
{ "tonne", 1000.0 },
{ "lbf", 4.4482216153 },
{ "kip", 4448.2216153 },
{ "psi", 6894.7572932 },
{ "ksi", 6894757.2932 },
{ "minute", 60.0 },
{ "hour", 3600.0 },
{ "day", 86400.0 },
{ "btu", 1055.056 },
{ "fahrenheit", 1.8 },
{"thou", 0.0000254},
{"inch", 0.0254},
{"foot", 0.3048},
{"yard", 0.914},
{"mile", 1609.0},
{"square thou", 6.4516e-10},
{"square inch", 0.0006452},
{"square foot", 0.09290304},
{"square yard", 0.83612736},
{"acre", 4046.86},
{"square mile", 2588881.0},
{"cubic thou", 1.6387064e-14},
{"cubic inch", 0.00001639},
{"cubic foot", 0.02831684671168849},
{"cubic yard", 0.7636},
{"cubic mile", 4165509529.0},
{"litre", 0.001},
{"fluid ounce uk", 0.0000284130625},
{"fluid ounce us", 0.00002957353},
{"pint uk", 0.000568},
{"pint us", 0.000473},
{"gallon uk", 0.004546},
{"gallon us", 0.003785},
{"degree", 0.0174532925199433}, // pi / 180
{"ounce", 0.02835},
{"pound", 0.454},
{"ton uk", 1016.0469088},
{"ton us", 907.18474},
{"tonne", 1000.0},
{"lbf", 4.4482216153},
{"kip", 4448.2216153},
{"psi", 6894.7572932},
{"ksi", 6894757.2932},
{"minute", 60.0},
{"hour", 3600.0},
{"day", 86400.0},
{"btu", 1055.056},
{"fahrenheit", 1.8},
};
const std::unordered_map<std::string, std::string> IMPERIAL_TYPES = {
{ "thou", "LENGTHUNIT" }, { "inch", "LENGTHUNIT" }, { "foot", "LENGTHUNIT" },
{ "yard", "LENGTHUNIT" }, { "mile", "LENGTHUNIT" },
{ "square thou", "AREAUNIT" }, { "square inch", "AREAUNIT" },
{ "square foot", "AREAUNIT" }, { "square yard", "AREAUNIT" },
{ "acre", "AREAUNIT" }, { "square mile", "AREAUNIT" },
{ "cubic thou", "VOLUMEUNIT" }, { "cubic inch", "VOLUMEUNIT" },
{ "cubic foot", "VOLUMEUNIT" }, { "cubic yard", "VOLUMEUNIT" },
{ "cubic mile", "VOLUMEUNIT" }, { "litre", "VOLUMEUNIT" },
{ "fluid ounce uk", "VOLUMEUNIT" }, { "fluid ounce us", "VOLUMEUNIT" },
{ "pint uk", "VOLUMEUNIT" }, { "pint us", "VOLUMEUNIT" },
{ "gallon uk", "VOLUMEUNIT" }, { "gallon us", "VOLUMEUNIT" },
{ "degree", "PLANEANGLEUNIT" },
{ "ounce", "MASSUNIT" }, { "pound", "MASSUNIT" },
{ "ton uk", "MASSUNIT" }, { "ton us", "MASSUNIT" }, { "tonne", "MASSUNIT" },
{ "lbf", "FORCEUNIT" }, { "kip", "FORCEUNIT" },
{ "psi", "PRESSUREUNIT" }, { "ksi", "PRESSUREUNIT" },
{ "minute", "TIMEUNIT" }, { "hour", "TIMEUNIT" }, { "day", "TIMEUNIT" },
{ "btu", "ENERGYUNIT" },
{ "fahrenheit", "THERMODYNAMICTEMPERATUREUNIT" },
{"thou", "LENGTHUNIT"},
{"inch", "LENGTHUNIT"},
{"foot", "LENGTHUNIT"},
{"yard", "LENGTHUNIT"},
{"mile", "LENGTHUNIT"},
{"square thou", "AREAUNIT"},
{"square inch", "AREAUNIT"},
{"square foot", "AREAUNIT"},
{"square yard", "AREAUNIT"},
{"acre", "AREAUNIT"},
{"square mile", "AREAUNIT"},
{"cubic thou", "VOLUMEUNIT"},
{"cubic inch", "VOLUMEUNIT"},
{"cubic foot", "VOLUMEUNIT"},
{"cubic yard", "VOLUMEUNIT"},
{"cubic mile", "VOLUMEUNIT"},
{"litre", "VOLUMEUNIT"},
{"fluid ounce uk", "VOLUMEUNIT"},
{"fluid ounce us", "VOLUMEUNIT"},
{"pint uk", "VOLUMEUNIT"},
{"pint us", "VOLUMEUNIT"},
{"gallon uk", "VOLUMEUNIT"},
{"gallon us", "VOLUMEUNIT"},
{"degree", "PLANEANGLEUNIT"},
{"ounce", "MASSUNIT"},
{"pound", "MASSUNIT"},
{"ton uk", "MASSUNIT"},
{"ton us", "MASSUNIT"},
{"tonne", "MASSUNIT"},
{"lbf", "FORCEUNIT"},
{"kip", "FORCEUNIT"},
{"psi", "PRESSUREUNIT"},
{"ksi", "PRESSUREUNIT"},
{"minute", "TIMEUNIT"},
{"hour", "TIMEUNIT"},
{"day", "TIMEUNIT"},
{"btu", "ENERGYUNIT"},
{"fahrenheit", "THERMODYNAMICTEMPERATUREUNIT"},
};
const std::unordered_map<std::string, std::string> UNIT_SYMBOLS = {
// SI base / derived
{ "CUBIC_METRE", "m3" },
{ "GRAM", "g" },
{ "SECOND", "s" },
{ "SQUARE_METRE", "m2" },
{ "METRE", "m" },
{ "NEWTON", "N" },
{ "PASCAL", "Pa" },
{"CUBIC_METRE", "m3"},
{"GRAM", "g"},
{"SECOND", "s"},
{"SQUARE_METRE", "m2"},
{"METRE", "m"},
{"NEWTON", "N"},
{"PASCAL", "Pa"},
// Conversion-based
{ "pound-force", "lbf" },
{ "pound-force per square inch", "psi" },
{ "thou", "th" }, { "inch", "in" }, { "foot", "ft" },
{ "yard", "yd" }, { "mile", "mi" },
{ "square thou", "th2" }, { "square inch", "in2" },
{ "square foot", "ft2" }, { "square yard", "yd2" },
{ "acre", "ac" }, { "square mile", "mi2" },
{ "cubic thou", "th3" }, { "cubic inch", "in3" },
{ "cubic foot", "ft3" }, { "cubic yard", "yd3" },
{ "cubic mile", "mi3" }, { "litre", "L" },
{ "fluid ounce uk", "fl oz" }, { "fluid ounce us", "fl oz" },
{ "pint uk", "pt" }, { "pint us", "pt" },
{ "gallon uk", "gal" }, { "gallon us", "gal" },
{ "degree", "\xC2\xB0" }, // °
{ "ounce", "oz" }, { "pound", "lb" },
{ "ton uk", "ton" }, { "ton us", "ton" }, { "tonne", "t" },
{ "lbf", "lbf" }, { "kip", "kip" },
{ "psi", "psi" }, { "ksi", "ksi" },
{ "minute", "min" }, { "hour", "hr" }, { "day", "day" },
{ "btu", "btu" },
{ "fahrenheit", "\xC2\xB0\x46" }, // °F
{"pound-force", "lbf"},
{"pound-force per square inch", "psi"},
{"thou", "th"},
{"inch", "in"},
{"foot", "ft"},
{"yard", "yd"},
{"mile", "mi"},
{"square thou", "th2"},
{"square inch", "in2"},
{"square foot", "ft2"},
{"square yard", "yd2"},
{"acre", "ac"},
{"square mile", "mi2"},
{"cubic thou", "th3"},
{"cubic inch", "in3"},
{"cubic foot", "ft3"},
{"cubic yard", "yd3"},
{"cubic mile", "mi3"},
{"litre", "L"},
{"fluid ounce uk", "fl oz"},
{"fluid ounce us", "fl oz"},
{"pint uk", "pt"},
{"pint us", "pt"},
{"gallon uk", "gal"},
{"gallon us", "gal"},
{"degree", "\xC2\xB0"}, // °
{"ounce", "oz"},
{"pound", "lb"},
{"ton uk", "ton"},
{"ton us", "ton"},
{"tonne", "t"},
{"lbf", "lbf"},
{"kip", "kip"},
{"psi", "psi"},
{"ksi", "ksi"},
{"minute", "min"},
{"hour", "hr"},
{"day", "day"},
{"btu", "btu"},
{"fahrenheit", "\xC2\xB0\x46"}, // °F
};
namespace {
@@ -165,117 +204,204 @@ namespace {
std::string to_lower(const std::string& s) {
std::string r;
r.resize(s.size());
std::transform(s.begin(), s.end(), r.begin(),
[](unsigned char c) { return std::tolower(c); });
std::transform(s.begin(), s.end(), r.begin(), [](unsigned char c) { return std::tolower(c); });
return r;
}
// Pull an enumeration string off an attribute_value, or "" if null/invalid.
std::string enum_string(const attribute_value& av) {
if (av.isNull()) return {};
if (av.type() != ifcopenshell::Argument_ENUMERATION) return {};
enumeration_reference er = av;
return std::string(er.value() ? er.value() : "");
[[noreturn]] void unsupported_schema(const std::string& name) {
throw ifcopenshell::exception("No helper implementation was built for schema " + name);
}
} // namespace
std::optional<double> numeric_value(const express::Base& value) {
if (!value) {
return std::nullopt;
}
const auto inner = value.get_attribute_value(0);
if (inner.isNull()) {
return std::nullopt;
}
if (inner.type() == ifcopenshell::Argument_DOUBLE) {
return static_cast<double>(inner);
}
if (inner.type() == ifcopenshell::Argument_INT) {
return static_cast<double>(static_cast<int>(inner));
}
return std::nullopt;
}
template <typename Schema>
std::optional<double> si_scale_from_named_unit_s(express::Base unit) {
double scale = 1.0;
while (auto conversion = unit.template as<typename Schema::IfcConversionBasedUnit>()) {
if (const auto it = SI_CONVERSIONS.find(to_lower(conversion.Name()));
it != SI_CONVERSIONS.end()) {
return scale * it->second;
}
const auto factor = conversion.ConversionFactor();
const auto value = numeric_value(factor.ValueComponent().concrete());
if (!value) {
return std::nullopt;
}
scale *= *value;
const auto component = factor.UnitComponent();
if (!component) {
return std::nullopt;
}
unit = component.concrete();
}
if (auto si = unit.template as<typename Schema::IfcSIUnit>()) {
std::string prefix;
if (const auto value = si.Prefix()) {
prefix = Schema::IfcSIPrefix::ToString(*value);
}
const std::string name = Schema::IfcSIUnitName::ToString(si.Name());
double multiplier = get_prefix_multiplier(prefix);
if (name.find("SQUARE") != std::string::npos) {
multiplier *= get_prefix_multiplier(prefix);
} else if (name.find("CUBIC") != std::string::npos) {
multiplier *= get_prefix_multiplier(prefix) * get_prefix_multiplier(prefix);
}
return scale * multiplier;
}
if (unit.template as<typename Schema::IfcContextDependentUnit>()) {
return std::nullopt;
}
return scale;
}
template <typename Schema>
std::optional<express::Base> get_unit_assignment_s(ifcopenshell::file* ifc_file) {
const auto projects = ifc_file->template instances_by_type<typename Schema::IfcProject>();
if (projects.empty()) {
return std::nullopt;
}
const auto assignment = projects.front().UnitsInContext();
if (!assignment) {
return std::nullopt;
}
return assignment;
}
template <typename Schema>
std::optional<express::Base> get_project_unit_s(ifcopenshell::file* ifc_file,
const std::string& unit_type) {
const auto assignment = get_unit_assignment_s<Schema>(ifc_file);
if (!assignment) {
return std::nullopt;
}
const auto typed_assignment = assignment->template as<typename Schema::IfcUnitAssignment>();
for (const auto& selected_unit : typed_assignment.Units()) {
const auto unit = selected_unit.concrete();
if (auto named = unit.template as<typename Schema::IfcNamedUnit>()) {
if (unit_type == Schema::IfcUnitEnum::ToString(named.UnitType())) {
return unit;
}
} else if (auto derived = unit.template as<typename Schema::IfcDerivedUnit>()) {
if (unit_type == Schema::IfcDerivedUnitEnum::ToString(derived.UnitType())) {
return unit;
}
}
}
return std::nullopt;
}
template <typename Schema>
double calculate_unit_scale_s(ifcopenshell::file* ifc_file, const std::string& unit_type) {
const auto unit = get_project_unit_s<Schema>(ifc_file, unit_type);
if (!unit) {
return 1.0;
}
return si_scale_from_named_unit_s<Schema>(*unit).value_or(1.0);
}
template <typename Schema>
void unit_name_s(const express::Base& unit, std::string& prefix, std::string& name) {
prefix.clear();
name.clear();
if (auto si = unit.template as<typename Schema::IfcSIUnit>()) {
if (const auto value = si.Prefix()) {
prefix = Schema::IfcSIPrefix::ToString(*value);
}
name = Schema::IfcSIUnitName::ToString(si.Name());
} else if (auto conversion = unit.template as<typename Schema::IfcConversionBasedUnit>()) {
name = conversion.Name();
} else if (auto contextual = unit.template as<typename Schema::IfcContextDependentUnit>()) {
name = contextual.Name();
}
}
template <typename Schema>
double convert_unit_s(double value, const express::Base& from_unit, const express::Base& to_unit) {
std::string from_prefix;
std::string from_name;
std::string to_prefix;
std::string to_name;
unit_name_s<Schema>(from_unit, from_prefix, from_name);
unit_name_s<Schema>(to_unit, to_prefix, to_name);
return convert(value, from_prefix, from_name, to_prefix, to_name);
}
} // namespace
double get_prefix_multiplier(const std::string& prefix) {
if (prefix.empty()) return 1.0;
if (prefix.empty()) {
return 1.0;
}
auto it = SI_PREFIXES.find(prefix);
return (it == SI_PREFIXES.end()) ? 1.0 : it->second;
}
std::optional<double> si_scale_from_named_unit(express::Base unit) {
double scale = 1.0;
while (unit && unit.declaration().is("IfcConversionBasedUnit")) {
auto e = unit.as<express::Entity>();
// Fast path: name in si_conversions table — matches python.
std::string name;
auto name_attr = e.get("Name");
if (!name_attr.isNull()) name = (std::string) name_attr;
if (auto it = SI_CONVERSIONS.find(to_lower(name));
it != SI_CONVERSIONS.end()) {
return scale * it->second;
}
// Otherwise walk the ConversionFactor chain.
auto cf_attr = e.get("ConversionFactor");
if (cf_attr.isNull()) return std::nullopt;
express::Base cf = cf_attr;
auto cf_e = cf.as<express::Entity>();
auto vc_attr = cf_e.get("ValueComponent");
if (vc_attr.isNull()) return std::nullopt;
express::Base vc = vc_attr;
// ValueComponent is an IfcValue SELECT wrapping a measure.
scale *= (double) vc.get_attribute_value(0);
auto uc_attr = cf_e.get("UnitComponent");
if (uc_attr.isNull()) return std::nullopt;
unit = (express::Base) uc_attr;
}
if (unit && unit.declaration().is("IfcSIUnit")) {
auto e = unit.as<express::Entity>();
const std::string prefix = enum_string(e.get("Prefix"));
const std::string name = enum_string(e.get("Name"));
double m = get_prefix_multiplier(prefix);
// SQUARE_/CUBIC_-prefixed SI names: prefix multiplier squared/cubed.
if (name.find("SQUARE") != std::string::npos) {
m *= get_prefix_multiplier(prefix);
} else if (name.find("CUBIC") != std::string::npos) {
m *= get_prefix_multiplier(prefix);
m *= get_prefix_multiplier(prefix);
}
return scale * m;
}
if (unit && unit.declaration().is("IfcContextDependentUnit")) {
// No conversion to SI is possible for a context-dependent unit.
if (!unit) {
return std::nullopt;
}
return scale;
const auto name = unit.declaration().schema()->name();
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (name == Identifier) \
return si_scale_from_named_unit_s<Schema>(unit);
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(name);
}
std::optional<express::Base> get_unit_assignment(ifcopenshell::file* ifc_file) {
auto projects = ifc_file->instances_by_type("IfcProject");
if (projects.empty()) return std::nullopt;
auto ua_attr = projects[0].as<express::Entity>().get("UnitsInContext");
if (ua_attr.isNull()) return std::nullopt;
return (express::Base) ua_attr;
const auto name = ifc_file->schema()->name();
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (name == Identifier) \
return get_unit_assignment_s<Schema>(ifc_file);
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(name);
}
std::optional<express::Base> get_project_unit(ifcopenshell::file* ifc_file,
const std::string& unit_type) {
auto ua = get_unit_assignment(ifc_file);
if (!ua) return std::nullopt;
auto units_attr = ua->as<express::Entity>().get("Units");
if (units_attr.isNull()) return std::nullopt;
std::vector<express::Base> units = units_attr;
for (const auto& unit : units) {
// IfcMonetaryUnit has no UnitType — guard via declaration check.
if (!unit.declaration().is("IfcNamedUnit") &&
!unit.declaration().is("IfcDerivedUnit")) {
continue;
}
auto ut = unit.as<express::Entity>().get("UnitType");
if (enum_string(ut) == unit_type) return unit;
}
return std::nullopt;
const std::string& unit_type) {
const auto name = ifc_file->schema()->name();
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (name == Identifier) \
return get_project_unit_s<Schema>(ifc_file, unit_type);
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(name);
}
double calculate_unit_scale(ifcopenshell::file* ifc_file,
const std::string& unit_type) {
auto unit = get_project_unit(ifc_file, unit_type);
if (!unit) return 1.0;
auto scale = si_scale_from_named_unit(*unit);
return scale.value_or(1.0);
const auto name = ifc_file->schema()->name();
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (name == Identifier) \
return calculate_unit_scale_s<Schema>(ifc_file, unit_type);
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(name);
}
double convert(double value,
const std::string& from_prefix, const std::string& from_unit,
const std::string& to_prefix, const std::string& to_unit) {
const std::string& from_prefix,
const std::string& from_unit,
const std::string& to_prefix,
const std::string& to_unit) {
const std::string fl = to_lower(from_unit);
const std::string tl = to_lower(to_unit);
@@ -309,21 +435,17 @@ double convert(double value,
}
double convert_unit(double value, express::Base from_unit, express::Base to_unit) {
auto pull = [](express::Base u, std::string& prefix, std::string& name) {
auto e = u.as<express::Entity>();
if (u.declaration().is("IfcSIUnit")) {
prefix = enum_string(e.get("Prefix"));
name = enum_string(e.get("Name"));
} else {
// IfcConversionBasedUnit / IfcContextDependentUnit: no Prefix,
// Name is a string attribute.
prefix.clear();
auto name_attr = e.get("Name");
name = name_attr.isNull() ? "" : (std::string) name_attr;
}
};
std::string fp, fn, tp, tn;
pull(from_unit, fp, fn);
pull(to_unit, tp, tn);
return convert(value, fp, fn, tp, tn);
if (!from_unit || !to_unit) {
return value;
}
const auto name = from_unit.declaration().schema()->name();
if (name != to_unit.declaration().schema()->name()) {
throw ifcopenshell::exception("Cannot convert units from different IFC schemas");
}
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
if (name == Identifier) \
return convert_unit_s<Schema>(value, from_unit, to_unit);
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
#undef IFCOPENSHELL_DISPATCH
unsupported_schema(name);
}
+9 -7
View File
@@ -18,9 +18,7 @@
********************************************************************************/
// Port of selected helpers from
// src/ifcopenshell-python/ifcopenshell/util/unit.py. Lives in src/ifcviewer/
// for now alongside Geolocation; will move out once ifcopenshell.util is
// ported to C++.
// src/ifcopenshell-python/ifcopenshell/util/unit.py.
#ifndef UNIT_H
#define UNIT_H
@@ -31,7 +29,9 @@
#include <string>
#include <unordered_map>
namespace ifcopenshell { class file; }
namespace ifcopenshell {
class file;
}
// SI prefix multipliers, e.g. "MILLI" -> 1e-3. Empty key not present;
// callers should pass an empty prefix string for "no prefix".
@@ -67,7 +67,7 @@ std::optional<express::Base> get_unit_assignment(ifcopenshell::file* ifc_file);
// First unit in the project's IfcUnitAssignment matching `unit_type`
// (e.g. "LENGTHUNIT"). Returns nullopt if not found.
std::optional<express::Base> get_project_unit(ifcopenshell::file* ifc_file,
const std::string& unit_type);
const std::string& unit_type);
// Project unit -> SI base scale (e.g. project in mm => 0.001). Defaults
// to 1.0 when no project unit of the requested type is set.
@@ -78,8 +78,10 @@ double calculate_unit_scale(ifcopenshell::file* ifc_file,
// SQUARE_/CUBIC_ prefixed SI names get the prefix multiplier squared/cubed
// (matches python ifcopenshell.util.unit.convert).
double convert(double value,
const std::string& from_prefix, const std::string& from_unit,
const std::string& to_prefix, const std::string& to_unit);
const std::string& from_prefix,
const std::string& from_unit,
const std::string& to_prefix,
const std::string& to_unit);
// Convert between two IfcNamedUnit entities. Pulls Name and Prefix off each
// and delegates to convert(). IfcConversionBasedUnit names that don't appear