ifcviewer: add helmertMetersFromParameters and getMapUnit

helmertMetersFromParameters builds the helmert transformation as a
meter-input/meter-output 4x4 directly from parsed parameters, bypassing
autoLocal2Global's normalisation step.  This preserves
IfcMapConversionScaled.FactorX/Y/Z in the rotation block so the factor
applies to placement translations when the matrix is precomputed
per-model and composed with placements at upload time.  For ordinary
IfcMapConversion (factor = 1) this is bit-identical to
autoLocal2Global; only diverges on rare surveyed models with non-unit
factors, where it is the only correct behaviour.

getMapUnit returns IfcCoordinateOperation.TargetCRS.MapUnit so callers
can resolve the unit-to-metres scale via Unit.h's siScaleFromNamedUnit.

autoLocal2Global is unchanged — kept as a clean port of the python
ifcopenshell.util.geolocation reference impl for one-shot
project-units-in / map-units-out callers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-05-01 12:43:27 +10:00
parent 0d3849737c
commit b3d29c4081
2 changed files with 67 additions and 0 deletions
+30
View File
@@ -27,6 +27,8 @@
#ifndef GEOLOCATION_H
#define GEOLOCATION_H
#include "../ifcparse/express.h"
#include <Eigen/Dense>
#include <optional>
@@ -70,4 +72,32 @@ Eigen::Matrix4d autoLocal2Global(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
// 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 the SI scale of IfcProjectedCRS.MapUnit (or the
// project's LENGTHUNIT scale if MapUnit is absent). The caller composes any
// IfcGeometricRepresentationContext WCS on the right:
// G = helmertMetersFromParameters(...) · inv(wcs_meters)
// (where wcs_meters has its translation column converted from project units
// to meters via calculateUnitScale).
//
// Unlike autoLocal2Global, 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);
// IfcCoordinateOperation.TargetCRS.MapUnit (the IfcNamedUnit), if present.
// Returns nullopt for IFC2X3, models without an IfcCoordinateOperation, or
// when MapUnit is absent on the IfcProjectedCRS. Callers fall back to
// calculateUnitScale(file, "LENGTHUNIT") in that case.
std::optional<express::Base> getMapUnit(ifcopenshell::file* ifc_file);
#endif // GEOLOCATION_H