mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 09:05:18 +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)
|
# (ifcopenshell.util.geolocation)
|
||||||
# * Placement — IfcLocalPlacement / IfcAxis2Placement reduction
|
# * Placement — IfcLocalPlacement / IfcAxis2Placement reduction
|
||||||
# (ifcopenshell.util.placement)
|
# (ifcopenshell.util.placement)
|
||||||
|
# * Pset — property and quantity retrieval
|
||||||
|
# (ifcopenshell.util.element)
|
||||||
|
|
||||||
find_package(Eigen3 REQUIRED)
|
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})
|
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
|
set_target_properties(helpers PROPERTIES
|
||||||
VERSION "${PROJECT_VERSION}"
|
VERSION "${PROJECT_VERSION}"
|
||||||
SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}"
|
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
|
target_link_libraries(helpers PUBLIC
|
||||||
IfcParse
|
IfcParse
|
||||||
Eigen3::Eigen
|
Eigen3::Eigen
|
||||||
|
PRIVATE
|
||||||
|
${HELPER_SCHEMA_LIBRARIES}
|
||||||
)
|
)
|
||||||
|
|
||||||
install(TARGETS helpers EXPORT ${IFCOPENSHELL_EXPORT_TARGETS})
|
install(TARGETS helpers EXPORT ${IFCOPENSHELL_EXPORT_TARGETS})
|
||||||
|
|||||||
+223
-149
@@ -18,166 +18,235 @@
|
|||||||
********************************************************************************/
|
********************************************************************************/
|
||||||
|
|
||||||
#include "geolocation.h"
|
#include "geolocation.h"
|
||||||
#include "placement.h"
|
|
||||||
|
|
||||||
#include "../ifcparse/express.h"
|
#include "../ifcparse/exception.h"
|
||||||
#include "../ifcparse/file.h"
|
#include "../ifcparse/file.h"
|
||||||
#include "../ifcparse/instance_data.h"
|
#include "../ifcparse/instance_data.h"
|
||||||
#include "../ifcparse/schema.h"
|
#include "placement.h"
|
||||||
|
#include "pset.h"
|
||||||
|
#include "schema_dispatch.i"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Read a numeric NominalValue out of an IfcPropertySingleValue. IFC2X3
|
template <typename T>
|
||||||
// ePSet_MapConversion stores eastings/northings/scale as IfcLengthMeasure or
|
struct is_optional : std::false_type {};
|
||||||
// IfcReal wrapped inside IfcValue (a SELECT) — get_attribute_value(0) peels
|
|
||||||
// the wrapper. Returns nullopt if the value is missing or non-numeric.
|
template <typename T>
|
||||||
std::optional<double> read_property_value_double(const express::Base& property) {
|
struct is_optional<std::optional<T>> : std::true_type {};
|
||||||
if (!property.declaration().is("IfcPropertySingleValue")) return std::nullopt;
|
|
||||||
auto pe = property.as<express::Entity>();
|
template <typename Schema, typename = void>
|
||||||
auto nv = pe.get("NominalValue");
|
struct is_ifc4_or_higher : std::false_type {};
|
||||||
if (nv.isNull()) return std::nullopt;
|
|
||||||
express::Base wrapper = nv;
|
template <typename Schema>
|
||||||
auto inner = wrapper.get_attribute_value(0);
|
struct is_ifc4_or_higher<Schema, std::void_t<typename Schema::IfcCoordinateOperation>> : std::true_type {};
|
||||||
if (inner.isNull()) return std::nullopt;
|
|
||||||
switch (inner.type()) {
|
template <typename Schema, typename = void>
|
||||||
case ifcopenshell::Argument_DOUBLE: return (double) inner;
|
struct has_map_conversion_scaled : std::false_type {};
|
||||||
case ifcopenshell::Argument_INT: return (double)(int) inner;
|
|
||||||
default: return std::nullopt;
|
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
|
template <typename Schema>
|
||||||
|
std::optional<HelmertTransformation> get_helmert_transformation_parameters_s(ifcopenshell::file* ifc_file) {
|
||||||
std::optional<HelmertTransformation>
|
HelmertTransformation result;
|
||||||
get_helmert_transformation_parameters(ifcopenshell::file* ifc_file) {
|
if constexpr (!is_ifc4_or_higher<Schema>::value) {
|
||||||
HelmertTransformation p;
|
const auto projects = ifc_file->template instances_by_type<typename Schema::IfcProject>();
|
||||||
const std::string schema_name = ifc_file->schema()->name();
|
if (projects.empty()) {
|
||||||
|
|
||||||
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.
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
if (conversions.empty()) return std::nullopt;
|
const auto conversion = get_pset(projects.front(), "ePSet_MapConversion");
|
||||||
const auto& conversion = conversions[0];
|
if (!conversion) {
|
||||||
auto entity = conversion.as<express::Entity>();
|
return std::nullopt;
|
||||||
const std::string type_name = conversion.declaration().name();
|
}
|
||||||
|
const auto* properties = conversion->template get_if<property_map>();
|
||||||
auto get_or = [&](const std::string& name, double fallback) {
|
if (!properties) {
|
||||||
auto a = entity.get(name);
|
return std::nullopt;
|
||||||
return a.isNull() ? fallback : (double) a;
|
}
|
||||||
};
|
result.e = numeric_property(*properties, "Eastings", 0.0);
|
||||||
|
result.n = numeric_property(*properties, "Northings", 0.0);
|
||||||
if (conversion.declaration().is("IfcMapConversion")) {
|
result.h = numeric_property(*properties, "OrthogonalHeight", 0.0);
|
||||||
p.e = get_or("Eastings", 0.0);
|
result.xaa = numeric_property(*properties, "XAxisAbscissa", 0.0);
|
||||||
p.n = get_or("Northings", 0.0);
|
result.xao = numeric_property(*properties, "XAxisOrdinate", 0.0);
|
||||||
p.h = get_or("OrthogonalHeight", 0.0);
|
result.scale = numeric_property(*properties, "Scale", 1.0);
|
||||||
p.xaa = get_or("XAxisAbscissa", 0.0);
|
} else {
|
||||||
p.xao = get_or("XAxisOrdinate", 0.0);
|
const auto conversions =
|
||||||
p.scale = get_or("Scale", 1.0);
|
ifc_file->template instances_by_type<typename Schema::IfcCoordinateOperation>();
|
||||||
if (p.scale == 0.0) p.scale = 1.0;
|
if (conversions.empty()) {
|
||||||
|
return std::nullopt;
|
||||||
if (type_name == "IfcMapConversionScaled") {
|
}
|
||||||
p.factor_x = entity.get("FactorX");
|
const auto& conversion = conversions.front();
|
||||||
p.factor_y = entity.get("FactorY");
|
if (auto map_conversion = conversion.template as<typename Schema::IfcMapConversion>()) {
|
||||||
p.factor_z = entity.get("FactorZ");
|
result.e = map_conversion.Eastings();
|
||||||
} else {
|
result.n = map_conversion.Northings();
|
||||||
p.factor_x = p.factor_y = p.factor_z = 1.0;
|
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 {
|
} else {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p.xaa == 0.0 && p.xao == 0.0) {
|
if (result.scale == 0.0) {
|
||||||
p.xaa = 1.0;
|
result.scale = 1.0;
|
||||||
p.xao = 0.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) {
|
template <typename Schema>
|
||||||
auto contexts = ifc_file->instances_by_type_excl_subtypes(
|
std::optional<Eigen::Matrix4d> get_wcs_s(ifcopenshell::file* ifc_file) {
|
||||||
"IfcGeometricRepresentationContext");
|
const auto contexts =
|
||||||
|
ifc_file->template instances_by_type_excl_subtypes<typename Schema::IfcGeometricRepresentationContext>();
|
||||||
express::Base wcs;
|
express::Base wcs;
|
||||||
bool found = false;
|
for (const auto& context : contexts) {
|
||||||
for (const auto& ctx : contexts) {
|
const auto placement = context.WorldCoordinateSystem();
|
||||||
auto entity = ctx.as<express::Entity>();
|
if (!placement) {
|
||||||
auto wcs_attr = entity.get("WorldCoordinateSystem");
|
continue;
|
||||||
if (wcs_attr.isNull()) continue;
|
}
|
||||||
wcs = (express::Base) wcs_attr;
|
wcs = placement.concrete();
|
||||||
found = true;
|
if (context.ContextType() == std::optional<std::string>("Model")) {
|
||||||
auto ctype_attr = entity.get("ContextType");
|
break;
|
||||||
if (!ctype_attr.isNull()) {
|
|
||||||
std::string ctype = ctype_attr;
|
|
||||||
if (ctype == "Model") break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found) return std::nullopt;
|
if (!wcs) {
|
||||||
const auto& decl = wcs.declaration();
|
|
||||||
if (!(decl.is("IfcAxis2Placement3D") || decl.is("IfcAxis2PlacementLinear"))) {
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return get_axis2_placement(wcs);
|
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,
|
Eigen::Matrix4d local_to_global(const Eigen::Matrix4d& matrix,
|
||||||
const HelmertTransformation& p) {
|
const HelmertTransformation& p) {
|
||||||
const double theta = std::atan2(p.xao, p.xaa);
|
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;
|
S(2, 2) = p.scale * p.factor_z;
|
||||||
|
|
||||||
Eigen::Matrix4d R = Eigen::Matrix4d::Identity();
|
Eigen::Matrix4d R = Eigen::Matrix4d::Identity();
|
||||||
R(0, 0) = c; R(0, 1) = -s;
|
R(0, 0) = c;
|
||||||
R(1, 0) = s; R(1, 1) = c;
|
R(0, 1) = -s;
|
||||||
|
R(1, 0) = s;
|
||||||
|
R(1, 1) = c;
|
||||||
|
|
||||||
Eigen::Matrix4d result = R * S * matrix;
|
Eigen::Matrix4d result = R * S * matrix;
|
||||||
// The scale was baked into the rotation+scale matrix so each axis column
|
// 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) {
|
for (int col = 0; col < 3; ++col) {
|
||||||
Eigen::Vector3d v = result.block<3, 1>(0, col);
|
Eigen::Vector3d v = result.block<3, 1>(0, col);
|
||||||
const double n = v.norm();
|
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(0, 3) += p.e;
|
||||||
result(1, 3) += p.n;
|
result(1, 3) += p.n;
|
||||||
@@ -212,7 +285,9 @@ Eigen::Matrix4d auto_local_to_global(ifcopenshell::file* ifc_file,
|
|||||||
const Eigen::Matrix4d& matrix,
|
const Eigen::Matrix4d& matrix,
|
||||||
bool should_return_in_map_units) {
|
bool should_return_in_map_units) {
|
||||||
auto params = get_helmert_transformation_parameters(ifc_file);
|
auto params = get_helmert_transformation_parameters(ifc_file);
|
||||||
if (!params) return matrix;
|
if (!params) {
|
||||||
|
return matrix;
|
||||||
|
}
|
||||||
|
|
||||||
Eigen::Matrix4d m = matrix;
|
Eigen::Matrix4d m = matrix;
|
||||||
if (auto wcs = get_wcs(ifc_file)) {
|
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
|
// they apply to placement translations on compose; this is the behaviour
|
||||||
// IfcMapConversionScaled actually wants ("grid distance ≠ ground
|
// IfcMapConversionScaled actually wants ("grid distance ≠ ground
|
||||||
// distance" — buildings on the grid should appear scaled by f).
|
// 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(0, 0) = c * p.factor_x;
|
||||||
M(1, 0) = s * p.factor_x; M(1, 1) = c * p.factor_y; M(1, 2) = 0.0;
|
M(0, 1) = -s * p.factor_y;
|
||||||
M(2, 0) = 0.0; M(2, 1) = 0.0; M(2, 2) = p.factor_z;
|
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(0, 3) = p.e * map_unit_to_meters;
|
||||||
M(1, 3) = p.n * map_unit_to_meters;
|
M(1, 3) = p.n * map_unit_to_meters;
|
||||||
M(2, 3) = p.h * 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::optional<express::Base> get_map_unit(ifcopenshell::file* ifc_file) {
|
||||||
std::vector<express::Base> coordops;
|
const auto name = ifc_file->schema()->name();
|
||||||
try {
|
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
|
||||||
coordops = ifc_file->instances_by_type("IfcCoordinateOperation");
|
if (name == Identifier) \
|
||||||
} catch (...) {
|
return get_map_unit_s<Schema>(ifc_file);
|
||||||
return std::nullopt;
|
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
|
||||||
}
|
#undef IFCOPENSHELL_DISPATCH
|
||||||
if (coordops.empty()) return std::nullopt;
|
unsupported_schema(name);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double x_axis_to_angle_deg(double xaa, double xao) {
|
double x_axis_to_angle_deg(double xaa, double xao) {
|
||||||
|
|||||||
+13
-16
@@ -18,11 +18,7 @@
|
|||||||
********************************************************************************/
|
********************************************************************************/
|
||||||
|
|
||||||
// Port of selected helpers from
|
// Port of selected helpers from
|
||||||
// src/ifcopenshell-python/ifcopenshell/util/geolocation.py — primarily
|
// src/ifcopenshell-python/ifcopenshell/util/geolocation.py.
|
||||||
// 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.
|
|
||||||
|
|
||||||
#ifndef GEOLOCATION_H
|
#ifndef GEOLOCATION_H
|
||||||
#define GEOLOCATION_H
|
#define GEOLOCATION_H
|
||||||
@@ -30,21 +26,22 @@
|
|||||||
#include "../ifcparse/express.h"
|
#include "../ifcparse/express.h"
|
||||||
|
|
||||||
#include <Eigen/Dense>
|
#include <Eigen/Dense>
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace ifcopenshell { class file; }
|
namespace ifcopenshell {
|
||||||
|
class file;
|
||||||
|
}
|
||||||
|
|
||||||
struct HelmertTransformation {
|
struct HelmertTransformation {
|
||||||
double e = 0.0; // eastings offset
|
double e = 0.0; // eastings offset
|
||||||
double n = 0.0; // northings offset
|
double n = 0.0; // northings offset
|
||||||
double h = 0.0; // orthogonal-height offset
|
double h = 0.0; // orthogonal-height offset
|
||||||
double xaa = 1.0; // X-axis abscissa (cos of grid-rotation angle)
|
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 xao = 0.0; // X-axis ordinate (sin of grid-rotation angle)
|
||||||
double scale = 1.0; // unit scale (project unit -> map unit)
|
double scale = 1.0; // unit scale (project unit -> map unit)
|
||||||
double factor_x = 1.0; // combined scale factor along X
|
double factor_x = 1.0; // combined scale factor along X
|
||||||
double factor_y = 1.0; // combined scale factor along Y
|
double factor_y = 1.0; // combined scale factor along Y
|
||||||
double factor_z = 1.0; // combined scale factor along Z
|
double factor_z = 1.0; // combined scale factor along Z
|
||||||
};
|
};
|
||||||
|
|
||||||
// Detect a Helmert transformation in the IFC model. Reads IfcMapConversion /
|
// Detect a Helmert transformation in the IFC model. Reads IfcMapConversion /
|
||||||
|
|||||||
+111
-86
@@ -19,9 +19,10 @@
|
|||||||
|
|
||||||
#include "placement.h"
|
#include "placement.h"
|
||||||
|
|
||||||
#include "../ifcparse/instance_data.h"
|
#include "../ifcparse/exception.h"
|
||||||
#include "../ifcparse/schema.h"
|
#include "schema_dispatch.i"
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -32,14 +33,99 @@ Eigen::Vector3d safe_normalize(const Eigen::Vector3d& v,
|
|||||||
return (n > 0.0) ? Eigen::Vector3d(v / n) : fallback;
|
return (n > 0.0) ? Eigen::Vector3d(v / n) : fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<double> read_direction_ratios(const express::Base& dir) {
|
template <typename Schema, typename = void>
|
||||||
if (!dir) return {};
|
struct has_axis2_placement_linear : std::false_type {};
|
||||||
auto attr = dir.as<express::Entity>().get("DirectionRatios");
|
|
||||||
if (attr.isNull()) return {};
|
template <typename Schema>
|
||||||
return attr;
|
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,
|
Eigen::Matrix4d axes_to_placement(const Eigen::Vector3d& origin,
|
||||||
const Eigen::Vector3d& z,
|
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) {
|
Eigen::Matrix4d get_axis2_placement(const express::Base& placement) {
|
||||||
if (!placement) return Eigen::Matrix4d::Identity();
|
if (!placement) {
|
||||||
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 {
|
|
||||||
return Eigen::Matrix4d::Identity();
|
return Eigen::Matrix4d::Identity();
|
||||||
}
|
}
|
||||||
|
const auto name = placement.declaration().schema()->name();
|
||||||
return axes_to_placement(o, z, x);
|
#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) {
|
Eigen::Matrix4d get_local_placement(const express::Base& placement) {
|
||||||
if (!placement) return Eigen::Matrix4d::Identity();
|
if (!placement) {
|
||||||
const auto& decl = placement.declaration();
|
return Eigen::Matrix4d::Identity();
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
const auto name = placement.declaration().schema()->name();
|
||||||
// IfcAxis2Placement* / IfcAxis1Placement passed in directly.
|
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
|
||||||
return get_axis2_placement(placement);
|
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 "unit.h"
|
||||||
|
|
||||||
|
#include "../ifcparse/exception.h"
|
||||||
#include "../ifcparse/file.h"
|
#include "../ifcparse/file.h"
|
||||||
#include "../ifcparse/instance_data.h"
|
#include "../ifcparse/instance_data.h"
|
||||||
#include "../ifcparse/schema.h"
|
#include "schema_dispatch.i"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
|
||||||
const std::unordered_map<std::string, double> SI_PREFIXES = {
|
const std::unordered_map<std::string, double> SI_PREFIXES = {
|
||||||
{ "EXA", 1e18 },
|
{"EXA", 1e18},
|
||||||
{ "PETA", 1e15 },
|
{"PETA", 1e15},
|
||||||
{ "TERA", 1e12 },
|
{"TERA", 1e12},
|
||||||
{ "GIGA", 1e9 },
|
{"GIGA", 1e9},
|
||||||
{ "MEGA", 1e6 },
|
{"MEGA", 1e6},
|
||||||
{ "KILO", 1e3 },
|
{"KILO", 1e3},
|
||||||
{ "HECTO", 1e2 },
|
{"HECTO", 1e2},
|
||||||
{ "DECA", 1e1 },
|
{"DECA", 1e1},
|
||||||
{ "DECI", 1e-1 },
|
{"DECI", 1e-1},
|
||||||
{ "CENTI", 1e-2 },
|
{"CENTI", 1e-2},
|
||||||
{ "MILLI", 1e-3 },
|
{"MILLI", 1e-3},
|
||||||
{ "MICRO", 1e-6 },
|
{"MICRO", 1e-6},
|
||||||
{ "NANO", 1e-9 },
|
{"NANO", 1e-9},
|
||||||
{ "PICO", 1e-12 },
|
{"PICO", 1e-12},
|
||||||
{ "FEMTO", 1e-15 },
|
{"FEMTO", 1e-15},
|
||||||
{ "ATTO", 1e-18 },
|
{"ATTO", 1e-18},
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::unordered_map<std::string, std::string> SI_PREFIX_SYMBOLS = {
|
const std::unordered_map<std::string, std::string> SI_PREFIX_SYMBOLS = {
|
||||||
{ "EXA", "E" },
|
{"EXA", "E"},
|
||||||
{ "PETA", "P" },
|
{"PETA", "P"},
|
||||||
{ "TERA", "T" },
|
{"TERA", "T"},
|
||||||
{ "GIGA", "G" },
|
{"GIGA", "G"},
|
||||||
{ "MEGA", "M" },
|
{"MEGA", "M"},
|
||||||
{ "KILO", "k" },
|
{"KILO", "k"},
|
||||||
{ "HECTO", "h" },
|
{"HECTO", "h"},
|
||||||
{ "DECA", "da" },
|
{"DECA", "da"},
|
||||||
{ "DECI", "d" },
|
{"DECI", "d"},
|
||||||
{ "CENTI", "c" },
|
{"CENTI", "c"},
|
||||||
{ "MILLI", "m" },
|
{"MILLI", "m"},
|
||||||
{ "MICRO", "\xCE\xBC" }, // μ (UTF-8)
|
{"MICRO", "\xCE\xBC"}, // μ (UTF-8)
|
||||||
{ "NANO", "n" },
|
{"NANO", "n"},
|
||||||
{ "PICO", "p" },
|
{"PICO", "p"},
|
||||||
{ "FEMTO", "f" },
|
{"FEMTO", "f"},
|
||||||
{ "ATTO", "a" },
|
{"ATTO", "a"},
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::unordered_map<std::string, double> SI_CONVERSIONS = {
|
const std::unordered_map<std::string, double> SI_CONVERSIONS = {
|
||||||
{ "thou", 0.0000254 },
|
{"thou", 0.0000254},
|
||||||
{ "inch", 0.0254 },
|
{"inch", 0.0254},
|
||||||
{ "foot", 0.3048 },
|
{"foot", 0.3048},
|
||||||
{ "yard", 0.914 },
|
{"yard", 0.914},
|
||||||
{ "mile", 1609.0 },
|
{"mile", 1609.0},
|
||||||
{ "square thou", 6.4516e-10 },
|
{"square thou", 6.4516e-10},
|
||||||
{ "square inch", 0.0006452 },
|
{"square inch", 0.0006452},
|
||||||
{ "square foot", 0.09290304 },
|
{"square foot", 0.09290304},
|
||||||
{ "square yard", 0.83612736 },
|
{"square yard", 0.83612736},
|
||||||
{ "acre", 4046.86 },
|
{"acre", 4046.86},
|
||||||
{ "square mile", 2588881.0 },
|
{"square mile", 2588881.0},
|
||||||
{ "cubic thou", 1.6387064e-14 },
|
{"cubic thou", 1.6387064e-14},
|
||||||
{ "cubic inch", 0.00001639 },
|
{"cubic inch", 0.00001639},
|
||||||
{ "cubic foot", 0.02831684671168849 },
|
{"cubic foot", 0.02831684671168849},
|
||||||
{ "cubic yard", 0.7636 },
|
{"cubic yard", 0.7636},
|
||||||
{ "cubic mile", 4165509529.0 },
|
{"cubic mile", 4165509529.0},
|
||||||
{ "litre", 0.001 },
|
{"litre", 0.001},
|
||||||
{ "fluid ounce uk", 0.0000284130625 },
|
{"fluid ounce uk", 0.0000284130625},
|
||||||
{ "fluid ounce us", 0.00002957353 },
|
{"fluid ounce us", 0.00002957353},
|
||||||
{ "pint uk", 0.000568 },
|
{"pint uk", 0.000568},
|
||||||
{ "pint us", 0.000473 },
|
{"pint us", 0.000473},
|
||||||
{ "gallon uk", 0.004546 },
|
{"gallon uk", 0.004546},
|
||||||
{ "gallon us", 0.003785 },
|
{"gallon us", 0.003785},
|
||||||
{ "degree", 0.0174532925199433 }, // pi / 180
|
{"degree", 0.0174532925199433}, // pi / 180
|
||||||
{ "ounce", 0.02835 },
|
{"ounce", 0.02835},
|
||||||
{ "pound", 0.454 },
|
{"pound", 0.454},
|
||||||
{ "ton uk", 1016.0469088 },
|
{"ton uk", 1016.0469088},
|
||||||
{ "ton us", 907.18474 },
|
{"ton us", 907.18474},
|
||||||
{ "tonne", 1000.0 },
|
{"tonne", 1000.0},
|
||||||
{ "lbf", 4.4482216153 },
|
{"lbf", 4.4482216153},
|
||||||
{ "kip", 4448.2216153 },
|
{"kip", 4448.2216153},
|
||||||
{ "psi", 6894.7572932 },
|
{"psi", 6894.7572932},
|
||||||
{ "ksi", 6894757.2932 },
|
{"ksi", 6894757.2932},
|
||||||
{ "minute", 60.0 },
|
{"minute", 60.0},
|
||||||
{ "hour", 3600.0 },
|
{"hour", 3600.0},
|
||||||
{ "day", 86400.0 },
|
{"day", 86400.0},
|
||||||
{ "btu", 1055.056 },
|
{"btu", 1055.056},
|
||||||
{ "fahrenheit", 1.8 },
|
{"fahrenheit", 1.8},
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::unordered_map<std::string, std::string> IMPERIAL_TYPES = {
|
const std::unordered_map<std::string, std::string> IMPERIAL_TYPES = {
|
||||||
{ "thou", "LENGTHUNIT" }, { "inch", "LENGTHUNIT" }, { "foot", "LENGTHUNIT" },
|
{"thou", "LENGTHUNIT"},
|
||||||
{ "yard", "LENGTHUNIT" }, { "mile", "LENGTHUNIT" },
|
{"inch", "LENGTHUNIT"},
|
||||||
{ "square thou", "AREAUNIT" }, { "square inch", "AREAUNIT" },
|
{"foot", "LENGTHUNIT"},
|
||||||
{ "square foot", "AREAUNIT" }, { "square yard", "AREAUNIT" },
|
{"yard", "LENGTHUNIT"},
|
||||||
{ "acre", "AREAUNIT" }, { "square mile", "AREAUNIT" },
|
{"mile", "LENGTHUNIT"},
|
||||||
{ "cubic thou", "VOLUMEUNIT" }, { "cubic inch", "VOLUMEUNIT" },
|
{"square thou", "AREAUNIT"},
|
||||||
{ "cubic foot", "VOLUMEUNIT" }, { "cubic yard", "VOLUMEUNIT" },
|
{"square inch", "AREAUNIT"},
|
||||||
{ "cubic mile", "VOLUMEUNIT" }, { "litre", "VOLUMEUNIT" },
|
{"square foot", "AREAUNIT"},
|
||||||
{ "fluid ounce uk", "VOLUMEUNIT" }, { "fluid ounce us", "VOLUMEUNIT" },
|
{"square yard", "AREAUNIT"},
|
||||||
{ "pint uk", "VOLUMEUNIT" }, { "pint us", "VOLUMEUNIT" },
|
{"acre", "AREAUNIT"},
|
||||||
{ "gallon uk", "VOLUMEUNIT" }, { "gallon us", "VOLUMEUNIT" },
|
{"square mile", "AREAUNIT"},
|
||||||
{ "degree", "PLANEANGLEUNIT" },
|
{"cubic thou", "VOLUMEUNIT"},
|
||||||
{ "ounce", "MASSUNIT" }, { "pound", "MASSUNIT" },
|
{"cubic inch", "VOLUMEUNIT"},
|
||||||
{ "ton uk", "MASSUNIT" }, { "ton us", "MASSUNIT" }, { "tonne", "MASSUNIT" },
|
{"cubic foot", "VOLUMEUNIT"},
|
||||||
{ "lbf", "FORCEUNIT" }, { "kip", "FORCEUNIT" },
|
{"cubic yard", "VOLUMEUNIT"},
|
||||||
{ "psi", "PRESSUREUNIT" }, { "ksi", "PRESSUREUNIT" },
|
{"cubic mile", "VOLUMEUNIT"},
|
||||||
{ "minute", "TIMEUNIT" }, { "hour", "TIMEUNIT" }, { "day", "TIMEUNIT" },
|
{"litre", "VOLUMEUNIT"},
|
||||||
{ "btu", "ENERGYUNIT" },
|
{"fluid ounce uk", "VOLUMEUNIT"},
|
||||||
{ "fahrenheit", "THERMODYNAMICTEMPERATUREUNIT" },
|
{"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 = {
|
const std::unordered_map<std::string, std::string> UNIT_SYMBOLS = {
|
||||||
// SI base / derived
|
// SI base / derived
|
||||||
{ "CUBIC_METRE", "m3" },
|
{"CUBIC_METRE", "m3"},
|
||||||
{ "GRAM", "g" },
|
{"GRAM", "g"},
|
||||||
{ "SECOND", "s" },
|
{"SECOND", "s"},
|
||||||
{ "SQUARE_METRE", "m2" },
|
{"SQUARE_METRE", "m2"},
|
||||||
{ "METRE", "m" },
|
{"METRE", "m"},
|
||||||
{ "NEWTON", "N" },
|
{"NEWTON", "N"},
|
||||||
{ "PASCAL", "Pa" },
|
{"PASCAL", "Pa"},
|
||||||
// Conversion-based
|
// Conversion-based
|
||||||
{ "pound-force", "lbf" },
|
{"pound-force", "lbf"},
|
||||||
{ "pound-force per square inch", "psi" },
|
{"pound-force per square inch", "psi"},
|
||||||
{ "thou", "th" }, { "inch", "in" }, { "foot", "ft" },
|
{"thou", "th"},
|
||||||
{ "yard", "yd" }, { "mile", "mi" },
|
{"inch", "in"},
|
||||||
{ "square thou", "th2" }, { "square inch", "in2" },
|
{"foot", "ft"},
|
||||||
{ "square foot", "ft2" }, { "square yard", "yd2" },
|
{"yard", "yd"},
|
||||||
{ "acre", "ac" }, { "square mile", "mi2" },
|
{"mile", "mi"},
|
||||||
{ "cubic thou", "th3" }, { "cubic inch", "in3" },
|
{"square thou", "th2"},
|
||||||
{ "cubic foot", "ft3" }, { "cubic yard", "yd3" },
|
{"square inch", "in2"},
|
||||||
{ "cubic mile", "mi3" }, { "litre", "L" },
|
{"square foot", "ft2"},
|
||||||
{ "fluid ounce uk", "fl oz" }, { "fluid ounce us", "fl oz" },
|
{"square yard", "yd2"},
|
||||||
{ "pint uk", "pt" }, { "pint us", "pt" },
|
{"acre", "ac"},
|
||||||
{ "gallon uk", "gal" }, { "gallon us", "gal" },
|
{"square mile", "mi2"},
|
||||||
{ "degree", "\xC2\xB0" }, // °
|
{"cubic thou", "th3"},
|
||||||
{ "ounce", "oz" }, { "pound", "lb" },
|
{"cubic inch", "in3"},
|
||||||
{ "ton uk", "ton" }, { "ton us", "ton" }, { "tonne", "t" },
|
{"cubic foot", "ft3"},
|
||||||
{ "lbf", "lbf" }, { "kip", "kip" },
|
{"cubic yard", "yd3"},
|
||||||
{ "psi", "psi" }, { "ksi", "ksi" },
|
{"cubic mile", "mi3"},
|
||||||
{ "minute", "min" }, { "hour", "hr" }, { "day", "day" },
|
{"litre", "L"},
|
||||||
{ "btu", "btu" },
|
{"fluid ounce uk", "fl oz"},
|
||||||
{ "fahrenheit", "\xC2\xB0\x46" }, // °F
|
{"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 {
|
namespace {
|
||||||
@@ -165,117 +204,204 @@ namespace {
|
|||||||
std::string to_lower(const std::string& s) {
|
std::string to_lower(const std::string& s) {
|
||||||
std::string r;
|
std::string r;
|
||||||
r.resize(s.size());
|
r.resize(s.size());
|
||||||
std::transform(s.begin(), s.end(), r.begin(),
|
std::transform(s.begin(), s.end(), r.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||||
[](unsigned char c) { return std::tolower(c); });
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull an enumeration string off an attribute_value, or "" if null/invalid.
|
[[noreturn]] void unsupported_schema(const std::string& name) {
|
||||||
std::string enum_string(const attribute_value& av) {
|
throw ifcopenshell::exception("No helper implementation was built for schema " + name);
|
||||||
if (av.isNull()) return {};
|
|
||||||
if (av.type() != ifcopenshell::Argument_ENUMERATION) return {};
|
|
||||||
enumeration_reference er = av;
|
|
||||||
return std::string(er.value() ? er.value() : "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // 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) {
|
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);
|
auto it = SI_PREFIXES.find(prefix);
|
||||||
return (it == SI_PREFIXES.end()) ? 1.0 : it->second;
|
return (it == SI_PREFIXES.end()) ? 1.0 : it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<double> si_scale_from_named_unit(express::Base unit) {
|
std::optional<double> si_scale_from_named_unit(express::Base unit) {
|
||||||
double scale = 1.0;
|
if (!unit) {
|
||||||
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.
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
const auto name = unit.declaration().schema()->name();
|
||||||
return scale;
|
#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) {
|
std::optional<express::Base> get_unit_assignment(ifcopenshell::file* ifc_file) {
|
||||||
auto projects = ifc_file->instances_by_type("IfcProject");
|
const auto name = ifc_file->schema()->name();
|
||||||
if (projects.empty()) return std::nullopt;
|
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
|
||||||
auto ua_attr = projects[0].as<express::Entity>().get("UnitsInContext");
|
if (name == Identifier) \
|
||||||
if (ua_attr.isNull()) return std::nullopt;
|
return get_unit_assignment_s<Schema>(ifc_file);
|
||||||
return (express::Base) ua_attr;
|
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
|
||||||
|
#undef IFCOPENSHELL_DISPATCH
|
||||||
|
unsupported_schema(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<express::Base> get_project_unit(ifcopenshell::file* ifc_file,
|
std::optional<express::Base> get_project_unit(ifcopenshell::file* ifc_file,
|
||||||
const std::string& unit_type) {
|
const std::string& unit_type) {
|
||||||
auto ua = get_unit_assignment(ifc_file);
|
const auto name = ifc_file->schema()->name();
|
||||||
if (!ua) return std::nullopt;
|
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
|
||||||
auto units_attr = ua->as<express::Entity>().get("Units");
|
if (name == Identifier) \
|
||||||
if (units_attr.isNull()) return std::nullopt;
|
return get_project_unit_s<Schema>(ifc_file, unit_type);
|
||||||
std::vector<express::Base> units = units_attr;
|
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
|
||||||
for (const auto& unit : units) {
|
#undef IFCOPENSHELL_DISPATCH
|
||||||
// IfcMonetaryUnit has no UnitType — guard via declaration check.
|
unsupported_schema(name);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double calculate_unit_scale(ifcopenshell::file* ifc_file,
|
double calculate_unit_scale(ifcopenshell::file* ifc_file,
|
||||||
const std::string& unit_type) {
|
const std::string& unit_type) {
|
||||||
auto unit = get_project_unit(ifc_file, unit_type);
|
const auto name = ifc_file->schema()->name();
|
||||||
if (!unit) return 1.0;
|
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
|
||||||
auto scale = si_scale_from_named_unit(*unit);
|
if (name == Identifier) \
|
||||||
return scale.value_or(1.0);
|
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,
|
double convert(double value,
|
||||||
const std::string& from_prefix, const std::string& from_unit,
|
const std::string& from_prefix,
|
||||||
const std::string& to_prefix, const std::string& to_unit) {
|
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 fl = to_lower(from_unit);
|
||||||
const std::string tl = to_lower(to_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) {
|
double convert_unit(double value, express::Base from_unit, express::Base to_unit) {
|
||||||
auto pull = [](express::Base u, std::string& prefix, std::string& name) {
|
if (!from_unit || !to_unit) {
|
||||||
auto e = u.as<express::Entity>();
|
return value;
|
||||||
if (u.declaration().is("IfcSIUnit")) {
|
}
|
||||||
prefix = enum_string(e.get("Prefix"));
|
const auto name = from_unit.declaration().schema()->name();
|
||||||
name = enum_string(e.get("Name"));
|
if (name != to_unit.declaration().schema()->name()) {
|
||||||
} else {
|
throw ifcopenshell::exception("Cannot convert units from different IFC schemas");
|
||||||
// IfcConversionBasedUnit / IfcContextDependentUnit: no Prefix,
|
}
|
||||||
// Name is a string attribute.
|
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
|
||||||
prefix.clear();
|
if (name == Identifier) \
|
||||||
auto name_attr = e.get("Name");
|
return convert_unit_s<Schema>(value, from_unit, to_unit);
|
||||||
name = name_attr.isNull() ? "" : (std::string) name_attr;
|
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
|
||||||
}
|
#undef IFCOPENSHELL_DISPATCH
|
||||||
};
|
unsupported_schema(name);
|
||||||
std::string fp, fn, tp, tn;
|
|
||||||
pull(from_unit, fp, fn);
|
|
||||||
pull(to_unit, tp, tn);
|
|
||||||
return convert(value, fp, fn, tp, tn);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-7
@@ -18,9 +18,7 @@
|
|||||||
********************************************************************************/
|
********************************************************************************/
|
||||||
|
|
||||||
// Port of selected helpers from
|
// Port of selected helpers from
|
||||||
// src/ifcopenshell-python/ifcopenshell/util/unit.py. Lives in src/ifcviewer/
|
// src/ifcopenshell-python/ifcopenshell/util/unit.py.
|
||||||
// for now alongside Geolocation; will move out once ifcopenshell.util is
|
|
||||||
// ported to C++.
|
|
||||||
|
|
||||||
#ifndef UNIT_H
|
#ifndef UNIT_H
|
||||||
#define UNIT_H
|
#define UNIT_H
|
||||||
@@ -31,7 +29,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace ifcopenshell { class file; }
|
namespace ifcopenshell {
|
||||||
|
class file;
|
||||||
|
}
|
||||||
|
|
||||||
// SI prefix multipliers, e.g. "MILLI" -> 1e-3. Empty key not present;
|
// SI prefix multipliers, e.g. "MILLI" -> 1e-3. Empty key not present;
|
||||||
// callers should pass an empty prefix string for "no prefix".
|
// 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`
|
// First unit in the project's IfcUnitAssignment matching `unit_type`
|
||||||
// (e.g. "LENGTHUNIT"). Returns nullopt if not found.
|
// (e.g. "LENGTHUNIT"). Returns nullopt if not found.
|
||||||
std::optional<express::Base> get_project_unit(ifcopenshell::file* ifc_file,
|
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
|
// 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.
|
// 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
|
// SQUARE_/CUBIC_ prefixed SI names get the prefix multiplier squared/cubed
|
||||||
// (matches python ifcopenshell.util.unit.convert).
|
// (matches python ifcopenshell.util.unit.convert).
|
||||||
double convert(double value,
|
double convert(double value,
|
||||||
const std::string& from_prefix, const std::string& from_unit,
|
const std::string& from_prefix,
|
||||||
const std::string& to_prefix, const std::string& to_unit);
|
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
|
// Convert between two IfcNamedUnit entities. Pulls Name and Prefix off each
|
||||||
// and delegates to convert(). IfcConversionBasedUnit names that don't appear
|
// and delegates to convert(). IfcConversionBasedUnit names that don't appear
|
||||||
|
|||||||
Reference in New Issue
Block a user