mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-18 11:20:21 +00:00
Split the schema-free half of the unit and geolocation helpers out
unit.h and geolocation.h both include ../ifcparse/express.h for the entity walking they do, which puts the whole module out of reach of anything that cannot link IfcParse. Most of what a viewer wants from them needs no IFC at all: the unit conversion tables, and the Helmert parameters-to-matrix math. Move those into unit_convert and geolocation_transform, and build them as a new helpers_math target that `helpers` re-exports PUBLIC, so existing callers keep working through the unchanged unit.h / geolocation.h includes. The new target has no IfcParse or Qt dependency and so builds under Emscripten, where the rest of this directory cannot. One target rather than compiling the sources into each consumer: the glob in this directory would otherwise put them in libhelpers.a as well, leaving two copies of the same objects in any link that pulls both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,8 +36,34 @@ message("Running CMakeLists.txt in /src/helpers")
|
||||
|
||||
find_package(Eigen3 REQUIRED)
|
||||
|
||||
# helpers_math: the schema-free subset — unit conversion tables and Helmert
|
||||
# matrix math over plain numbers. No IfcParse, no Qt, no schema dispatch, so it
|
||||
# builds under Emscripten where the rest of this directory cannot.
|
||||
#
|
||||
# It exists as its own target rather than being compiled into both `helpers` and
|
||||
# IfcViewerCore so the objects are built exactly once and both libraries agree
|
||||
# on them. IfcViewerCore links it directly (see src/ifcviewer/CMakeLists.txt);
|
||||
# `helpers` re-exports it PUBLIC below so existing consumers keep working
|
||||
# through the unchanged unit.h / geolocation.h includes.
|
||||
set(HELPER_MATH_CPP_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/unit_convert.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/geolocation_transform.cpp
|
||||
)
|
||||
add_library(helpers_math STATIC ${HELPER_MATH_CPP_FILES})
|
||||
target_include_directories(helpers_math PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(helpers_math PUBLIC Eigen3::Eigen)
|
||||
set_target_properties(helpers_math PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
install(TARGETS helpers_math EXPORT ${IFCOPENSHELL_EXPORT_TARGETS})
|
||||
|
||||
# The rest of this directory needs IfcParse and the generated schema bindings,
|
||||
# so it is desktop-only.
|
||||
if(EMSCRIPTEN)
|
||||
return()
|
||||
endif()
|
||||
|
||||
file(GLOB HELPER_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
|
||||
file(GLOB HELPER_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
|
||||
list(REMOVE_ITEM HELPER_CPP_FILES ${HELPER_MATH_CPP_FILES})
|
||||
|
||||
add_library(helpers STATIC ${HELPER_CPP_FILES} ${HELPER_H_FILES})
|
||||
|
||||
@@ -64,6 +90,7 @@ target_include_directories(helpers PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(helpers PUBLIC
|
||||
IfcParse
|
||||
Eigen3::Eigen
|
||||
helpers_math
|
||||
PRIVATE
|
||||
${HELPER_SCHEMA_LIBRARIES}
|
||||
)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
********************************************************************************/
|
||||
|
||||
#include "geolocation.h"
|
||||
#include "geolocation_transform.h"
|
||||
|
||||
#include "../ifcparse/exception.h"
|
||||
#include "../ifcparse/file.h"
|
||||
@@ -247,40 +248,6 @@ std::optional<Eigen::Matrix4d> get_wcs(ifcopenshell::file* ifc_file) {
|
||||
unsupported_schema(name);
|
||||
}
|
||||
|
||||
Eigen::Matrix4d local_to_global(const Eigen::Matrix4d& matrix,
|
||||
const HelmertTransformation& p) {
|
||||
const double theta = std::atan2(p.xao, p.xaa);
|
||||
const double c = std::cos(theta);
|
||||
const double s = std::sin(theta);
|
||||
|
||||
Eigen::Matrix4d S = Eigen::Matrix4d::Identity();
|
||||
S(0, 0) = p.scale * p.factor_x;
|
||||
S(1, 1) = p.scale * p.factor_y;
|
||||
S(2, 2) = p.scale * p.factor_z;
|
||||
|
||||
Eigen::Matrix4d R = Eigen::Matrix4d::Identity();
|
||||
R(0, 0) = c;
|
||||
R(0, 1) = -s;
|
||||
R(1, 0) = s;
|
||||
R(1, 1) = c;
|
||||
|
||||
Eigen::Matrix4d result = R * S * matrix;
|
||||
// The scale was baked into the rotation+scale matrix so each axis column
|
||||
// ended up scaled. Renormalise so the rotation part is pure orientation
|
||||
// and the translation alone carries the scaled offsets.
|
||||
for (int col = 0; col < 3; ++col) {
|
||||
Eigen::Vector3d v = result.block<3, 1>(0, col);
|
||||
const double n = v.norm();
|
||||
if (n > 0.0) {
|
||||
result.block<3, 1>(0, col) = v / n;
|
||||
}
|
||||
}
|
||||
result(0, 3) += p.e;
|
||||
result(1, 3) += p.n;
|
||||
result(2, 3) += p.h;
|
||||
return result;
|
||||
}
|
||||
|
||||
Eigen::Matrix4d auto_local_to_global(ifcopenshell::file* ifc_file,
|
||||
const Eigen::Matrix4d& matrix,
|
||||
bool should_return_in_map_units) {
|
||||
@@ -302,32 +269,6 @@ Eigen::Matrix4d auto_local_to_global(ifcopenshell::file* ifc_file,
|
||||
return result;
|
||||
}
|
||||
|
||||
Eigen::Matrix4d helmert_meters_from_parameters(const HelmertTransformation& p,
|
||||
double map_unit_to_meters) {
|
||||
const double theta = std::atan2(p.xao, p.xaa);
|
||||
const double c = std::cos(theta);
|
||||
const double s = std::sin(theta);
|
||||
|
||||
Eigen::Matrix4d M = Eigen::Matrix4d::Identity();
|
||||
// R_z(theta) · diag(fx, fy, fz). Factors stay in the rotation block so
|
||||
// they apply to placement translations on compose; this is the behaviour
|
||||
// IfcMapConversionScaled actually wants ("grid distance ≠ ground
|
||||
// distance" — buildings on the grid should appear scaled by f).
|
||||
M(0, 0) = c * p.factor_x;
|
||||
M(0, 1) = -s * p.factor_y;
|
||||
M(0, 2) = 0.0;
|
||||
M(1, 0) = s * p.factor_x;
|
||||
M(1, 1) = c * p.factor_y;
|
||||
M(1, 2) = 0.0;
|
||||
M(2, 0) = 0.0;
|
||||
M(2, 1) = 0.0;
|
||||
M(2, 2) = p.factor_z;
|
||||
M(0, 3) = p.e * map_unit_to_meters;
|
||||
M(1, 3) = p.n * map_unit_to_meters;
|
||||
M(2, 3) = p.h * map_unit_to_meters;
|
||||
return M;
|
||||
}
|
||||
|
||||
std::optional<express::base> get_map_unit(ifcopenshell::file* ifc_file) {
|
||||
const auto name = ifc_file->schema()->name();
|
||||
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
|
||||
@@ -337,8 +278,3 @@ std::optional<express::base> get_map_unit(ifcopenshell::file* ifc_file) {
|
||||
#undef IFCOPENSHELL_DISPATCH
|
||||
unsupported_schema(name);
|
||||
}
|
||||
|
||||
double x_axis_to_angle_deg(double xaa, double xao) {
|
||||
constexpr double PI = 3.14159265358979323846;
|
||||
return -std::atan2(xao, xaa) * (180.0 / PI);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#define GEOLOCATION_H
|
||||
|
||||
#include "../ifcparse/express.h"
|
||||
#include "geolocation_transform.h"
|
||||
|
||||
#include <Eigen/Dense>
|
||||
#include <optional>
|
||||
@@ -32,18 +33,6 @@ namespace ifcopenshell {
|
||||
class file;
|
||||
}
|
||||
|
||||
struct HelmertTransformation {
|
||||
double e = 0.0; // eastings offset
|
||||
double n = 0.0; // northings offset
|
||||
double h = 0.0; // orthogonal-height offset
|
||||
double xaa = 1.0; // X-axis abscissa (cos of grid-rotation angle)
|
||||
double xao = 0.0; // X-axis ordinate (sin of grid-rotation angle)
|
||||
double scale = 1.0; // unit scale (project unit -> map unit)
|
||||
double factor_x = 1.0; // combined scale factor along X
|
||||
double factor_y = 1.0; // combined scale factor along Y
|
||||
double factor_z = 1.0; // combined scale factor along Z
|
||||
};
|
||||
|
||||
// Detect a Helmert transformation in the IFC model. Reads IfcMapConversion /
|
||||
// IfcMapConversionScaled / IfcRigidOperation in IFC4+, or the
|
||||
// IfcProject.ePSet_MapConversion property set in IFC2X3. Returns nullopt
|
||||
@@ -56,10 +45,6 @@ get_helmert_transformation_parameters(ifcopenshell::file* ifc_file);
|
||||
// no parseable WCS.
|
||||
std::optional<Eigen::Matrix4d> get_wcs(ifcopenshell::file* ifc_file);
|
||||
|
||||
// Apply a Helmert transformation to a 4x4 local matrix.
|
||||
Eigen::Matrix4d local_to_global(const Eigen::Matrix4d& matrix,
|
||||
const HelmertTransformation& params);
|
||||
|
||||
// Lift a 4x4 local matrix into global (map) coordinates using the IFC model's
|
||||
// georeferencing data. When no map conversion is present the matrix is
|
||||
// returned unchanged. When should_return_in_map_units is false, the
|
||||
@@ -69,30 +54,6 @@ Eigen::Matrix4d auto_local_to_global(ifcopenshell::file* ifc_file,
|
||||
const Eigen::Matrix4d& matrix,
|
||||
bool should_return_in_map_units = true);
|
||||
|
||||
// Build the Helmert transformation as a meter-input / meter-output 4x4 matrix
|
||||
// directly from parsed parameters, bypassing auto_local_to_global's normalisation
|
||||
// step. Used by callers that want a single per-model georef matrix to compose
|
||||
// with placement matrices at upload time.
|
||||
//
|
||||
// Result has shape:
|
||||
// [ R_z(theta) · diag(fx, fy, fz) | (e, n, h) · u_m ]
|
||||
// [ 0 | 1 ]
|
||||
//
|
||||
// `map_unit_to_meters` is derived by the caller from the IFC project length
|
||||
// unit and the authoritative IfcMapConversion.Scale. Since this matrix takes
|
||||
// meter inputs from the geometry iterator, Scale is represented by that unit
|
||||
// conversion and is not applied again in the linear block. The caller composes
|
||||
// any IfcGeometricRepresentationContext WCS on the right:
|
||||
// G = helmert_meters_from_parameters(...) · inv(wcs_meters)
|
||||
// (where wcs_meters has its translation column converted from project units
|
||||
// to meters via calculate_unit_scale).
|
||||
//
|
||||
// Unlike auto_local_to_global, this preserves IfcMapConversionScaled.FactorX/Y/Z
|
||||
// in the rotation block, so they apply correctly to placement translations
|
||||
// when composing per-model.
|
||||
Eigen::Matrix4d helmert_meters_from_parameters(const HelmertTransformation& params,
|
||||
double map_unit_to_meters);
|
||||
|
||||
// IfcCoordinateOperation.TargetCRS.MapUnit (the IfcNamedUnit), if present.
|
||||
// Returns nullopt for IFC2X3, models without an IfcCoordinateOperation, or
|
||||
// when MapUnit is absent on the IfcProjectedCRS. This is retained for UI /
|
||||
@@ -100,9 +61,4 @@ Eigen::Matrix4d helmert_meters_from_parameters(const HelmertTransformation& para
|
||||
// IfcMapConversion.Scale instead.
|
||||
std::optional<express::base> get_map_unit(ifcopenshell::file* ifc_file);
|
||||
|
||||
// "How do I rotate project east to get to grid east?" — i.e. -atan2(xao, xaa)
|
||||
// converted to degrees, anticlockwise positive. Mirrors
|
||||
// ifcopenshell.util.geolocation.xaxis2angle.
|
||||
double x_axis_to_angle_deg(double xaa, double xao);
|
||||
|
||||
#endif // GEOLOCATION_H
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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 *
|
||||
* Lesser GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#include "geolocation_transform.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
Eigen::Matrix4d local_to_global(const Eigen::Matrix4d& matrix,
|
||||
const HelmertTransformation& p) {
|
||||
const double theta = std::atan2(p.xao, p.xaa);
|
||||
const double c = std::cos(theta);
|
||||
const double s = std::sin(theta);
|
||||
|
||||
Eigen::Matrix4d S = Eigen::Matrix4d::Identity();
|
||||
S(0, 0) = p.scale * p.factor_x;
|
||||
S(1, 1) = p.scale * p.factor_y;
|
||||
S(2, 2) = p.scale * p.factor_z;
|
||||
|
||||
Eigen::Matrix4d R = Eigen::Matrix4d::Identity();
|
||||
R(0, 0) = c;
|
||||
R(0, 1) = -s;
|
||||
R(1, 0) = s;
|
||||
R(1, 1) = c;
|
||||
|
||||
Eigen::Matrix4d result = R * S * matrix;
|
||||
// The scale was baked into the rotation+scale matrix so each axis column
|
||||
// ended up scaled. Renormalise so the rotation part is pure orientation
|
||||
// and the translation alone carries the scaled offsets.
|
||||
for (int col = 0; col < 3; ++col) {
|
||||
Eigen::Vector3d v = result.block<3, 1>(0, col);
|
||||
const double n = v.norm();
|
||||
if (n > 0.0) {
|
||||
result.block<3, 1>(0, col) = v / n;
|
||||
}
|
||||
}
|
||||
result(0, 3) += p.e;
|
||||
result(1, 3) += p.n;
|
||||
result(2, 3) += p.h;
|
||||
return result;
|
||||
}
|
||||
|
||||
Eigen::Matrix4d helmert_meters_from_parameters(const HelmertTransformation& p,
|
||||
double map_unit_to_meters) {
|
||||
const double theta = std::atan2(p.xao, p.xaa);
|
||||
const double c = std::cos(theta);
|
||||
const double s = std::sin(theta);
|
||||
|
||||
Eigen::Matrix4d M = Eigen::Matrix4d::Identity();
|
||||
// R_z(theta) · diag(fx, fy, fz). Factors stay in the rotation block so
|
||||
// they apply to placement translations on compose; this is the behaviour
|
||||
// IfcMapConversionScaled actually wants ("grid distance ≠ ground
|
||||
// distance" — buildings on the grid should appear scaled by f).
|
||||
M(0, 0) = c * p.factor_x;
|
||||
M(0, 1) = -s * p.factor_y;
|
||||
M(0, 2) = 0.0;
|
||||
M(1, 0) = s * p.factor_x;
|
||||
M(1, 1) = c * p.factor_y;
|
||||
M(1, 2) = 0.0;
|
||||
M(2, 0) = 0.0;
|
||||
M(2, 1) = 0.0;
|
||||
M(2, 2) = p.factor_z;
|
||||
M(0, 3) = p.e * map_unit_to_meters;
|
||||
M(1, 3) = p.n * map_unit_to_meters;
|
||||
M(2, 3) = p.h * map_unit_to_meters;
|
||||
return M;
|
||||
}
|
||||
|
||||
double x_axis_to_angle_deg(double xaa, double xao) {
|
||||
constexpr double PI = 3.14159265358979323846;
|
||||
return -std::atan2(xao, xaa) * (180.0 / PI);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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 *
|
||||
* Lesser GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
// Georeferencing matrix math over plain numbers: no IFC file, no schema
|
||||
// dispatch, no Qt.
|
||||
//
|
||||
// Split out of geolocation.h/geolocation.cpp for the same reason as
|
||||
// unit_convert: those pull in ../ifcparse/express.h to read an
|
||||
// IfcMapConversion out of a model, but IfcViewerCore — and through it the
|
||||
// Emscripten build — needs to compose the resulting matrices without
|
||||
// linking IfcParse. Reading the parameters out of an IFC stays in
|
||||
// geolocation.h; turning them into matrices lives here.
|
||||
|
||||
#ifndef GEOLOCATION_TRANSFORM_H
|
||||
#define GEOLOCATION_TRANSFORM_H
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
struct HelmertTransformation {
|
||||
double e = 0.0; // eastings offset
|
||||
double n = 0.0; // northings offset
|
||||
double h = 0.0; // orthogonal-height offset
|
||||
double xaa = 1.0; // X-axis abscissa (cos of grid-rotation angle)
|
||||
double xao = 0.0; // X-axis ordinate (sin of grid-rotation angle)
|
||||
double scale = 1.0; // unit scale (project unit -> map unit)
|
||||
double factor_x = 1.0; // combined scale factor along X
|
||||
double factor_y = 1.0; // combined scale factor along Y
|
||||
double factor_z = 1.0; // combined scale factor along Z
|
||||
};
|
||||
|
||||
// Apply a Helmert transformation to a 4x4 local matrix.
|
||||
Eigen::Matrix4d local_to_global(const Eigen::Matrix4d& matrix,
|
||||
const HelmertTransformation& params);
|
||||
|
||||
// Build the Helmert transformation as a meter-input / meter-output 4x4 matrix
|
||||
// directly from parsed parameters, bypassing auto_local_to_global's normalisation
|
||||
// step. Used by callers that want a single per-model georef matrix to compose
|
||||
// with placement matrices at upload time.
|
||||
//
|
||||
// Result has shape:
|
||||
// [ R_z(theta) · diag(fx, fy, fz) | (e, n, h) · u_m ]
|
||||
// [ 0 | 1 ]
|
||||
//
|
||||
// `map_unit_to_meters` is derived by the caller from the IFC project length
|
||||
// unit and the authoritative IfcMapConversion.Scale. Since this matrix takes
|
||||
// meter inputs from the geometry iterator, Scale is represented by that unit
|
||||
// conversion and is not applied again in the linear block. The caller composes
|
||||
// any IfcGeometricRepresentationContext WCS on the right:
|
||||
// G = helmert_meters_from_parameters(...) · inv(wcs_meters)
|
||||
// (where wcs_meters has its translation column converted from project units
|
||||
// to meters via calculate_unit_scale).
|
||||
//
|
||||
// Unlike auto_local_to_global, this preserves IfcMapConversionScaled.FactorX/Y/Z
|
||||
// in the rotation block, so they apply correctly to placement translations
|
||||
// when composing per-model.
|
||||
Eigen::Matrix4d helmert_meters_from_parameters(const HelmertTransformation& params,
|
||||
double map_unit_to_meters);
|
||||
|
||||
// "How do I rotate project east to get to grid east?" — i.e. -atan2(xao, xaa)
|
||||
// converted to degrees, anticlockwise positive. Mirrors
|
||||
// ifcopenshell.util.geolocation.xaxis2angle.
|
||||
double x_axis_to_angle_deg(double xaa, double xao);
|
||||
|
||||
#endif // GEOLOCATION_TRANSFORM_H
|
||||
+1
-223
@@ -18,6 +18,7 @@
|
||||
********************************************************************************/
|
||||
|
||||
#include "unit.h"
|
||||
#include "unit_convert.h"
|
||||
|
||||
#include "../ifcparse/exception.h"
|
||||
#include "../ifcparse/file.h"
|
||||
@@ -27,187 +28,8 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
const std::unordered_map<std::string, double> SI_PREFIXES = {
|
||||
{"EXA", 1e18},
|
||||
{"PETA", 1e15},
|
||||
{"TERA", 1e12},
|
||||
{"GIGA", 1e9},
|
||||
{"MEGA", 1e6},
|
||||
{"KILO", 1e3},
|
||||
{"HECTO", 1e2},
|
||||
{"DECA", 1e1},
|
||||
{"DECI", 1e-1},
|
||||
{"CENTI", 1e-2},
|
||||
{"MILLI", 1e-3},
|
||||
{"MICRO", 1e-6},
|
||||
{"NANO", 1e-9},
|
||||
{"PICO", 1e-12},
|
||||
{"FEMTO", 1e-15},
|
||||
{"ATTO", 1e-18},
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, std::string> SI_PREFIX_SYMBOLS = {
|
||||
{"EXA", "E"},
|
||||
{"PETA", "P"},
|
||||
{"TERA", "T"},
|
||||
{"GIGA", "G"},
|
||||
{"MEGA", "M"},
|
||||
{"KILO", "k"},
|
||||
{"HECTO", "h"},
|
||||
{"DECA", "da"},
|
||||
{"DECI", "d"},
|
||||
{"CENTI", "c"},
|
||||
{"MILLI", "m"},
|
||||
{"MICRO", "\xCE\xBC"}, // μ (UTF-8)
|
||||
{"NANO", "n"},
|
||||
{"PICO", "p"},
|
||||
{"FEMTO", "f"},
|
||||
{"ATTO", "a"},
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, double> SI_CONVERSIONS = {
|
||||
{"thou", 0.0000254},
|
||||
{"inch", 0.0254},
|
||||
{"foot", 0.3048},
|
||||
{"yard", 0.914},
|
||||
{"mile", 1609.0},
|
||||
{"square thou", 6.4516e-10},
|
||||
{"square inch", 0.0006452},
|
||||
{"square foot", 0.09290304},
|
||||
{"square yard", 0.83612736},
|
||||
{"acre", 4046.86},
|
||||
{"square mile", 2588881.0},
|
||||
{"cubic thou", 1.6387064e-14},
|
||||
{"cubic inch", 0.00001639},
|
||||
{"cubic foot", 0.02831684671168849},
|
||||
{"cubic yard", 0.7636},
|
||||
{"cubic mile", 4165509529.0},
|
||||
{"litre", 0.001},
|
||||
{"fluid ounce uk", 0.0000284130625},
|
||||
{"fluid ounce us", 0.00002957353},
|
||||
{"pint uk", 0.000568},
|
||||
{"pint us", 0.000473},
|
||||
{"gallon uk", 0.004546},
|
||||
{"gallon us", 0.003785},
|
||||
{"degree", 0.0174532925199433}, // pi / 180
|
||||
{"ounce", 0.02835},
|
||||
{"pound", 0.454},
|
||||
{"ton uk", 1016.0469088},
|
||||
{"ton us", 907.18474},
|
||||
{"tonne", 1000.0},
|
||||
{"lbf", 4.4482216153},
|
||||
{"kip", 4448.2216153},
|
||||
{"psi", 6894.7572932},
|
||||
{"ksi", 6894757.2932},
|
||||
{"minute", 60.0},
|
||||
{"hour", 3600.0},
|
||||
{"day", 86400.0},
|
||||
{"btu", 1055.056},
|
||||
{"fahrenheit", 1.8},
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, std::string> IMPERIAL_TYPES = {
|
||||
{"thou", "LENGTHUNIT"},
|
||||
{"inch", "LENGTHUNIT"},
|
||||
{"foot", "LENGTHUNIT"},
|
||||
{"yard", "LENGTHUNIT"},
|
||||
{"mile", "LENGTHUNIT"},
|
||||
{"square thou", "AREAUNIT"},
|
||||
{"square inch", "AREAUNIT"},
|
||||
{"square foot", "AREAUNIT"},
|
||||
{"square yard", "AREAUNIT"},
|
||||
{"acre", "AREAUNIT"},
|
||||
{"square mile", "AREAUNIT"},
|
||||
{"cubic thou", "VOLUMEUNIT"},
|
||||
{"cubic inch", "VOLUMEUNIT"},
|
||||
{"cubic foot", "VOLUMEUNIT"},
|
||||
{"cubic yard", "VOLUMEUNIT"},
|
||||
{"cubic mile", "VOLUMEUNIT"},
|
||||
{"litre", "VOLUMEUNIT"},
|
||||
{"fluid ounce uk", "VOLUMEUNIT"},
|
||||
{"fluid ounce us", "VOLUMEUNIT"},
|
||||
{"pint uk", "VOLUMEUNIT"},
|
||||
{"pint us", "VOLUMEUNIT"},
|
||||
{"gallon uk", "VOLUMEUNIT"},
|
||||
{"gallon us", "VOLUMEUNIT"},
|
||||
{"degree", "PLANEANGLEUNIT"},
|
||||
{"ounce", "MASSUNIT"},
|
||||
{"pound", "MASSUNIT"},
|
||||
{"ton uk", "MASSUNIT"},
|
||||
{"ton us", "MASSUNIT"},
|
||||
{"tonne", "MASSUNIT"},
|
||||
{"lbf", "FORCEUNIT"},
|
||||
{"kip", "FORCEUNIT"},
|
||||
{"psi", "PRESSUREUNIT"},
|
||||
{"ksi", "PRESSUREUNIT"},
|
||||
{"minute", "TIMEUNIT"},
|
||||
{"hour", "TIMEUNIT"},
|
||||
{"day", "TIMEUNIT"},
|
||||
{"btu", "ENERGYUNIT"},
|
||||
{"fahrenheit", "THERMODYNAMICTEMPERATUREUNIT"},
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, std::string> UNIT_SYMBOLS = {
|
||||
// SI base / derived
|
||||
{"CUBIC_METRE", "m3"},
|
||||
{"GRAM", "g"},
|
||||
{"SECOND", "s"},
|
||||
{"SQUARE_METRE", "m2"},
|
||||
{"METRE", "m"},
|
||||
{"NEWTON", "N"},
|
||||
{"PASCAL", "Pa"},
|
||||
// Conversion-based
|
||||
{"pound-force", "lbf"},
|
||||
{"pound-force per square inch", "psi"},
|
||||
{"thou", "th"},
|
||||
{"inch", "in"},
|
||||
{"foot", "ft"},
|
||||
{"yard", "yd"},
|
||||
{"mile", "mi"},
|
||||
{"square thou", "th2"},
|
||||
{"square inch", "in2"},
|
||||
{"square foot", "ft2"},
|
||||
{"square yard", "yd2"},
|
||||
{"acre", "ac"},
|
||||
{"square mile", "mi2"},
|
||||
{"cubic thou", "th3"},
|
||||
{"cubic inch", "in3"},
|
||||
{"cubic foot", "ft3"},
|
||||
{"cubic yard", "yd3"},
|
||||
{"cubic mile", "mi3"},
|
||||
{"litre", "L"},
|
||||
{"fluid ounce uk", "fl oz"},
|
||||
{"fluid ounce us", "fl oz"},
|
||||
{"pint uk", "pt"},
|
||||
{"pint us", "pt"},
|
||||
{"gallon uk", "gal"},
|
||||
{"gallon us", "gal"},
|
||||
{"degree", "\xC2\xB0"}, // °
|
||||
{"ounce", "oz"},
|
||||
{"pound", "lb"},
|
||||
{"ton uk", "ton"},
|
||||
{"ton us", "ton"},
|
||||
{"tonne", "t"},
|
||||
{"lbf", "lbf"},
|
||||
{"kip", "kip"},
|
||||
{"psi", "psi"},
|
||||
{"ksi", "ksi"},
|
||||
{"minute", "min"},
|
||||
{"hour", "hr"},
|
||||
{"day", "day"},
|
||||
{"btu", "btu"},
|
||||
{"fahrenheit", "\xC2\xB0\x46"}, // °F
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
std::string to_lower(const std::string& s) {
|
||||
std::string r;
|
||||
r.resize(s.size());
|
||||
std::transform(s.begin(), s.end(), r.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
return r;
|
||||
}
|
||||
|
||||
[[noreturn]] void unsupported_schema(const std::string& name) {
|
||||
throw ifcopenshell::exception("No helper implementation was built for schema " + name);
|
||||
}
|
||||
@@ -344,14 +166,6 @@ double convert_unit_s(double value, const express::base& from_unit, const expres
|
||||
|
||||
} // namespace
|
||||
|
||||
double get_prefix_multiplier(const std::string& prefix) {
|
||||
if (prefix.empty()) {
|
||||
return 1.0;
|
||||
}
|
||||
auto it = SI_PREFIXES.find(prefix);
|
||||
return (it == SI_PREFIXES.end()) ? 1.0 : it->second;
|
||||
}
|
||||
|
||||
std::optional<double> si_scale_from_named_unit(express::base unit) {
|
||||
if (!unit) {
|
||||
return std::nullopt;
|
||||
@@ -397,42 +211,6 @@ double calculate_unit_scale(ifcopenshell::file* ifc_file,
|
||||
unsupported_schema(name);
|
||||
}
|
||||
|
||||
double convert(double value,
|
||||
const std::string& from_prefix,
|
||||
const std::string& from_unit,
|
||||
const std::string& to_prefix,
|
||||
const std::string& to_unit) {
|
||||
const std::string fl = to_lower(from_unit);
|
||||
const std::string tl = to_lower(to_unit);
|
||||
|
||||
if (auto it = SI_CONVERSIONS.find(fl); it != SI_CONVERSIONS.end()) {
|
||||
value *= it->second;
|
||||
} else if (!from_prefix.empty()) {
|
||||
value *= get_prefix_multiplier(from_prefix);
|
||||
if (from_unit.find("SQUARE") != std::string::npos) {
|
||||
value *= get_prefix_multiplier(from_prefix);
|
||||
} else if (from_unit.find("CUBIC") != std::string::npos) {
|
||||
value *= get_prefix_multiplier(from_prefix);
|
||||
value *= get_prefix_multiplier(from_prefix);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto it = SI_CONVERSIONS.find(tl); it != SI_CONVERSIONS.end()) {
|
||||
return value * (1.0 / it->second);
|
||||
} else if (!to_prefix.empty()) {
|
||||
value *= 1.0 / get_prefix_multiplier(to_prefix);
|
||||
// NB: python ifcopenshell.util.unit.convert checks `from_unit` (not
|
||||
// `to_unit`) here. Mirrored for parity — from_unit and to_unit are
|
||||
// always the same dimension in valid calls, so behaviour is the same.
|
||||
if (from_unit.find("SQUARE") != std::string::npos) {
|
||||
value *= 1.0 / get_prefix_multiplier(to_prefix);
|
||||
} else if (from_unit.find("CUBIC") != std::string::npos) {
|
||||
value *= 1.0 / get_prefix_multiplier(to_prefix);
|
||||
value *= 1.0 / get_prefix_multiplier(to_prefix);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
double convert_unit(double value, express::base from_unit, express::base to_unit) {
|
||||
if (!from_unit || !to_unit) {
|
||||
|
||||
+1
-30
@@ -24,6 +24,7 @@
|
||||
#define UNIT_H
|
||||
|
||||
#include "../ifcparse/express.h"
|
||||
#include "unit_convert.h"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -33,27 +34,6 @@ namespace ifcopenshell {
|
||||
class file;
|
||||
}
|
||||
|
||||
// SI prefix multipliers, e.g. "MILLI" -> 1e-3. Empty key not present;
|
||||
// callers should pass an empty prefix string for "no prefix".
|
||||
extern const std::unordered_map<std::string, double> SI_PREFIXES;
|
||||
|
||||
// SI prefix display symbols, e.g. "MILLI" -> "m".
|
||||
extern const std::unordered_map<std::string, std::string> SI_PREFIX_SYMBOLS;
|
||||
|
||||
// Conversion-based unit name (lowercase, IFC convention) -> SI base scale.
|
||||
// e.g. "foot" -> 0.3048, "square foot" -> 0.09290304.
|
||||
extern const std::unordered_map<std::string, double> SI_CONVERSIONS;
|
||||
|
||||
// Conversion-based unit name -> IFC unit type, e.g. "foot" -> "LENGTHUNIT".
|
||||
extern const std::unordered_map<std::string, std::string> IMPERIAL_TYPES;
|
||||
|
||||
// Display symbol per unit name. Covers IfcSIUnit names ("METRE" -> "m") and
|
||||
// IfcConversionBasedUnit names ("foot" -> "ft").
|
||||
extern const std::unordered_map<std::string, std::string> UNIT_SYMBOLS;
|
||||
|
||||
// Returns the multiplier for an SI prefix. Empty string returns 1.0.
|
||||
double get_prefix_multiplier(const std::string& prefix);
|
||||
|
||||
// Returns the SI scale for an IfcNamedUnit such that
|
||||
// value_in_unit * scale == value_in_si_base
|
||||
// Walks IfcConversionBasedUnit chains down to IfcSIUnit. Returns nullopt
|
||||
@@ -74,15 +54,6 @@ std::optional<express::base> get_project_unit(ifcopenshell::file* ifc_file,
|
||||
double calculate_unit_scale(ifcopenshell::file* ifc_file,
|
||||
const std::string& unit_type = "LENGTHUNIT");
|
||||
|
||||
// Convert between two units identified by name + optional SI prefix.
|
||||
// SQUARE_/CUBIC_ prefixed SI names get the prefix multiplier squared/cubed
|
||||
// (matches python ifcopenshell.util.unit.convert).
|
||||
double convert(double value,
|
||||
const std::string& from_prefix,
|
||||
const std::string& from_unit,
|
||||
const std::string& to_prefix,
|
||||
const std::string& to_unit);
|
||||
|
||||
// Convert between two IfcNamedUnit entities. Pulls Name and Prefix off each
|
||||
// and delegates to convert(). IfcConversionBasedUnit names that don't appear
|
||||
// in SI_CONVERSIONS return the value unchanged.
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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 *
|
||||
* Lesser GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#include "unit_convert.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
const std::unordered_map<std::string, double> SI_PREFIXES = {
|
||||
{"EXA", 1e18},
|
||||
{"PETA", 1e15},
|
||||
{"TERA", 1e12},
|
||||
{"GIGA", 1e9},
|
||||
{"MEGA", 1e6},
|
||||
{"KILO", 1e3},
|
||||
{"HECTO", 1e2},
|
||||
{"DECA", 1e1},
|
||||
{"DECI", 1e-1},
|
||||
{"CENTI", 1e-2},
|
||||
{"MILLI", 1e-3},
|
||||
{"MICRO", 1e-6},
|
||||
{"NANO", 1e-9},
|
||||
{"PICO", 1e-12},
|
||||
{"FEMTO", 1e-15},
|
||||
{"ATTO", 1e-18},
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, std::string> SI_PREFIX_SYMBOLS = {
|
||||
{"EXA", "E"},
|
||||
{"PETA", "P"},
|
||||
{"TERA", "T"},
|
||||
{"GIGA", "G"},
|
||||
{"MEGA", "M"},
|
||||
{"KILO", "k"},
|
||||
{"HECTO", "h"},
|
||||
{"DECA", "da"},
|
||||
{"DECI", "d"},
|
||||
{"CENTI", "c"},
|
||||
{"MILLI", "m"},
|
||||
{"MICRO", "\xCE\xBC"}, // μ (UTF-8)
|
||||
{"NANO", "n"},
|
||||
{"PICO", "p"},
|
||||
{"FEMTO", "f"},
|
||||
{"ATTO", "a"},
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, double> SI_CONVERSIONS = {
|
||||
{"thou", 0.0000254},
|
||||
{"inch", 0.0254},
|
||||
{"foot", 0.3048},
|
||||
{"yard", 0.914},
|
||||
{"mile", 1609.0},
|
||||
{"square thou", 6.4516e-10},
|
||||
{"square inch", 0.0006452},
|
||||
{"square foot", 0.09290304},
|
||||
{"square yard", 0.83612736},
|
||||
{"acre", 4046.86},
|
||||
{"square mile", 2588881.0},
|
||||
{"cubic thou", 1.6387064e-14},
|
||||
{"cubic inch", 0.00001639},
|
||||
{"cubic foot", 0.02831684671168849},
|
||||
{"cubic yard", 0.7636},
|
||||
{"cubic mile", 4165509529.0},
|
||||
{"litre", 0.001},
|
||||
{"fluid ounce uk", 0.0000284130625},
|
||||
{"fluid ounce us", 0.00002957353},
|
||||
{"pint uk", 0.000568},
|
||||
{"pint us", 0.000473},
|
||||
{"gallon uk", 0.004546},
|
||||
{"gallon us", 0.003785},
|
||||
{"degree", 0.0174532925199433}, // pi / 180
|
||||
{"ounce", 0.02835},
|
||||
{"pound", 0.454},
|
||||
{"ton uk", 1016.0469088},
|
||||
{"ton us", 907.18474},
|
||||
{"tonne", 1000.0},
|
||||
{"lbf", 4.4482216153},
|
||||
{"kip", 4448.2216153},
|
||||
{"psi", 6894.7572932},
|
||||
{"ksi", 6894757.2932},
|
||||
{"minute", 60.0},
|
||||
{"hour", 3600.0},
|
||||
{"day", 86400.0},
|
||||
{"btu", 1055.056},
|
||||
{"fahrenheit", 1.8},
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, std::string> IMPERIAL_TYPES = {
|
||||
{"thou", "LENGTHUNIT"},
|
||||
{"inch", "LENGTHUNIT"},
|
||||
{"foot", "LENGTHUNIT"},
|
||||
{"yard", "LENGTHUNIT"},
|
||||
{"mile", "LENGTHUNIT"},
|
||||
{"square thou", "AREAUNIT"},
|
||||
{"square inch", "AREAUNIT"},
|
||||
{"square foot", "AREAUNIT"},
|
||||
{"square yard", "AREAUNIT"},
|
||||
{"acre", "AREAUNIT"},
|
||||
{"square mile", "AREAUNIT"},
|
||||
{"cubic thou", "VOLUMEUNIT"},
|
||||
{"cubic inch", "VOLUMEUNIT"},
|
||||
{"cubic foot", "VOLUMEUNIT"},
|
||||
{"cubic yard", "VOLUMEUNIT"},
|
||||
{"cubic mile", "VOLUMEUNIT"},
|
||||
{"litre", "VOLUMEUNIT"},
|
||||
{"fluid ounce uk", "VOLUMEUNIT"},
|
||||
{"fluid ounce us", "VOLUMEUNIT"},
|
||||
{"pint uk", "VOLUMEUNIT"},
|
||||
{"pint us", "VOLUMEUNIT"},
|
||||
{"gallon uk", "VOLUMEUNIT"},
|
||||
{"gallon us", "VOLUMEUNIT"},
|
||||
{"degree", "PLANEANGLEUNIT"},
|
||||
{"ounce", "MASSUNIT"},
|
||||
{"pound", "MASSUNIT"},
|
||||
{"ton uk", "MASSUNIT"},
|
||||
{"ton us", "MASSUNIT"},
|
||||
{"tonne", "MASSUNIT"},
|
||||
{"lbf", "FORCEUNIT"},
|
||||
{"kip", "FORCEUNIT"},
|
||||
{"psi", "PRESSUREUNIT"},
|
||||
{"ksi", "PRESSUREUNIT"},
|
||||
{"minute", "TIMEUNIT"},
|
||||
{"hour", "TIMEUNIT"},
|
||||
{"day", "TIMEUNIT"},
|
||||
{"btu", "ENERGYUNIT"},
|
||||
{"fahrenheit", "THERMODYNAMICTEMPERATUREUNIT"},
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, std::string> UNIT_SYMBOLS = {
|
||||
// SI base / derived
|
||||
{"CUBIC_METRE", "m3"},
|
||||
{"GRAM", "g"},
|
||||
{"SECOND", "s"},
|
||||
{"SQUARE_METRE", "m2"},
|
||||
{"METRE", "m"},
|
||||
{"NEWTON", "N"},
|
||||
{"PASCAL", "Pa"},
|
||||
// Conversion-based
|
||||
{"pound-force", "lbf"},
|
||||
{"pound-force per square inch", "psi"},
|
||||
{"thou", "th"},
|
||||
{"inch", "in"},
|
||||
{"foot", "ft"},
|
||||
{"yard", "yd"},
|
||||
{"mile", "mi"},
|
||||
{"square thou", "th2"},
|
||||
{"square inch", "in2"},
|
||||
{"square foot", "ft2"},
|
||||
{"square yard", "yd2"},
|
||||
{"acre", "ac"},
|
||||
{"square mile", "mi2"},
|
||||
{"cubic thou", "th3"},
|
||||
{"cubic inch", "in3"},
|
||||
{"cubic foot", "ft3"},
|
||||
{"cubic yard", "yd3"},
|
||||
{"cubic mile", "mi3"},
|
||||
{"litre", "L"},
|
||||
{"fluid ounce uk", "fl oz"},
|
||||
{"fluid ounce us", "fl oz"},
|
||||
{"pint uk", "pt"},
|
||||
{"pint us", "pt"},
|
||||
{"gallon uk", "gal"},
|
||||
{"gallon us", "gal"},
|
||||
{"degree", "\xC2\xB0"}, // °
|
||||
{"ounce", "oz"},
|
||||
{"pound", "lb"},
|
||||
{"ton uk", "ton"},
|
||||
{"ton us", "ton"},
|
||||
{"tonne", "t"},
|
||||
{"lbf", "lbf"},
|
||||
{"kip", "kip"},
|
||||
{"psi", "psi"},
|
||||
{"ksi", "ksi"},
|
||||
{"minute", "min"},
|
||||
{"hour", "hr"},
|
||||
{"day", "day"},
|
||||
{"btu", "btu"},
|
||||
{"fahrenheit", "\xC2\xB0\x46"}, // °F
|
||||
};
|
||||
|
||||
std::string to_lower(const std::string& s) {
|
||||
std::string r;
|
||||
r.resize(s.size());
|
||||
std::transform(s.begin(), s.end(), r.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
return r;
|
||||
}
|
||||
|
||||
double get_prefix_multiplier(const std::string& prefix) {
|
||||
if (prefix.empty()) {
|
||||
return 1.0;
|
||||
}
|
||||
auto it = SI_PREFIXES.find(prefix);
|
||||
return (it == SI_PREFIXES.end()) ? 1.0 : it->second;
|
||||
}
|
||||
|
||||
double convert(double value,
|
||||
const std::string& from_prefix,
|
||||
const std::string& from_unit,
|
||||
const std::string& to_prefix,
|
||||
const std::string& to_unit) {
|
||||
const std::string fl = to_lower(from_unit);
|
||||
const std::string tl = to_lower(to_unit);
|
||||
|
||||
if (auto it = SI_CONVERSIONS.find(fl); it != SI_CONVERSIONS.end()) {
|
||||
value *= it->second;
|
||||
} else if (!from_prefix.empty()) {
|
||||
value *= get_prefix_multiplier(from_prefix);
|
||||
if (from_unit.find("SQUARE") != std::string::npos) {
|
||||
value *= get_prefix_multiplier(from_prefix);
|
||||
} else if (from_unit.find("CUBIC") != std::string::npos) {
|
||||
value *= get_prefix_multiplier(from_prefix);
|
||||
value *= get_prefix_multiplier(from_prefix);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto it = SI_CONVERSIONS.find(tl); it != SI_CONVERSIONS.end()) {
|
||||
return value * (1.0 / it->second);
|
||||
} else if (!to_prefix.empty()) {
|
||||
value *= 1.0 / get_prefix_multiplier(to_prefix);
|
||||
// NB: python ifcopenshell.util.unit.convert checks `from_unit` (not
|
||||
// `to_unit`) here. Mirrored for parity — from_unit and to_unit are
|
||||
// always the same dimension in valid calls, so behaviour is the same.
|
||||
if (from_unit.find("SQUARE") != std::string::npos) {
|
||||
value *= 1.0 / get_prefix_multiplier(to_prefix);
|
||||
} else if (from_unit.find("CUBIC") != std::string::npos) {
|
||||
value *= 1.0 / get_prefix_multiplier(to_prefix);
|
||||
value *= 1.0 / get_prefix_multiplier(to_prefix);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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 *
|
||||
* Lesser GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the Lesser GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
// Unit conversion that needs no IFC file: the ported lookup tables from
|
||||
// src/ifcopenshell-python/ifcopenshell/util/unit.py plus the two pure
|
||||
// functions over them.
|
||||
//
|
||||
// Split out of unit.h/unit.cpp because those pull in ../ifcparse/express.h for
|
||||
// the entity-walking helpers (calculate_unit_scale, si_scale_from_named_unit,
|
||||
// convert_unit). IfcViewerCore — and through it the Emscripten build — needs
|
||||
// convert() to resolve a federation unit name to metres, but links neither
|
||||
// IfcParse nor Qt. Keeping one copy of the tables here rather than a second
|
||||
// one in the web layer is the whole point of the split.
|
||||
|
||||
#ifndef UNIT_CONVERT_H
|
||||
#define UNIT_CONVERT_H
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
// SI prefix multipliers, e.g. "MILLI" -> 1e-3. Empty key not present;
|
||||
// callers should pass an empty prefix string for "no prefix".
|
||||
extern const std::unordered_map<std::string, double> SI_PREFIXES;
|
||||
|
||||
// SI prefix display symbols, e.g. "MILLI" -> "m".
|
||||
extern const std::unordered_map<std::string, std::string> SI_PREFIX_SYMBOLS;
|
||||
|
||||
// Conversion-based unit name (lowercase, IFC convention) -> SI base scale.
|
||||
// e.g. "foot" -> 0.3048, "square foot" -> 0.09290304.
|
||||
extern const std::unordered_map<std::string, double> SI_CONVERSIONS;
|
||||
|
||||
// Conversion-based unit name -> IFC unit type, e.g. "foot" -> "LENGTHUNIT".
|
||||
extern const std::unordered_map<std::string, std::string> IMPERIAL_TYPES;
|
||||
|
||||
// Display symbol per unit name. Covers IfcSIUnit names ("METRE" -> "m") and
|
||||
// IfcConversionBasedUnit names ("foot" -> "ft").
|
||||
extern const std::unordered_map<std::string, std::string> UNIT_SYMBOLS;
|
||||
|
||||
// Lowercase an IFC unit name so it can be looked up in the tables above,
|
||||
// which are keyed by the lowercase IFC convention ("foot", "square foot").
|
||||
std::string to_lower(const std::string& s);
|
||||
|
||||
// Returns the multiplier for an SI prefix. Empty string returns 1.0.
|
||||
double get_prefix_multiplier(const std::string& prefix);
|
||||
|
||||
// Convert between two units identified by name + optional SI prefix.
|
||||
// SQUARE_/CUBIC_ prefixed SI names get the prefix multiplier squared/cubed
|
||||
// (matches python ifcopenshell.util.unit.convert).
|
||||
double convert(double value,
|
||||
const std::string& from_prefix,
|
||||
const std::string& from_unit,
|
||||
const std::string& to_prefix,
|
||||
const std::string& to_unit);
|
||||
|
||||
#endif // UNIT_CONVERT_H
|
||||
Reference in New Issue
Block a user