2026-05-01 12:12:29 +10:00
|
|
|
/********************************************************************************
|
|
|
|
|
* *
|
|
|
|
|
* 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/>. *
|
|
|
|
|
* *
|
|
|
|
|
********************************************************************************/
|
|
|
|
|
|
2026-07-03 12:14:15 +02:00
|
|
|
#include "unit.h"
|
2026-05-01 12:12:29 +10:00
|
|
|
|
2026-07-03 13:26:30 +02:00
|
|
|
#include "../ifcparse/exception.h"
|
2026-05-01 12:12:29 +10:00
|
|
|
#include "../ifcparse/file.h"
|
|
|
|
|
#include "../ifcparse/instance_data.h"
|
2026-07-03 13:26:30 +02:00
|
|
|
#include "schema_dispatch.i"
|
2026-05-01 12:12:29 +10:00
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cctype>
|
|
|
|
|
|
2026-07-03 12:14:15 +02:00
|
|
|
const std::unordered_map<std::string, double> SI_PREFIXES = {
|
2026-07-03 13:26:30 +02:00
|
|
|
{"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},
|
2026-05-01 12:12:29 +10:00
|
|
|
};
|
|
|
|
|
|
2026-07-03 12:14:15 +02:00
|
|
|
const std::unordered_map<std::string, std::string> SI_PREFIX_SYMBOLS = {
|
2026-07-03 13:26:30 +02:00
|
|
|
{"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"},
|
2026-05-01 12:12:29 +10:00
|
|
|
};
|
|
|
|
|
|
2026-07-03 12:14:15 +02:00
|
|
|
const std::unordered_map<std::string, double> SI_CONVERSIONS = {
|
2026-07-03 13:26:30 +02:00
|
|
|
{"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},
|
2026-05-01 12:12:29 +10:00
|
|
|
};
|
|
|
|
|
|
2026-07-03 12:14:15 +02:00
|
|
|
const std::unordered_map<std::string, std::string> IMPERIAL_TYPES = {
|
2026-07-03 13:26:30 +02:00
|
|
|
{"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"},
|
2026-05-01 12:12:29 +10:00
|
|
|
};
|
|
|
|
|
|
2026-07-03 12:14:15 +02:00
|
|
|
const std::unordered_map<std::string, std::string> UNIT_SYMBOLS = {
|
2026-05-01 12:12:29 +10:00
|
|
|
// SI base / derived
|
2026-07-03 13:26:30 +02:00
|
|
|
{"CUBIC_METRE", "m3"},
|
|
|
|
|
{"GRAM", "g"},
|
|
|
|
|
{"SECOND", "s"},
|
|
|
|
|
{"SQUARE_METRE", "m2"},
|
|
|
|
|
{"METRE", "m"},
|
|
|
|
|
{"NEWTON", "N"},
|
|
|
|
|
{"PASCAL", "Pa"},
|
2026-05-01 12:12:29 +10:00
|
|
|
// Conversion-based
|
2026-07-03 13:26:30 +02:00
|
|
|
{"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
|
2026-05-01 12:12:29 +10:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
2026-07-03 12:14:15 +02:00
|
|
|
std::string to_lower(const std::string& s) {
|
2026-05-01 12:12:29 +10:00
|
|
|
std::string r;
|
|
|
|
|
r.resize(s.size());
|
2026-07-03 13:26:30 +02:00
|
|
|
std::transform(s.begin(), s.end(), r.begin(), [](unsigned char c) { return std::tolower(c); });
|
2026-05-01 12:12:29 +10:00
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-03 13:26:30 +02:00
|
|
|
[[noreturn]] void unsupported_schema(const std::string& name) {
|
|
|
|
|
throw ifcopenshell::exception("No helper implementation was built for schema " + name);
|
2026-05-01 12:12:29 +10:00
|
|
|
}
|
|
|
|
|
|
2026-08-08 07:42:45 +02:00
|
|
|
std::optional<double> numeric_value(const express::base& value) {
|
2026-07-03 13:26:30 +02:00
|
|
|
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;
|
2026-05-01 12:12:29 +10:00
|
|
|
}
|
|
|
|
|
|
2026-07-03 13:26:30 +02:00
|
|
|
template <typename Schema>
|
2026-08-08 07:42:45 +02:00
|
|
|
std::optional<double> si_scale_from_named_unit_s(express::base unit) {
|
2026-05-01 12:12:29 +10:00
|
|
|
double scale = 1.0;
|
2026-07-03 13:26:30 +02:00
|
|
|
while (auto conversion = unit.template as<typename Schema::IfcConversionBasedUnit>()) {
|
|
|
|
|
if (const auto it = SI_CONVERSIONS.find(to_lower(conversion.Name()));
|
2026-07-03 12:14:15 +02:00
|
|
|
it != SI_CONVERSIONS.end()) {
|
2026-05-01 12:12:29 +10:00
|
|
|
return scale * it->second;
|
|
|
|
|
}
|
2026-07-03 13:26:30 +02:00
|
|
|
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();
|
2026-05-01 12:12:29 +10:00
|
|
|
}
|
|
|
|
|
|
2026-07-03 13:26:30 +02:00
|
|
|
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);
|
2026-05-01 12:12:29 +10:00
|
|
|
if (name.find("SQUARE") != std::string::npos) {
|
2026-07-03 13:26:30 +02:00
|
|
|
multiplier *= get_prefix_multiplier(prefix);
|
2026-05-01 12:12:29 +10:00
|
|
|
} else if (name.find("CUBIC") != std::string::npos) {
|
2026-07-03 13:26:30 +02:00
|
|
|
multiplier *= get_prefix_multiplier(prefix) * get_prefix_multiplier(prefix);
|
2026-05-01 12:12:29 +10:00
|
|
|
}
|
2026-07-03 13:26:30 +02:00
|
|
|
return scale * multiplier;
|
2026-05-01 12:12:29 +10:00
|
|
|
}
|
2026-07-03 13:26:30 +02:00
|
|
|
if (unit.template as<typename Schema::IfcContextDependentUnit>()) {
|
2026-05-01 12:12:29 +10:00
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
return scale;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-03 13:26:30 +02:00
|
|
|
template <typename Schema>
|
2026-08-08 07:42:45 +02:00
|
|
|
std::optional<express::base> get_unit_assignment_s(ifcopenshell::file* ifc_file) {
|
2026-07-03 13:26:30 +02:00
|
|
|
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;
|
2026-05-01 12:12:29 +10:00
|
|
|
}
|
|
|
|
|
|
2026-07-03 13:26:30 +02:00
|
|
|
template <typename Schema>
|
2026-08-08 07:42:45 +02:00
|
|
|
std::optional<express::base> get_project_unit_s(ifcopenshell::file* ifc_file,
|
2026-07-03 13:26:30 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2026-05-01 12:12:29 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-03 13:26:30 +02:00
|
|
|
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>
|
2026-08-08 07:42:45 +02:00
|
|
|
void unit_name_s(const express::base& unit, std::string& prefix, std::string& name) {
|
2026-07-03 13:26:30 +02:00
|
|
|
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>
|
2026-08-08 07:42:45 +02:00
|
|
|
double convert_unit_s(double value, const express::base& from_unit, const express::base& to_unit) {
|
2026-07-03 13:26:30 +02:00
|
|
|
std::string from_prefix;
|
|
|
|
|
std::string from_name;
|
|
|
|
|
std::string to_prefix;
|
|
|
|
|
std::string to_name;
|
|
|
|
|
unit_name_s<Schema>(from_unit, from_prefix, from_name);
|
|
|
|
|
unit_name_s<Schema>(to_unit, to_prefix, to_name);
|
|
|
|
|
return convert(value, from_prefix, from_name, to_prefix, to_name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
double get_prefix_multiplier(const std::string& prefix) {
|
|
|
|
|
if (prefix.empty()) {
|
|
|
|
|
return 1.0;
|
|
|
|
|
}
|
|
|
|
|
auto it = SI_PREFIXES.find(prefix);
|
|
|
|
|
return (it == SI_PREFIXES.end()) ? 1.0 : it->second;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-08 07:42:45 +02:00
|
|
|
std::optional<double> si_scale_from_named_unit(express::base unit) {
|
2026-07-03 13:26:30 +02:00
|
|
|
if (!unit) {
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
const auto name = unit.declaration().schema()->name();
|
|
|
|
|
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
|
|
|
|
|
if (name == Identifier) \
|
|
|
|
|
return si_scale_from_named_unit_s<Schema>(unit);
|
|
|
|
|
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
|
|
|
|
|
#undef IFCOPENSHELL_DISPATCH
|
|
|
|
|
unsupported_schema(name);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-08 07:42:45 +02:00
|
|
|
std::optional<express::base> get_unit_assignment(ifcopenshell::file* ifc_file) {
|
2026-07-03 13:26:30 +02:00
|
|
|
const auto name = ifc_file->schema()->name();
|
|
|
|
|
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
|
|
|
|
|
if (name == Identifier) \
|
|
|
|
|
return get_unit_assignment_s<Schema>(ifc_file);
|
|
|
|
|
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
|
|
|
|
|
#undef IFCOPENSHELL_DISPATCH
|
|
|
|
|
unsupported_schema(name);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-08 07:42:45 +02:00
|
|
|
std::optional<express::base> get_project_unit(ifcopenshell::file* ifc_file,
|
2026-07-03 13:26:30 +02:00
|
|
|
const std::string& unit_type) {
|
|
|
|
|
const auto name = ifc_file->schema()->name();
|
|
|
|
|
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
|
|
|
|
|
if (name == Identifier) \
|
|
|
|
|
return get_project_unit_s<Schema>(ifc_file, unit_type);
|
|
|
|
|
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
|
|
|
|
|
#undef IFCOPENSHELL_DISPATCH
|
|
|
|
|
unsupported_schema(name);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-03 12:14:15 +02:00
|
|
|
double calculate_unit_scale(ifcopenshell::file* ifc_file,
|
|
|
|
|
const std::string& unit_type) {
|
2026-07-03 13:26:30 +02:00
|
|
|
const auto name = ifc_file->schema()->name();
|
|
|
|
|
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
|
|
|
|
|
if (name == Identifier) \
|
|
|
|
|
return calculate_unit_scale_s<Schema>(ifc_file, unit_type);
|
|
|
|
|
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
|
|
|
|
|
#undef IFCOPENSHELL_DISPATCH
|
|
|
|
|
unsupported_schema(name);
|
2026-05-01 12:12:29 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double convert(double value,
|
2026-07-03 13:26:30 +02:00
|
|
|
const std::string& from_prefix,
|
|
|
|
|
const std::string& from_unit,
|
|
|
|
|
const std::string& to_prefix,
|
|
|
|
|
const std::string& to_unit) {
|
2026-07-03 12:14:15 +02:00
|
|
|
const std::string fl = to_lower(from_unit);
|
|
|
|
|
const std::string tl = to_lower(to_unit);
|
2026-05-01 12:12:29 +10:00
|
|
|
|
2026-07-03 12:14:15 +02:00
|
|
|
if (auto it = SI_CONVERSIONS.find(fl); it != SI_CONVERSIONS.end()) {
|
2026-05-01 12:12:29 +10:00
|
|
|
value *= it->second;
|
|
|
|
|
} else if (!from_prefix.empty()) {
|
2026-07-03 12:14:15 +02:00
|
|
|
value *= get_prefix_multiplier(from_prefix);
|
2026-05-01 12:12:29 +10:00
|
|
|
if (from_unit.find("SQUARE") != std::string::npos) {
|
2026-07-03 12:14:15 +02:00
|
|
|
value *= get_prefix_multiplier(from_prefix);
|
2026-05-01 12:12:29 +10:00
|
|
|
} else if (from_unit.find("CUBIC") != std::string::npos) {
|
2026-07-03 12:14:15 +02:00
|
|
|
value *= get_prefix_multiplier(from_prefix);
|
|
|
|
|
value *= get_prefix_multiplier(from_prefix);
|
2026-05-01 12:12:29 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-03 12:14:15 +02:00
|
|
|
if (auto it = SI_CONVERSIONS.find(tl); it != SI_CONVERSIONS.end()) {
|
2026-05-01 12:12:29 +10:00
|
|
|
return value * (1.0 / it->second);
|
|
|
|
|
} else if (!to_prefix.empty()) {
|
2026-07-03 12:14:15 +02:00
|
|
|
value *= 1.0 / get_prefix_multiplier(to_prefix);
|
2026-05-01 12:12:29 +10:00
|
|
|
// 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) {
|
2026-07-03 12:14:15 +02:00
|
|
|
value *= 1.0 / get_prefix_multiplier(to_prefix);
|
2026-05-01 12:12:29 +10:00
|
|
|
} else if (from_unit.find("CUBIC") != std::string::npos) {
|
2026-07-03 12:14:15 +02:00
|
|
|
value *= 1.0 / get_prefix_multiplier(to_prefix);
|
|
|
|
|
value *= 1.0 / get_prefix_multiplier(to_prefix);
|
2026-05-01 12:12:29 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-08 07:42:45 +02:00
|
|
|
double convert_unit(double value, express::base from_unit, express::base to_unit) {
|
2026-07-03 13:26:30 +02:00
|
|
|
if (!from_unit || !to_unit) {
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
const auto name = from_unit.declaration().schema()->name();
|
|
|
|
|
if (name != to_unit.declaration().schema()->name()) {
|
|
|
|
|
throw ifcopenshell::exception("Cannot convert units from different IFC schemas");
|
|
|
|
|
}
|
|
|
|
|
#define IFCOPENSHELL_DISPATCH(Schema, Identifier) \
|
|
|
|
|
if (name == Identifier) \
|
|
|
|
|
return convert_unit_s<Schema>(value, from_unit, to_unit);
|
|
|
|
|
IFCOPENSHELL_HELPER_FOR_EACH_SCHEMA(IFCOPENSHELL_DISPATCH)
|
|
|
|
|
#undef IFCOPENSHELL_DISPATCH
|
|
|
|
|
unsupported_schema(name);
|
2026-05-01 12:12:29 +10:00
|
|
|
}
|