mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Schema dispatch in helpers
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user