From 971cef0170cb92daf0a918eb58839efc0c086bc6 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Fri, 3 Jul 2026 13:26:30 +0200 Subject: [PATCH] Schema dispatch in helpers --- src/helpers/CMakeLists.txt | 14 + src/helpers/geolocation.cpp | 372 +++++++++------- src/helpers/geolocation.h | 29 +- src/helpers/placement.cpp | 197 +++++---- src/helpers/pset.cpp | 801 ++++++++++++++++++++++++++++++++++ src/helpers/pset.h | 109 +++++ src/helpers/schema_dispatch.i | 69 +++ src/helpers/unit.cpp | 560 ++++++++++++++---------- src/helpers/unit.h | 16 +- 9 files changed, 1690 insertions(+), 477 deletions(-) create mode 100644 src/helpers/pset.cpp create mode 100644 src/helpers/pset.h create mode 100644 src/helpers/schema_dispatch.i diff --git a/src/helpers/CMakeLists.txt b/src/helpers/CMakeLists.txt index d6efac5eec..7abd7411de 100644 --- a/src/helpers/CMakeLists.txt +++ b/src/helpers/CMakeLists.txt @@ -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}) diff --git a/src/helpers/geolocation.cpp b/src/helpers/geolocation.cpp index 4b4288e619..6fbb4c29fc 100644 --- a/src/helpers/geolocation.cpp +++ b/src/helpers/geolocation.cpp @@ -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 #include +#include #include 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 read_property_value_double(const express::Base& property) { - if (!property.declaration().is("IfcPropertySingleValue")) return std::nullopt; - auto pe = property.as(); - 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 +struct is_optional : std::false_type {}; + +template +struct is_optional> : std::true_type {}; + +template +struct is_ifc4_or_higher : std::false_type {}; + +template +struct is_ifc4_or_higher> : std::true_type {}; + +template +struct has_map_conversion_scaled : std::false_type {}; + +template +struct has_map_conversion_scaled> : std::true_type {}; + +template +struct has_factor_x : std::false_type {}; + +template +struct has_factor_x().FactorX())>> : std::true_type {}; + +template +struct has_rigid_operation : std::false_type {}; + +template +struct has_rigid_operation> : 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()) { + return *value; + } + if (const auto value = found->second.get_if()) { + return static_cast(*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(value); + } + if (value.type() == ifcopenshell::Argument_INT) { + return static_cast(static_cast(value)); + } + return fallback; +} + +template +double optional_number(const T& value, double fallback) { + if constexpr (is_optional::value) { + return value.value_or(fallback); + } else { + return value; } } -} // namespace - -std::optional -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().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(); - 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 props = pset.get("HasProperties"); - for (const auto& prop : props) { - if (!prop.declaration().is("IfcPropertySingleValue")) continue; - auto pe = prop.as(); - 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 conversions; - try { - conversions = ifc_file->instances_by_type("IfcCoordinateOperation"); - } catch (...) { - // Schema doesn't know IfcCoordinateOperation. +template +std::optional get_helmert_transformation_parameters_s(ifcopenshell::file* ifc_file) { + HelmertTransformation result; + if constexpr (!is_ifc4_or_higher::value) { + const auto projects = ifc_file->template instances_by_type(); + if (projects.empty()) { return std::nullopt; } - if (conversions.empty()) return std::nullopt; - const auto& conversion = conversions[0]; - auto entity = conversion.as(); - 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(); + 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(); + if (conversions.empty()) { + return std::nullopt; + } + const auto& conversion = conversions.front(); + if (auto map_conversion = conversion.template as()) { + 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::value) { + if (auto scaled = conversion.template as()) { + if constexpr (has_factor_x::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::value) { + if (auto rigid = conversion.template as()) { + 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 get_wcs(ifcopenshell::file* ifc_file) { - auto contexts = ifc_file->instances_by_type_excl_subtypes( - "IfcGeometricRepresentationContext"); +template +std::optional get_wcs_s(ifcopenshell::file* ifc_file) { + const auto contexts = + ifc_file->template instances_by_type_excl_subtypes(); express::Base wcs; - bool found = false; - for (const auto& ctx : contexts) { - auto entity = ctx.as(); - 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("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 +std::optional get_map_unit_s(ifcopenshell::file* ifc_file) { + if constexpr (!is_ifc4_or_higher::value) { + return std::nullopt; + } else { + const auto operations = + ifc_file->template instances_by_type(); + if (operations.empty()) { + return std::nullopt; + } + const auto target = operations.front().TargetCRS(); + const auto projected = target.template as(); + if (!projected) { + return std::nullopt; + } + const auto unit = projected.MapUnit(); + if (!unit) { + return std::nullopt; + } + return unit; + } +} + +} // namespace + +std::optional +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(ifc_file); + IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH) +#undef IFCOPENSHELL_DISPATCH + unsupported_schema(name); +} + +std::optional 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(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 get_map_unit(ifcopenshell::file* ifc_file) { - std::vector 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().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().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(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) { diff --git a/src/helpers/geolocation.h b/src/helpers/geolocation.h index 72730521e6..10a9c08ab0 100644 --- a/src/helpers/geolocation.h +++ b/src/helpers/geolocation.h @@ -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 - #include -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 / diff --git a/src/helpers/placement.cpp b/src/helpers/placement.cpp index 5a0ce1c1c6..f0fdfd75b9 100644 --- a/src/helpers/placement.cpp +++ b/src/helpers/placement.cpp @@ -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 #include namespace { @@ -32,14 +33,99 @@ Eigen::Vector3d safe_normalize(const Eigen::Vector3d& v, return (n > 0.0) ? Eigen::Vector3d(v / n) : fallback; } -std::vector read_direction_ratios(const express::Base& dir) { - if (!dir) return {}; - auto attr = dir.as().get("DirectionRatios"); - if (attr.isNull()) return {}; - return attr; +template +struct has_axis2_placement_linear : std::false_type {}; + +template +struct has_axis2_placement_linear> : std::true_type {}; + +[[noreturn]] void unsupported_schema(const std::string& name) { + throw ifcopenshell::exception("No helper implementation was built for schema " + name); } -} // namespace +template +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 +std::optional cartesian_point(const typename Schema::IfcPoint& point) { + const auto cartesian = point.template as(); + 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 +Eigen::Matrix4d axis2_placement_3d_s(const Placement& placement) { + const auto origin = cartesian_point(placement.Location()); + if (!origin) { + return Eigen::Matrix4d::Identity(); + } + const auto z = direction_or(placement.Axis(), Eigen::Vector3d::UnitZ()); + const auto x = direction_or(placement.RefDirection(), Eigen::Vector3d::UnitX()); + return axes_to_placement(*origin, z, x); +} + +template +Eigen::Matrix4d get_axis2_placement_s(const express::Base& placement) { + if (auto axis3 = placement.template as()) { + return axis2_placement_3d_s(axis3); + } + if constexpr (has_axis2_placement_linear::value) { + if (auto linear = placement.template as()) { + return axis2_placement_3d_s(linear); + } + } + if (auto axis2 = placement.template as()) { + const auto origin = cartesian_point(axis2.Location()); + if (!origin) { + return Eigen::Matrix4d::Identity(); + } + const auto x = direction_or(axis2.RefDirection(), Eigen::Vector3d::UnitX()); + return axes_to_placement(*origin, Eigen::Vector3d::UnitZ(), x); + } + if (auto axis1 = placement.template as()) { + const auto origin = cartesian_point(axis1.Location()); + if (!origin) { + return Eigen::Matrix4d::Identity(); + } + const auto z = direction_or(axis1.Axis(), Eigen::Vector3d::UnitZ()); + return axes_to_placement(*origin, z, Eigen::Vector3d::UnitX()); + } + return Eigen::Matrix4d::Identity(); +} + +template +Eigen::Matrix4d get_local_placement_s(const express::Base& placement) { + if (auto local = placement.template as()) { + Eigen::Matrix4d parent = Eigen::Matrix4d::Identity(); + if (const auto relative_to = local.PlacementRelTo()) { + parent = get_local_placement_s(relative_to); + } + const auto relative = local.RelativePlacement(); + if (!relative) { + return parent; + } + return parent * get_axis2_placement_s(relative.concrete()); + } + return get_axis2_placement_s(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(); - - 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().get("Coordinates"); - if (coords_attr.isNull()) return Eigen::Matrix4d::Identity(); - std::vector 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().get("Coordinates"); - if (coords_attr.isNull()) return Eigen::Matrix4d::Identity(); - std::vector 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().get("Coordinates"); - if (coords_attr.isNull()) return Eigen::Matrix4d::Identity(); - std::vector 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(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(); - 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(placement); + IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH) +#undef IFCOPENSHELL_DISPATCH + unsupported_schema(name); } diff --git a/src/helpers/pset.cpp b/src/helpers/pset.cpp new file mode 100644 index 0000000000..628e0309ae --- /dev/null +++ b/src/helpers/pset.cpp @@ -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 . * + * * + ********************************************************************************/ + +// 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 +#include + +namespace { + +template +struct is_optional : std::false_type {}; + +template +struct is_optional> : std::true_type {}; + +template +struct is_ifc4_or_higher : std::false_type {}; + +template +struct is_ifc4_or_higher> : std::true_type {}; + +template +struct has_predefined_property_set : std::false_type {}; + +template +struct has_predefined_property_set> : std::true_type {}; + +template +struct has_set_point_value : std::false_type {}; + +template +struct has_set_point_value().SetPointValue())>> : std::true_type {}; + +template +struct has_curve_interpolation : std::false_type {}; + +template +struct has_curve_interpolation().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 +property_list scalar_list(const std::vector& 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(value); + case ifcopenshell::Argument_BOOL: + return static_cast(value); + case ifcopenshell::Argument_LOGICAL: { + const boost::logic::tribool logical = value; + if (boost::logic::indeterminate(logical)) { + return "UNKNOWN"; + } + return static_cast(logical); + } + case ifcopenshell::Argument_DOUBLE: + return static_cast(value); + case ifcopenshell::Argument_STRING: + return static_cast(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>(value)); + case ifcopenshell::Argument_AGGREGATE_OF_DOUBLE: + return scalar_list(static_cast>(value)); + case ifcopenshell::Argument_AGGREGATE_OF_STRING: + return scalar_list(static_cast>(value)); + case ifcopenshell::Argument_AGGREGATE_OF_ENTITY_INSTANCE: { + property_list result; + for (const auto& instance : static_cast>(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 +property_value from_select(const Select& value) { + if (!value) { + return {}; + } + return from_attribute(value.concrete().get_attribute_value(0)); +} + +template +property_value from_select_values(const Values& maybe_values) { + if constexpr (is_optional::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& 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 +property_map get_properties_s(const std::vector& properties, bool verbose); + +template +property_map get_quantities_s(const std::vector& quantities, bool verbose); + +template +std::optional property_value_s(const typename Schema::IfcProperty& property, bool verbose) { + property_value result; + std::optional value_type; + + if (auto single = property.template as()) { + const auto nominal = single.NominalValue(); + result = from_select(nominal); + if (nominal) { + value_type = nominal.concrete().declaration().name(); + } + } else if (auto enumerated = property.template as()) { + result = from_select_values(enumerated.EnumerationValues()); + } else if (auto list = property.template as()) { + result = from_select_values(list.ListValues()); + } else if (auto bounded = property.template as()) { + 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::value) { + data["SetPointValue"] = from_select(bounded.SetPointValue()); + } + result = std::move(data); + } else if (auto table = property.template as()) { + 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::value) { + if (const auto interpolation = table.CurveInterpolation()) { + data["CurveInterpolation"] = + Schema::IfcCurveInterpolationEnum::ToString(*interpolation); + } + } + result = std::move(data); + } else if (auto complex = property.template as()) { + result = property_map{ + {"id", complex.id()}, + {"type", complex.declaration().name()}, + {"UsageName", complex.UsageName()}, + {"properties", get_properties_s(complex.HasProperties(), verbose)}, + }; + } else { + return std::nullopt; + } + + if (verbose) { + return verbose_value(property, std::move(result), value_type); + } + return result; +} + +template +std::optional get_property_s(const std::vector& properties, + const std::string& name, + bool verbose) { + for (const auto& property : properties) { + if (property.Name() == name) { + return property_value_s(property, verbose); + } + } + return std::nullopt; +} + +template +property_map get_properties_s(const std::vector& properties, bool verbose) { + property_map result; + for (const auto& property : properties) { + if (auto value = property_value_s(property, verbose)) { + result[property.Name()] = std::move(*value); + } + } + return result; +} + +template +std::optional quantity_value_s(const typename Schema::IfcPhysicalQuantity& quantity, + bool verbose) { + property_value result; + if (quantity.template as()) { + result = from_attribute(quantity.get_attribute_value(3)); + } else if (auto complex = quantity.template as()) { + result = property_map{ + {"id", complex.id()}, + {"type", complex.declaration().name()}, + {"Discrimination", complex.Discrimination()}, + {"properties", get_quantities_s(complex.HasQuantities(), verbose)}, + }; + } else { + return std::nullopt; + } + if (verbose) { + return verbose_value(quantity, std::move(result)); + } + return result; +} + +template +std::optional get_quantity_s( + const std::vector& quantities, + const std::string& name, + bool verbose) { + for (const auto& quantity : quantities) { + if (quantity.Name() == name) { + return quantity_value_s(quantity, verbose); + } + } + return std::nullopt; +} + +template +property_map get_quantities_s(const std::vector& quantities, bool verbose) { + property_map result; + for (const auto& quantity : quantities) { + if (auto value = quantity_value_s(quantity, verbose)) { + result[quantity.Name()] = std::move(*value); + } + } + return result; +} + +template +std::optional predefined_properties_s( + const typename Schema::IfcPreDefinedPropertySet& definition, + const std::optional& 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 +std::optional get_property_definition_s( + const express::Base& definition, + const std::optional& property_name, + bool verbose) { + if (!definition) { + return std::nullopt; + } + if (auto quantity = definition.template as()) { + if (property_name) { + return get_quantity_s(quantity.Quantities(), *property_name, verbose); + } + auto result = get_quantities_s(quantity.Quantities(), verbose); + result["id"] = definition.id(); + return result; + } + if (auto pset = definition.template as()) { + if (property_name) { + return get_property_s(pset.HasProperties(), *property_name, verbose); + } + auto result = get_properties_s(pset.HasProperties(), verbose); + result["id"] = definition.id(); + return result; + } + if constexpr (is_ifc4_or_higher::value) { + if (auto extended = definition.template as()) { + if (property_name) { + return get_property_s(extended.Properties(), *property_name, verbose); + } + auto result = get_properties_s(extended.Properties(), verbose); + result["id"] = definition.id(); + return result; + } + } else { + if (auto extended = definition.template as()) { + if (property_name) { + return get_property_s(extended.ExtendedProperties(), *property_name, verbose); + } + auto result = get_properties_s(extended.ExtendedProperties(), verbose); + result["id"] = definition.id(); + return result; + } + } + if constexpr (has_predefined_property_set::value) { + if (auto predefined = definition.template as()) { + return predefined_properties_s(predefined, property_name); + } + } + return std::nullopt; +} + +template +express::Base concrete_definition(const Definition& definition) { + if constexpr (std::is_base_of_v) { + return definition.concrete(); + } else { + return definition; + } +} + +template +bool definition_is_pset_s(const express::Base& definition) { + if (definition.template as()) { + return true; + } + if constexpr (has_predefined_property_set::value) { + if (definition.template as()) { + return true; + } + } + if constexpr (!is_ifc4_or_higher::value) { + if (definition.template as()) { + return true; + } + } + return false; +} + +template +std::optional definition_name_s(const express::Base& definition) { + if (auto pset = definition.template as()) { + return pset.Name(); + } + if (auto quantity = definition.template as()) { + return quantity.Name(); + } + if constexpr (is_ifc4_or_higher::value) { + if (auto extended = definition.template as()) { + return extended.Name(); + } + } else { + if (auto extended = definition.template as()) { + return extended.Name(); + } + } + if constexpr (has_predefined_property_set::value) { + if (auto predefined = definition.template as()) { + return predefined.Name(); + } + } + return std::nullopt; +} + +template +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(definition.template as()); + if (psets_only && !definition_is_pset_s(definition)) { + return; + } + if (qtos_only && !is_quantity) { + return; + } + const auto name = definition_name_s(definition); + if (!name) { + return; + } + auto values = get_property_definition_s(definition, std::nullopt, verbose); + if (!values) { + return; + } + const auto* properties = values->template get_if(); + if (!properties) { + return; + } + auto& destination = result[*name]; + for (const auto& [property_name, value] : *properties) { + destination.insert_or_assign(property_name, value); + } +} + +template +typename Schema::IfcTypeObject get_type_s(const typename Schema::IfcObject& object) { + if constexpr (is_ifc4_or_higher::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()) { + return by_type.RelatingType(); + } + } + } + return {}; +} + +template +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()) { + if (const auto definitions = type.HasPropertySets()) { + for (const auto& definition : *definitions) { + merge_definition_s(result, definition, psets_only, qtos_only, verbose); + } + } + return result; + } + + if constexpr (is_ifc4_or_higher::value) { + if (auto material = element.template as()) { + if (qtos_only) { + return result; + } + for (const auto& definition : material.HasProperties()) { + merge_definition_s(result, definition, false, false, verbose); + } + return result; + } + if (auto profile = element.template as()) { + if (qtos_only) { + return result; + } + for (const auto& definition : profile.HasProperties()) { + merge_definition_s(result, definition, false, false, verbose); + } + return result; + } + } else { + if (auto material = element.template as()) { + if (qtos_only) { + return result; + } + for (const auto& definition : + element.file()->template instances_by_type()) { + if (definition.Material() == material) { + merge_definition_s(result, definition, false, false, verbose); + } + } + return result; + } + if (element.template as()) { + return result; + } + } + + if (auto object = element.template as()) { + if (should_inherit) { + if (const auto type = get_type_s(object)) { + result = get_psets_s(type, psets_only, qtos_only, false, verbose); + } + } + for (const auto& relationship : object.IsDefinedBy()) { + if (auto by_properties = relationship.template as()) { + merge_definition_s(result, + concrete_definition(by_properties.RelatingPropertyDefinition()), + psets_only, + qtos_only, + verbose); + } + } + } + return result; +} + +template +std::optional 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(); + if (!object) { + return std::nullopt; + } + const auto type = get_type_s(object); + if (!type) { + return std::nullopt; + } + const auto psets = get_psets_s(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 +std::vector cast_entities(const std::vector& values) { + std::vector result; + result.reserve(values.size()); + for (const auto& value : values) { + if (auto typed = value.template as()) { + 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; + if constexpr (std::is_same_v) { + stream << "null"; + } else if constexpr (std::is_same_v) { + stream << (item ? "true" : "false"); + } else if constexpr (std::is_same_v) { + if (item) { + item.to_string(stream); + } else { + stream << "null"; + } + } else if constexpr (std::is_same_v) { + 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) { + 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(element, psets_only, qtos_only, should_inherit, verbose); \ + } + IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH) +#undef IFCOPENSHELL_DISPATCH + unsupported_schema(name); +} + +std::optional get_pset(const express::Base& element, + const std::string& name, + const std::optional& 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(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 get_property_definition( + const express::Base& definition, + const std::optional& 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(definition, property_name, verbose); + IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH) +#undef IFCOPENSHELL_DISPATCH + unsupported_schema(name); +} + +std::optional get_quantity(const std::vector& 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(cast_entities(quantities), \ + name, \ + verbose); \ + } + IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH) +#undef IFCOPENSHELL_DISPATCH + unsupported_schema(schema); +} + +property_map get_quantities(const std::vector& 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(cast_entities(quantities), \ + verbose); \ + } + IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH) +#undef IFCOPENSHELL_DISPATCH + unsupported_schema(schema); +} + +std::optional get_property(const std::vector& 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(cast_entities(properties), \ + name, \ + verbose); \ + } + IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH) +#undef IFCOPENSHELL_DISPATCH + unsupported_schema(schema); +} + +property_map get_properties(const std::vector& 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(cast_entities(properties), \ + verbose); \ + } + IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH) +#undef IFCOPENSHELL_DISPATCH + unsupported_schema(schema); +} diff --git a/src/helpers/pset.h b/src/helpers/pset.h new file mode 100644 index 0000000000..b84e3c8517 --- /dev/null +++ b/src/helpers/pset.h @@ -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 . * + * * + ********************************************************************************/ + +// This file was generated with the assistance of an AI coding tool. + +#ifndef PSET_H +#define PSET_H + +#include "../ifcparse/express.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +struct property_value; + +using property_list = std::vector; +using property_map = std::map; +using element_properties = std::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; + + storage_type value; + + property_value() = default; + property_value(bool value) : value(value) {} + property_value(int value) : value(static_cast(value)) {} + property_value(unsigned value) : value(static_cast(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(value); } + + template + const T* get_if() const { + return std::get_if(&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 get_pset( + const express::Base& element, + const std::string& name, + const std::optional& 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 get_property_definition( + const express::Base& definition, + const std::optional& property_name = std::nullopt, + bool verbose = false); + +std::optional get_quantity(const std::vector& quantities, + const std::string& name, + bool verbose = false); +property_map get_quantities(const std::vector& quantities, + bool verbose = false); + +std::optional get_property(const std::vector& properties, + const std::string& name, + bool verbose = false); +property_map get_properties(const std::vector& properties, + bool verbose = false); + +#endif // PSET_H diff --git a/src/helpers/schema_dispatch.i b/src/helpers/schema_dispatch.i new file mode 100644 index 0000000000..5dd58c0294 --- /dev/null +++ b/src/helpers/schema_dispatch.i @@ -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 diff --git a/src/helpers/unit.cpp b/src/helpers/unit.cpp index 350cf097f0..0dfd92df1f 100644 --- a/src/helpers/unit.cpp +++ b/src/helpers/unit.cpp @@ -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 #include const std::unordered_map 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 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 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 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 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 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(inner); + } + if (inner.type() == ifcopenshell::Argument_INT) { + return static_cast(static_cast(inner)); + } + return std::nullopt; +} + +template +std::optional si_scale_from_named_unit_s(express::Base unit) { + double scale = 1.0; + while (auto conversion = unit.template as()) { + 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()) { + 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()) { + return std::nullopt; + } + return scale; +} + +template +std::optional get_unit_assignment_s(ifcopenshell::file* ifc_file) { + const auto projects = ifc_file->template instances_by_type(); + if (projects.empty()) { + return std::nullopt; + } + const auto assignment = projects.front().UnitsInContext(); + if (!assignment) { + return std::nullopt; + } + return assignment; +} + +template +std::optional get_project_unit_s(ifcopenshell::file* ifc_file, + const std::string& unit_type) { + const auto assignment = get_unit_assignment_s(ifc_file); + if (!assignment) { + return std::nullopt; + } + const auto typed_assignment = assignment->template as(); + for (const auto& selected_unit : typed_assignment.Units()) { + const auto unit = selected_unit.concrete(); + if (auto named = unit.template as()) { + if (unit_type == Schema::IfcUnitEnum::ToString(named.UnitType())) { + return unit; + } + } else if (auto derived = unit.template as()) { + if (unit_type == Schema::IfcDerivedUnitEnum::ToString(derived.UnitType())) { + return unit; + } + } + } + return std::nullopt; +} + +template +double calculate_unit_scale_s(ifcopenshell::file* ifc_file, const std::string& unit_type) { + const auto unit = get_project_unit_s(ifc_file, unit_type); + if (!unit) { + return 1.0; + } + return si_scale_from_named_unit_s(*unit).value_or(1.0); +} + +template +void unit_name_s(const express::Base& unit, std::string& prefix, std::string& name) { + prefix.clear(); + name.clear(); + if (auto si = unit.template as()) { + if (const auto value = si.Prefix()) { + prefix = Schema::IfcSIPrefix::ToString(*value); + } + name = Schema::IfcSIUnitName::ToString(si.Name()); + } else if (auto conversion = unit.template as()) { + name = conversion.Name(); + } else if (auto contextual = unit.template as()) { + name = contextual.Name(); + } +} + +template +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(from_unit, from_prefix, from_name); + unit_name_s(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 si_scale_from_named_unit(express::Base unit) { - double scale = 1.0; - while (unit && unit.declaration().is("IfcConversionBasedUnit")) { - auto e = unit.as(); - - // 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(); - 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(); - 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(unit); + IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH) +#undef IFCOPENSHELL_DISPATCH + unsupported_schema(name); } std::optional 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().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(ifc_file); + IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH) +#undef IFCOPENSHELL_DISPATCH + unsupported_schema(name); } std::optional 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().get("Units"); - if (units_attr.isNull()) return std::nullopt; - std::vector 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().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(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(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(); - 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(value, from_unit, to_unit); + IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH) +#undef IFCOPENSHELL_DISPATCH + unsupported_schema(name); } diff --git a/src/helpers/unit.h b/src/helpers/unit.h index 00b88023a9..f93cc691dd 100644 --- a/src/helpers/unit.h +++ b/src/helpers/unit.h @@ -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 #include -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 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 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