This commit is contained in:
Thomas Krijnen
2026-07-03 12:14:15 +02:00
parent a54a8b80d3
commit 55e97e5379
13 changed files with 152 additions and 152 deletions
@@ -34,7 +34,7 @@ Stack
primitives used in instrumentation.
* **IfcOpenShell C++ libs** (IfcParse / IfcGeom) for IFC parsing and
geometry generation, plus **helpers** for the schema-agnostic
helpers (``Unit``, ``Geolocation``, ``Placement``).
helpers (``unit``, ``geolocation``, ``placement``).
* **Eigen3** for 4×4 matrices and small linear algebra.
* **meshoptimizer** at sidecar-build time only — decimates each
unique mesh into an LOD1 slice. Not pulled at runtime.
@@ -23,9 +23,9 @@
#include "../../SessionState.h"
#include "../../../ifcviewer/Federation.h"
#include "../../../ifcutil/Geolocation.h"
#include "../../../helpers/geolocation.h"
#include "../../../ifcviewer/SceneLoader.h"
#include "../../../ifcutil/Unit.h"
#include "../../../helpers/unit.h"
#include <cmath>
@@ -89,8 +89,8 @@ QString formatNamedUnit(const express::Base& unit) {
text += QString::fromStdString(prefix) + " ";
}
text += QString::fromStdString(name);
auto symbol_it = kUnitSymbols.find(name);
if (symbol_it != kUnitSymbols.end()) {
auto symbol_it = UNIT_SYMBOLS.find(name);
if (symbol_it != UNIT_SYMBOLS.end()) {
text += " (" + QString::fromStdString(symbol_it->second) + ")";
}
return text;
@@ -100,8 +100,8 @@ QString formatNamedUnit(const express::Base& unit) {
if (name_attr.isNull()) return "";
const std::string name = static_cast<std::string>(name_attr);
QString text = QString::fromStdString(name);
auto symbol_it = kUnitSymbols.find(name);
if (symbol_it != kUnitSymbols.end()) {
auto symbol_it = UNIT_SYMBOLS.find(name);
if (symbol_it != UNIT_SYMBOLS.end()) {
text += " (" + QString::fromStdString(symbol_it->second) + ")";
}
return text;
@@ -119,7 +119,7 @@ std::optional<QString> coordinateOperationType(ifcopenshell::file* ifc_file) {
}
if (ifc_file->schema()->name() == "IFC2X3") {
if (getHelmertTransformationParameters(ifc_file)) {
if (get_helmert_transformation_parameters(ifc_file)) {
return QStringLiteral("ePSet_MapConversion");
}
}
@@ -129,10 +129,10 @@ std::optional<QString> coordinateOperationType(ifcopenshell::file* ifc_file) {
SelectedModelGeorefState stateFromLiveFile(ifcopenshell::file* ifc_file) {
if (!ifc_file) return unknownState("Not available yet", "No data source");
const auto params = getHelmertTransformationParameters(ifc_file);
const auto params = get_helmert_transformation_parameters(ifc_file);
const auto coordop_type = coordinateOperationType(ifc_file);
const auto project_unit_value = getProjectUnit(ifc_file, "LENGTHUNIT");
const auto map_unit_value = getMapUnit(ifc_file);
const auto project_unit_value = get_project_unit(ifc_file, "LENGTHUNIT");
const auto map_unit_value = get_map_unit(ifc_file);
const QString project_unit = project_unit_value ? formatNamedUnit(*project_unit_value) : QString("");
const QString map_unit = map_unit_value ? formatNamedUnit(*map_unit_value) : project_unit;
@@ -143,7 +143,7 @@ SelectedModelGeorefState stateFromLiveFile(ifcopenshell::file* ifc_file) {
return state;
}
const double rotation_dd = xaxis2angleDeg(params->xaa, params->xao);
const double rotation_dd = x_axis_to_angle_deg(params->xaa, params->xao);
return {
"Yes",
coordop_type.value_or("Unknown"),
+3 -3
View File
@@ -17,7 +17,7 @@
# #
################################################################################
message("Running CMakeLists.txt in /src/ifcutil")
message("Running CMakeLists.txt in /src/helpers")
# Schema-agnostic IFC helpers ported from ifcopenshell.util.*. Pure
# IfcParse + Eigen, no Qt, no IfcGeom, no viewer/renderer deps — so unit
@@ -44,8 +44,8 @@ set_target_properties(helpers PROPERTIES
)
# PUBLIC: callers (IfcViewer, test_federation, future tools) get
# `#include "Unit.h"` etc. via this directory automatically, no
# `#include "../helpers/Unit.h"` ugliness at call sites.
# `#include "unit.h"` etc. via this directory automatically, no
# `#include "../helpers/unit.h"` ugliness at call sites.
target_include_directories(helpers PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(helpers PUBLIC
+21 -21
View File
@@ -17,8 +17,8 @@
* *
********************************************************************************/
#include "Geolocation.h"
#include "Placement.h"
#include "geolocation.h"
#include "placement.h"
#include "../ifcparse/express.h"
#include "../ifcparse/file.h"
@@ -35,7 +35,7 @@ namespace {
// ePSet_MapConversion stores eastings/northings/scale as IfcLengthMeasure or
// IfcReal wrapped inside IfcValue (a SELECT) — get_attribute_value(0) peels
// the wrapper. Returns nullopt if the value is missing or non-numeric.
std::optional<double> readPropertyValueDouble(const express::Base& property) {
std::optional<double> read_property_value_double(const express::Base& property) {
if (!property.declaration().is("IfcPropertySingleValue")) return std::nullopt;
auto pe = property.as<express::Entity>();
auto nv = pe.get("NominalValue");
@@ -53,7 +53,7 @@ std::optional<double> readPropertyValueDouble(const express::Base& property) {
} // namespace
std::optional<HelmertTransformation>
getHelmertTransformationParameters(ifcopenshell::file* ifc_file) {
get_helmert_transformation_parameters(ifcopenshell::file* ifc_file) {
HelmertTransformation p;
const std::string schema_name = ifc_file->schema()->name();
@@ -81,7 +81,7 @@ getHelmertTransformationParameters(ifcopenshell::file* ifc_file) {
auto pname_attr = pe.get("Name");
if (pname_attr.isNull()) continue;
std::string pname = pname_attr;
auto value = readPropertyValueDouble(prop);
auto value = read_property_value_double(prop);
if (!value) continue;
if (pname == "Eastings") p.e = *value;
else if (pname == "Northings") p.n = *value;
@@ -153,7 +153,7 @@ getHelmertTransformationParameters(ifcopenshell::file* ifc_file) {
return p;
}
std::optional<Eigen::Matrix4d> getWcs(ifcopenshell::file* ifc_file) {
std::optional<Eigen::Matrix4d> get_wcs(ifcopenshell::file* ifc_file) {
auto contexts = ifc_file->instances_by_type_excl_subtypes(
"IfcGeometricRepresentationContext");
express::Base wcs;
@@ -175,11 +175,11 @@ std::optional<Eigen::Matrix4d> getWcs(ifcopenshell::file* ifc_file) {
if (!(decl.is("IfcAxis2Placement3D") || decl.is("IfcAxis2PlacementLinear"))) {
return std::nullopt;
}
return getAxis2Placement(wcs);
return get_axis2_placement(wcs);
}
Eigen::Matrix4d local2global(const Eigen::Matrix4d& matrix,
const HelmertTransformation& p) {
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);
@@ -208,17 +208,17 @@ Eigen::Matrix4d local2global(const Eigen::Matrix4d& matrix,
return result;
}
Eigen::Matrix4d autoLocal2Global(ifcopenshell::file* ifc_file,
const Eigen::Matrix4d& matrix,
bool should_return_in_map_units) {
auto params = getHelmertTransformationParameters(ifc_file);
Eigen::Matrix4d auto_local_to_global(ifcopenshell::file* ifc_file,
const Eigen::Matrix4d& matrix,
bool should_return_in_map_units) {
auto params = get_helmert_transformation_parameters(ifc_file);
if (!params) return matrix;
Eigen::Matrix4d m = matrix;
if (auto wcs = getWcs(ifc_file)) {
if (auto wcs = get_wcs(ifc_file)) {
m = wcs->inverse() * m;
}
Eigen::Matrix4d result = local2global(m, *params);
Eigen::Matrix4d result = local_to_global(m, *params);
if (!should_return_in_map_units) {
result(0, 3) /= params->scale;
result(1, 3) /= params->scale;
@@ -227,8 +227,8 @@ Eigen::Matrix4d autoLocal2Global(ifcopenshell::file* ifc_file,
return result;
}
Eigen::Matrix4d helmertMetersFromParameters(const HelmertTransformation& p,
double map_unit_to_meters) {
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);
@@ -247,7 +247,7 @@ Eigen::Matrix4d helmertMetersFromParameters(const HelmertTransformation& p,
return M;
}
std::optional<express::Base> getMapUnit(ifcopenshell::file* ifc_file) {
std::optional<express::Base> get_map_unit(ifcopenshell::file* ifc_file) {
std::vector<express::Base> coordops;
try {
coordops = ifc_file->instances_by_type("IfcCoordinateOperation");
@@ -264,7 +264,7 @@ std::optional<express::Base> getMapUnit(ifcopenshell::file* ifc_file) {
return (express::Base) mu_attr;
}
double xaxis2angleDeg(double xaa, double xao) {
constexpr double kPi = 3.14159265358979323846;
return -std::atan2(xao, xaa) * (180.0 / kPi);
double x_axis_to_angle_deg(double xaa, double xao) {
constexpr double PI = 3.14159265358979323846;
return -std::atan2(xao, xaa) * (180.0 / PI);
}
+16 -16
View File
@@ -19,7 +19,7 @@
// Port of selected helpers from
// src/ifcopenshell-python/ifcopenshell/util/geolocation.py — primarily
// auto_local2global, which builds a 4x4 matrix that lifts an element's local
// 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.
@@ -52,28 +52,28 @@ struct HelmertTransformation {
// IfcProject.ePSet_MapConversion property set in IFC2X3. Returns nullopt
// when the model has no map conversion.
std::optional<HelmertTransformation>
getHelmertTransformationParameters(ifcopenshell::file* ifc_file);
get_helmert_transformation_parameters(ifcopenshell::file* ifc_file);
// Read the IfcGeometricRepresentationContext.WorldCoordinateSystem (preferring
// the "Model" context) as a 4x4 matrix. Returns nullopt when the model has
// no parseable WCS.
std::optional<Eigen::Matrix4d> getWcs(ifcopenshell::file* ifc_file);
std::optional<Eigen::Matrix4d> get_wcs(ifcopenshell::file* ifc_file);
// Apply a Helmert transformation to a 4x4 local matrix.
Eigen::Matrix4d local2global(const Eigen::Matrix4d& matrix,
const HelmertTransformation& params);
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
// translation column is divided by the map scale so the result is expressed
// in project length units.
Eigen::Matrix4d autoLocal2Global(ifcopenshell::file* ifc_file,
const Eigen::Matrix4d& matrix,
bool should_return_in_map_units = true);
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 autoLocal2Global's normalisation
// 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.
//
@@ -86,26 +86,26 @@ Eigen::Matrix4d autoLocal2Global(ifcopenshell::file* ifc_file,
// 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 = helmertMetersFromParameters(...) · inv(wcs_meters)
// G = helmert_meters_from_parameters(...) · inv(wcs_meters)
// (where wcs_meters has its translation column converted from project units
// to meters via calculateUnitScale).
// to meters via calculate_unit_scale).
//
// Unlike autoLocal2Global, this preserves IfcMapConversionScaled.FactorX/Y/Z
// 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 helmertMetersFromParameters(const HelmertTransformation& params,
double map_unit_to_meters);
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 /
// metadata inspection; transform composition derives map unit scale from
// IfcMapConversion.Scale instead.
std::optional<express::Base> getMapUnit(ifcopenshell::file* ifc_file);
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 xaxis2angleDeg(double xaa, double xao);
double x_axis_to_angle_deg(double xaa, double xao);
#endif // GEOLOCATION_H
+20 -20
View File
@@ -17,7 +17,7 @@
* *
********************************************************************************/
#include "Placement.h"
#include "placement.h"
#include "../ifcparse/instance_data.h"
#include "../ifcparse/schema.h"
@@ -26,13 +26,13 @@
namespace {
Eigen::Vector3d safeNormalize(const Eigen::Vector3d& v,
const Eigen::Vector3d& fallback) {
Eigen::Vector3d safe_normalize(const Eigen::Vector3d& v,
const Eigen::Vector3d& fallback) {
const double n = v.norm();
return (n > 0.0) ? Eigen::Vector3d(v / n) : fallback;
}
std::vector<double> readDirectionRatios(const express::Base& dir) {
std::vector<double> read_direction_ratios(const express::Base& dir) {
if (!dir) return {};
auto attr = dir.as<express::Entity>().get("DirectionRatios");
if (attr.isNull()) return {};
@@ -41,12 +41,12 @@ std::vector<double> readDirectionRatios(const express::Base& dir) {
} // namespace
Eigen::Matrix4d a2p(const Eigen::Vector3d& origin,
const Eigen::Vector3d& z,
const Eigen::Vector3d& x) {
const Eigen::Vector3d xn = safeNormalize(x, Eigen::Vector3d::UnitX());
const Eigen::Vector3d zn = safeNormalize(z, Eigen::Vector3d::UnitZ());
const Eigen::Vector3d yn = safeNormalize(zn.cross(xn), Eigen::Vector3d::UnitY());
Eigen::Matrix4d axes_to_placement(const Eigen::Vector3d& origin,
const Eigen::Vector3d& z,
const Eigen::Vector3d& x) {
const Eigen::Vector3d xn = safe_normalize(x, Eigen::Vector3d::UnitX());
const Eigen::Vector3d zn = safe_normalize(z, Eigen::Vector3d::UnitZ());
const Eigen::Vector3d yn = safe_normalize(zn.cross(xn), Eigen::Vector3d::UnitY());
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
m.block<3, 1>(0, 0) = xn;
@@ -56,7 +56,7 @@ Eigen::Matrix4d a2p(const Eigen::Vector3d& origin,
return m;
}
Eigen::Matrix4d getAxis2Placement(const express::Base& placement) {
Eigen::Matrix4d get_axis2_placement(const express::Base& placement) {
if (!placement) return Eigen::Matrix4d::Identity();
const auto& decl = placement.declaration();
auto entity = placement.as<express::Entity>();
@@ -68,12 +68,12 @@ Eigen::Matrix4d getAxis2Placement(const express::Base& placement) {
if (decl.is("IfcAxis2Placement3D") || decl.is("IfcAxis2PlacementLinear")) {
auto axis_attr = entity.get("Axis");
if (!axis_attr.isNull()) {
auto dr = readDirectionRatios((express::Base) axis_attr);
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 = readDirectionRatios((express::Base) refdir_attr);
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");
@@ -86,7 +86,7 @@ Eigen::Matrix4d getAxis2Placement(const express::Base& placement) {
} else if (decl.is("IfcAxis2Placement2D")) {
auto refdir_attr = entity.get("RefDirection");
if (!refdir_attr.isNull()) {
auto dr = readDirectionRatios((express::Base) refdir_attr);
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,
@@ -106,7 +106,7 @@ Eigen::Matrix4d getAxis2Placement(const express::Base& placement) {
} else if (decl.is("IfcAxis1Placement")) {
auto axis_attr = entity.get("Axis");
if (!axis_attr.isNull()) {
auto dr = readDirectionRatios((express::Base) axis_attr);
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");
@@ -120,10 +120,10 @@ Eigen::Matrix4d getAxis2Placement(const express::Base& placement) {
return Eigen::Matrix4d::Identity();
}
return a2p(o, z, x);
return axes_to_placement(o, z, x);
}
Eigen::Matrix4d getLocalPlacement(const express::Base& placement) {
Eigen::Matrix4d get_local_placement(const express::Base& placement) {
if (!placement) return Eigen::Matrix4d::Identity();
const auto& decl = placement.declaration();
@@ -132,13 +132,13 @@ Eigen::Matrix4d getLocalPlacement(const express::Base& placement) {
Eigen::Matrix4d parent = Eigen::Matrix4d::Identity();
auto rel_attr = entity.get("PlacementRelTo");
if (!rel_attr.isNull()) {
parent = getLocalPlacement((express::Base) rel_attr);
parent = get_local_placement((express::Base) rel_attr);
}
auto rp_attr = entity.get("RelativePlacement");
if (rp_attr.isNull()) return parent;
return parent * getAxis2Placement((express::Base) rp_attr);
return parent * get_axis2_placement((express::Base) rp_attr);
}
// IfcAxis2Placement* / IfcAxis1Placement passed in directly.
return getAxis2Placement(placement);
return get_axis2_placement(placement);
}
+5 -5
View File
@@ -32,19 +32,19 @@
// Build a 4x4 placement matrix from an origin + Z + X axis triple, mirroring
// ifcopenshell.util.placement.a2p. The Y axis is derived as Z × X. Inputs
// don't need to be unit; vectors are renormalised internally.
Eigen::Matrix4d a2p(const Eigen::Vector3d& origin,
const Eigen::Vector3d& z,
const Eigen::Vector3d& x);
Eigen::Matrix4d axes_to_placement(const Eigen::Vector3d& origin,
const Eigen::Vector3d& z,
const Eigen::Vector3d& x);
// IfcAxis2Placement{2D,3D,Linear} / IfcAxis1Placement -> 4x4 matrix. Mirrors
// ifcopenshell.util.placement.get_axis2placement. Returns identity for null
// or unparseable inputs. Translation is in the IFC's project length unit.
Eigen::Matrix4d getAxis2Placement(const express::Base& placement);
Eigen::Matrix4d get_axis2_placement(const express::Base& placement);
// Resolve an IfcLocalPlacement (or an IfcAxis2Placement* directly) into a
// 4x4 matrix in the IFC project's length unit, walking the PlacementRelTo
// chain. Mirrors ifcopenshell.util.placement.get_local_placement. Returns
// identity for a null input.
Eigen::Matrix4d getLocalPlacement(const express::Base& placement);
Eigen::Matrix4d get_local_placement(const express::Base& placement);
#endif // PLACEMENT_H
+44 -44
View File
@@ -17,7 +17,7 @@
* *
********************************************************************************/
#include "Unit.h"
#include "unit.h"
#include "../ifcparse/file.h"
#include "../ifcparse/instance_data.h"
@@ -26,7 +26,7 @@
#include <algorithm>
#include <cctype>
const std::unordered_map<std::string, double> kSiPrefixes = {
const std::unordered_map<std::string, double> SI_PREFIXES = {
{ "EXA", 1e18 },
{ "PETA", 1e15 },
{ "TERA", 1e12 },
@@ -45,7 +45,7 @@ const std::unordered_map<std::string, double> kSiPrefixes = {
{ "ATTO", 1e-18 },
};
const std::unordered_map<std::string, std::string> kSiPrefixSymbols = {
const std::unordered_map<std::string, std::string> SI_PREFIX_SYMBOLS = {
{ "EXA", "E" },
{ "PETA", "P" },
{ "TERA", "T" },
@@ -64,7 +64,7 @@ const std::unordered_map<std::string, std::string> kSiPrefixSymbols = {
{ "ATTO", "a" },
};
const std::unordered_map<std::string, double> kSiConversions = {
const std::unordered_map<std::string, double> SI_CONVERSIONS = {
{ "thou", 0.0000254 },
{ "inch", 0.0254 },
{ "foot", 0.3048 },
@@ -105,7 +105,7 @@ const std::unordered_map<std::string, double> kSiConversions = {
{ "fahrenheit", 1.8 },
};
const std::unordered_map<std::string, std::string> kImperialTypes = {
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" },
@@ -127,7 +127,7 @@ const std::unordered_map<std::string, std::string> kImperialTypes = {
{ "fahrenheit", "THERMODYNAMICTEMPERATUREUNIT" },
};
const std::unordered_map<std::string, std::string> kUnitSymbols = {
const std::unordered_map<std::string, std::string> UNIT_SYMBOLS = {
// SI base / derived
{ "CUBIC_METRE", "m3" },
{ "GRAM", "g" },
@@ -162,7 +162,7 @@ const std::unordered_map<std::string, std::string> kUnitSymbols = {
namespace {
std::string toLower(const std::string& s) {
std::string to_lower(const std::string& s) {
std::string r;
r.resize(s.size());
std::transform(s.begin(), s.end(), r.begin(),
@@ -171,7 +171,7 @@ std::string toLower(const std::string& s) {
}
// Pull an enumeration string off an attribute_value, or "" if null/invalid.
std::string enumString(const attribute_value& av) {
std::string enum_string(const attribute_value& av) {
if (av.isNull()) return {};
if (av.type() != ifcopenshell::Argument_ENUMERATION) return {};
enumeration_reference er = av;
@@ -180,13 +180,13 @@ std::string enumString(const attribute_value& av) {
} // namespace
double getPrefixMultiplier(const std::string& prefix) {
double get_prefix_multiplier(const std::string& prefix) {
if (prefix.empty()) return 1.0;
auto it = kSiPrefixes.find(prefix);
return (it == kSiPrefixes.end()) ? 1.0 : it->second;
auto it = SI_PREFIXES.find(prefix);
return (it == SI_PREFIXES.end()) ? 1.0 : it->second;
}
std::optional<double> siScaleFromNamedUnit(express::Base unit) {
std::optional<double> si_scale_from_named_unit(express::Base unit) {
double scale = 1.0;
while (unit && unit.declaration().is("IfcConversionBasedUnit")) {
auto e = unit.as<express::Entity>();
@@ -195,8 +195,8 @@ std::optional<double> siScaleFromNamedUnit(express::Base unit) {
std::string name;
auto name_attr = e.get("Name");
if (!name_attr.isNull()) name = (std::string) name_attr;
if (auto it = kSiConversions.find(toLower(name));
it != kSiConversions.end()) {
if (auto it = SI_CONVERSIONS.find(to_lower(name));
it != SI_CONVERSIONS.end()) {
return scale * it->second;
}
@@ -217,15 +217,15 @@ std::optional<double> siScaleFromNamedUnit(express::Base unit) {
if (unit && unit.declaration().is("IfcSIUnit")) {
auto e = unit.as<express::Entity>();
const std::string prefix = enumString(e.get("Prefix"));
const std::string name = enumString(e.get("Name"));
double m = getPrefixMultiplier(prefix);
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 *= getPrefixMultiplier(prefix);
m *= get_prefix_multiplier(prefix);
} else if (name.find("CUBIC") != std::string::npos) {
m *= getPrefixMultiplier(prefix);
m *= getPrefixMultiplier(prefix);
m *= get_prefix_multiplier(prefix);
m *= get_prefix_multiplier(prefix);
}
return scale * m;
}
@@ -238,7 +238,7 @@ std::optional<double> siScaleFromNamedUnit(express::Base unit) {
return scale;
}
std::optional<express::Base> getUnitAssignment(ifcopenshell::file* ifc_file) {
std::optional<express::Base> get_unit_assignment(ifcopenshell::file* ifc_file) {
auto projects = ifc_file->instances_by_type("IfcProject");
if (projects.empty()) return std::nullopt;
auto ua_attr = projects[0].as<express::Entity>().get("UnitsInContext");
@@ -246,9 +246,9 @@ std::optional<express::Base> getUnitAssignment(ifcopenshell::file* ifc_file) {
return (express::Base) ua_attr;
}
std::optional<express::Base> getProjectUnit(ifcopenshell::file* ifc_file,
const std::string& unit_type) {
auto ua = getUnitAssignment(ifc_file);
std::optional<express::Base> get_project_unit(ifcopenshell::file* ifc_file,
const std::string& unit_type) {
auto ua = get_unit_assignment(ifc_file);
if (!ua) return std::nullopt;
auto units_attr = ua->as<express::Entity>().get("Units");
if (units_attr.isNull()) return std::nullopt;
@@ -260,60 +260,60 @@ std::optional<express::Base> getProjectUnit(ifcopenshell::file* ifc_file,
continue;
}
auto ut = unit.as<express::Entity>().get("UnitType");
if (enumString(ut) == unit_type) return unit;
if (enum_string(ut) == unit_type) return unit;
}
return std::nullopt;
}
double calculateUnitScale(ifcopenshell::file* ifc_file,
const std::string& unit_type) {
auto unit = getProjectUnit(ifc_file, unit_type);
double calculate_unit_scale(ifcopenshell::file* ifc_file,
const std::string& unit_type) {
auto unit = get_project_unit(ifc_file, unit_type);
if (!unit) return 1.0;
auto scale = siScaleFromNamedUnit(*unit);
auto scale = si_scale_from_named_unit(*unit);
return scale.value_or(1.0);
}
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 = toLower(from_unit);
const std::string tl = toLower(to_unit);
const std::string fl = to_lower(from_unit);
const std::string tl = to_lower(to_unit);
if (auto it = kSiConversions.find(fl); it != kSiConversions.end()) {
if (auto it = SI_CONVERSIONS.find(fl); it != SI_CONVERSIONS.end()) {
value *= it->second;
} else if (!from_prefix.empty()) {
value *= getPrefixMultiplier(from_prefix);
value *= get_prefix_multiplier(from_prefix);
if (from_unit.find("SQUARE") != std::string::npos) {
value *= getPrefixMultiplier(from_prefix);
value *= get_prefix_multiplier(from_prefix);
} else if (from_unit.find("CUBIC") != std::string::npos) {
value *= getPrefixMultiplier(from_prefix);
value *= getPrefixMultiplier(from_prefix);
value *= get_prefix_multiplier(from_prefix);
value *= get_prefix_multiplier(from_prefix);
}
}
if (auto it = kSiConversions.find(tl); it != kSiConversions.end()) {
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 / getPrefixMultiplier(to_prefix);
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 / getPrefixMultiplier(to_prefix);
value *= 1.0 / get_prefix_multiplier(to_prefix);
} else if (from_unit.find("CUBIC") != std::string::npos) {
value *= 1.0 / getPrefixMultiplier(to_prefix);
value *= 1.0 / getPrefixMultiplier(to_prefix);
value *= 1.0 / get_prefix_multiplier(to_prefix);
value *= 1.0 / get_prefix_multiplier(to_prefix);
}
}
return value;
}
double convertUnit(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) {
auto e = u.as<express::Entity>();
if (u.declaration().is("IfcSIUnit")) {
prefix = enumString(e.get("Prefix"));
name = enumString(e.get("Name"));
prefix = enum_string(e.get("Prefix"));
name = enum_string(e.get("Name"));
} else {
// IfcConversionBasedUnit / IfcContextDependentUnit: no Prefix,
// Name is a string attribute.
+14 -14
View File
@@ -35,44 +35,44 @@ 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> kSiPrefixes;
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> kSiPrefixSymbols;
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> kSiConversions;
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> kImperialTypes;
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> kUnitSymbols;
extern const std::unordered_map<std::string, std::string> UNIT_SYMBOLS;
// Returns the multiplier for an SI prefix. Empty string returns 1.0.
double getPrefixMultiplier(const std::string& prefix);
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
// when the chain bottoms out in IfcContextDependentUnit (cannot convert).
std::optional<double> siScaleFromNamedUnit(express::Base named_unit);
std::optional<double> si_scale_from_named_unit(express::Base named_unit);
// IfcProject.UnitsInContext (the IfcUnitAssignment). Returns nullopt if
// the file has no project or no assignment.
std::optional<express::Base> getUnitAssignment(ifcopenshell::file* ifc_file);
std::optional<express::Base> get_unit_assignment(ifcopenshell::file* ifc_file);
// First unit in the project's IfcUnitAssignment matching `unit_type`
// (e.g. "LENGTHUNIT"). Returns nullopt if not found.
std::optional<express::Base> getProjectUnit(ifcopenshell::file* ifc_file,
const std::string& unit_type);
std::optional<express::Base> get_project_unit(ifcopenshell::file* ifc_file,
const std::string& unit_type);
// Project unit -> SI base scale (e.g. project in mm => 0.001). Defaults
// to 1.0 when no project unit of the requested type is set.
double calculateUnitScale(ifcopenshell::file* ifc_file,
const std::string& unit_type = "LENGTHUNIT");
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
@@ -83,7 +83,7 @@ double convert(double value,
// Convert between two IfcNamedUnit entities. Pulls Name and Prefix off each
// and delegates to convert(). IfcConversionBasedUnit names that don't appear
// in kSiConversions return the value unchanged.
double convertUnit(double value, express::Base from_unit, express::Base to_unit);
// in SI_CONVERSIONS return the value unchanged.
double convert_unit(double value, express::Base from_unit, express::Base to_unit);
#endif // UNIT_H
+10 -10
View File
@@ -18,8 +18,8 @@
********************************************************************************/
#include "Federation.h"
#include "Geolocation.h"
#include "Unit.h"
#include "geolocation.h"
#include "unit.h"
#include <QDir>
#include <QFile>
@@ -103,18 +103,18 @@ ModelGeoref computeModelGeoref(ifcopenshell::file* ifc_file) {
if (!ifc_file) return out;
out.units.project_length_to_meters =
calculateUnitScale(ifc_file, "LENGTHUNIT");
calculate_unit_scale(ifc_file, "LENGTHUNIT");
auto params = getHelmertTransformationParameters(ifc_file);
auto params = get_helmert_transformation_parameters(ifc_file);
const double scale = (params && params->scale != 0.0) ? params->scale : 1.0;
out.units.map_unit_to_meters = out.units.project_length_to_meters / scale;
if (!params) return out;
Eigen::Matrix4d helmert =
helmertMetersFromParameters(*params, out.units.map_unit_to_meters);
helmert_meters_from_parameters(*params, out.units.map_unit_to_meters);
if (auto wcs = getWcs(ifc_file)) {
// getWcs returns the WCS in project units (translation in project
if (auto wcs = get_wcs(ifc_file)) {
// get_wcs returns the WCS in project units (translation in project
// length units). Convert translation to metres before inverting.
Eigen::Matrix4d wcs_m = *wcs;
wcs_m(0, 3) *= out.units.project_length_to_meters;
@@ -147,11 +147,11 @@ guessFederatedFalseOrigin(const Eigen::Vector3d& first_geometry_point_m,
out.xyz = t_m * u_fed_inv;
// Rotation: helmert grid-north baked into coordinate_operation_meters.
// helmertMetersFromParameters built that block as R_z(theta)·diag(fx,fy,fz)
// with theta = atan2(xao, xaa); xaxis2angle is `-theta` in degrees.
// helmert_meters_from_parameters built that block as R_z(theta)·diag(fx,fy,fz)
// with theta = atan2(xao, xaa); x_axis_to_angle_deg is `-theta` in degrees.
if (use_coord_op) {
const Eigen::Matrix4d& M = georef.coordinate_operation_meters;
out.rz_deg = xaxis2angleDeg(M(0, 0), M(1, 0));
out.rz_deg = x_axis_to_angle_deg(M(0, 0), M(1, 0));
}
return out;
}
+2 -2
View File
@@ -101,7 +101,7 @@ struct ModelTransformation {
};
// Per-model unit scales captured at load time. project_length_to_meters comes
// from calculateUnitScale(file, "LENGTHUNIT"). map_unit_to_meters is derived
// from calculate_unit_scale(file, "LENGTHUNIT"). map_unit_to_meters is derived
// from IfcMapConversion.Scale as project_length_to_meters / Scale; the
// IfcProjectedCRS.MapUnit named unit is metadata and does not affect the
// transform composition.
@@ -161,7 +161,7 @@ Eigen::Matrix4d composeFederatedFalseOrigin(const FederatedFalseOrigin&,
// Compose ModelTransformation into a 4x4 matrix in metres.
// `coordinate_operation_meters` is the model's CoordinateOperation matrix
// (e.g. helmertMetersFromParameters · inv(wcs_meters)) — needed to lift
// (e.g. helmert_meters_from_parameters · inv(wcs_meters)) — needed to lift
// `a` into metres when a_frame == ModelLocal. Pass identity when the
// CoordinateOperation is disabled or absent.
Eigen::Matrix4d composeModelTransformation(const ModelTransformation&,
+3 -3
View File
@@ -158,9 +158,9 @@ target_link_libraries(test_federation PRIVATE
Qt${QT_VERSION}::Gui # Federation::HomeView uses QVector3D from QtGui
Qt${QT_VERSION}::Test # QSignalSpy
Eigen3::Eigen # Federation.h: composed matrices use Eigen
# helpers provides Unit::convert + Geolocation helpers
# (helmertMetersFromParameters, getWcs, getMapUnit) + Placement
# (getAxis2Placement, called from Geolocation::getWcs). Linking the
# helpers provides unit conversion and geolocation helpers
# (helmert_meters_from_parameters, get_wcs, get_map_unit) + Placement
# (get_axis2_placement, called from geolocation::get_wcs). Linking the
# static lib avoids re-compiling those .cpp files here and pulls
# the src/helpers include dir + IfcParse transitively.
helpers
+2 -2
View File
@@ -18,7 +18,7 @@
********************************************************************************/
#include "Federation.h"
#include "Geolocation.h"
#include "geolocation.h"
#include <catch2/catch_test_macros.hpp>
@@ -467,7 +467,7 @@ TEST_CASE("helmert metres transform consumes Scale through map unit scale",
const double project_length_to_meters = 0.001;
const double map_unit_to_meters = project_length_to_meters / params.scale;
Eigen::Matrix4d M = helmertMetersFromParameters(params, map_unit_to_meters);
Eigen::Matrix4d M = helmert_meters_from_parameters(params, map_unit_to_meters);
Eigen::Vector4d local_m(10.0, 20.0, 0.0, 1.0);
Eigen::Vector4d global_m = M * local_m;