mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 01:41:57 +00:00
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:
@@ -274,3 +274,40 @@ Eigen::Matrix4d autoLocal2Global(ifcopenshell::file* ifc_file,
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Eigen::Matrix4d helmertMetersFromParameters(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> getMapUnit(ifcopenshell::file* ifc_file) {
|
||||
std::vector<express::Base> coordops;
|
||||
try {
|
||||
coordops = ifc_file->instances_by_type("IfcCoordinateOperation");
|
||||
} catch (...) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (coordops.empty()) return std::nullopt;
|
||||
auto target_attr = coordops[0].as<express::Entity>().get("TargetCRS");
|
||||
if (target_attr.isNull()) return std::nullopt;
|
||||
express::Base target = target_attr;
|
||||
if (!target.declaration().is("IfcProjectedCRS")) return std::nullopt;
|
||||
auto mu_attr = target.as<express::Entity>().get("MapUnit");
|
||||
if (mu_attr.isNull()) return std::nullopt;
|
||||
return (express::Base) mu_attr;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user