mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 23:36:20 +00:00
ifcviewer: cache per-model georef in SceneLoader
Adds ModelGeoref { ModelUnits units; Eigen::Matrix4d stage2_meters; bool
has_stage2; } and computeModelGeoref(file*) in Federation.{h,cpp}. The
helper reads the project length unit, IfcProjectedCRS.MapUnit, helmert
parameters and WCS, and reduces them to a metres-in/metres-out stage 2
matrix using the existing Geolocation + Unit primitives. When the model
has no IfcMapConversion it returns an identity stage_2 with has_stage2
== false, so the upload pipeline can branch cheaply.
SceneLoader::Model gains a cached ModelGeoref; SceneLoader::modelGeoref
(uint32_t mid) computes lazily on first call (returns nullptr when the
IFC file isn't available yet — happens on the sidecar-hit path before
the data-source thread populates the streamer) and serves from cache
afterwards.
Not yet consumed by the upload pipeline; that's the next commit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
********************************************************************************/
|
********************************************************************************/
|
||||||
|
|
||||||
#include "Federation.h"
|
#include "Federation.h"
|
||||||
|
#include "Geolocation.h"
|
||||||
#include "Unit.h"
|
#include "Unit.h"
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
@@ -95,6 +96,45 @@ Eigen::Matrix4d composeFederationOrigin(const FederationOrigin& origin,
|
|||||||
return Rz4 * translation4(-xyz_m);
|
return Rz4 * translation4(-xyz_m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModelGeoref computeModelGeoref(ifcopenshell::file* ifc_file) {
|
||||||
|
ModelGeoref out;
|
||||||
|
if (!ifc_file) return out;
|
||||||
|
|
||||||
|
out.units.project_length_to_meters =
|
||||||
|
calculateUnitScale(ifc_file, "LENGTHUNIT");
|
||||||
|
|
||||||
|
if (auto map_unit = getMapUnit(ifc_file)) {
|
||||||
|
if (auto s = siScaleFromNamedUnit(*map_unit)) {
|
||||||
|
out.units.map_unit_to_meters = *s;
|
||||||
|
} else {
|
||||||
|
out.units.map_unit_to_meters = out.units.project_length_to_meters;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No MapUnit on the IfcProjectedCRS — fall back to project length unit.
|
||||||
|
out.units.map_unit_to_meters = out.units.project_length_to_meters;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto params = getHelmertTransformationParameters(ifc_file);
|
||||||
|
if (!params) return out;
|
||||||
|
|
||||||
|
Eigen::Matrix4d helmert =
|
||||||
|
helmertMetersFromParameters(*params, out.units.map_unit_to_meters);
|
||||||
|
|
||||||
|
if (auto wcs = getWcs(ifc_file)) {
|
||||||
|
// getWcs 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;
|
||||||
|
wcs_m(1, 3) *= out.units.project_length_to_meters;
|
||||||
|
wcs_m(2, 3) *= out.units.project_length_to_meters;
|
||||||
|
out.stage2_meters = helmert * wcs_m.inverse();
|
||||||
|
} else {
|
||||||
|
out.stage2_meters = helmert;
|
||||||
|
}
|
||||||
|
out.has_stage2 = true;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
Eigen::Matrix4d composeModelTransform(const ModelTransform& xf,
|
Eigen::Matrix4d composeModelTransform(const ModelTransform& xf,
|
||||||
const FederationConfig& fed_cfg,
|
const FederationConfig& fed_cfg,
|
||||||
const ModelUnits& model_units,
|
const ModelUnits& model_units,
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
namespace ifcopenshell { class file; }
|
||||||
|
|
||||||
// === Stage-3/4 data model ===
|
// === Stage-3/4 data model ===
|
||||||
//
|
//
|
||||||
// A federation places one or more IFC models in a shared scene. Each model's
|
// A federation places one or more IFC models in a shared scene. Each model's
|
||||||
@@ -96,6 +98,21 @@ struct ModelUnits {
|
|||||||
double map_unit_to_meters = 1.0;
|
double map_unit_to_meters = 1.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Per-model georeferencing data derived from the IFC. `stage2_meters` is
|
||||||
|
// the helmert · inv(wcs) georef matrix in metres; consumers compose it
|
||||||
|
// before stage 3 / stage 4 at upload time. When the model has no map
|
||||||
|
// conversion, `has_stage2 == false` and `stage2_meters` is identity.
|
||||||
|
struct ModelGeoref {
|
||||||
|
ModelUnits units;
|
||||||
|
Eigen::Matrix4d stage2_meters = Eigen::Matrix4d::Identity();
|
||||||
|
bool has_stage2 = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read a model's project length unit, map unit, helmert parameters, and WCS
|
||||||
|
// from `ifc_file` and reduce them to a metres-in / metres-out georef matrix.
|
||||||
|
// Pure compute; safe to call repeatedly if the caller doesn't want to cache.
|
||||||
|
ModelGeoref computeModelGeoref(ifcopenshell::file* ifc_file);
|
||||||
|
|
||||||
// 1 federation_unit -> N metres.
|
// 1 federation_unit -> N metres.
|
||||||
double federationUnitToMeters(const FederationConfig&);
|
double federationUnitToMeters(const FederationConfig&);
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,18 @@ ifcopenshell::file* SceneLoader::ifcFile(uint32_t mid) const {
|
|||||||
return it == models_.end() ? nullptr : it->second.streamer->ifcFile();
|
return it == models_.end() ? nullptr : it->second.streamer->ifcFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ModelGeoref* SceneLoader::modelGeoref(uint32_t mid) {
|
||||||
|
auto it = models_.find(mid);
|
||||||
|
if (it == models_.end()) return nullptr;
|
||||||
|
auto& m = it->second;
|
||||||
|
if (m.has_georef) return &m.georef;
|
||||||
|
auto* file = m.streamer ? m.streamer->ifcFile() : nullptr;
|
||||||
|
if (!file) return nullptr;
|
||||||
|
m.georef = computeModelGeoref(file);
|
||||||
|
m.has_georef = true;
|
||||||
|
return &m.georef;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<uint32_t> SceneLoader::addFiles(const QStringList& paths) {
|
std::vector<uint32_t> SceneLoader::addFiles(const QStringList& paths) {
|
||||||
std::vector<uint32_t> assigned;
|
std::vector<uint32_t> assigned;
|
||||||
assigned.reserve(paths.size());
|
assigned.reserve(paths.size());
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Federation.h"
|
||||||
#include "ViewportWindow.h"
|
#include "ViewportWindow.h"
|
||||||
#include "GeometryStreamer.h"
|
#include "GeometryStreamer.h"
|
||||||
#include "SidecarCache.h"
|
#include "SidecarCache.h"
|
||||||
@@ -65,6 +66,12 @@ public:
|
|||||||
QString displayName(uint32_t mid) const;
|
QString displayName(uint32_t mid) const;
|
||||||
ifcopenshell::file* ifcFile(uint32_t mid) const;
|
ifcopenshell::file* ifcFile(uint32_t mid) const;
|
||||||
|
|
||||||
|
// Lazily computes the model's georef matrix + unit scales the first
|
||||||
|
// time it's asked for, caches the result, and returns a pointer into the
|
||||||
|
// cache. Returns nullptr when the IFC file isn't available yet (e.g.
|
||||||
|
// sidecar-hit path before the data-source thread populates the streamer).
|
||||||
|
const ModelGeoref* modelGeoref(uint32_t mid);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void progressChanged(int percent);
|
void progressChanged(int percent);
|
||||||
void loadStarted(uint32_t mid, QString display_name);
|
void loadStarted(uint32_t mid, QString display_name);
|
||||||
@@ -113,6 +120,11 @@ private:
|
|||||||
QString display_name;
|
QString display_name;
|
||||||
GeometryStreamer* streamer = nullptr;
|
GeometryStreamer* streamer = nullptr;
|
||||||
QElapsedTimer load_timer;
|
QElapsedTimer load_timer;
|
||||||
|
|
||||||
|
// Cached on first SceneLoader::modelGeoref(mid) call once the
|
||||||
|
// streamer has its IFC file loaded.
|
||||||
|
ModelGeoref georef;
|
||||||
|
bool has_georef = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
void startNextLoad();
|
void startNextLoad();
|
||||||
|
|||||||
@@ -58,10 +58,13 @@ find_package(Eigen3 REQUIRED)
|
|||||||
add_executable(test_federation
|
add_executable(test_federation
|
||||||
test_federation.cpp
|
test_federation.cpp
|
||||||
${IFCVIEWER_SRC}/Federation.cpp
|
${IFCVIEWER_SRC}/Federation.cpp
|
||||||
# Federation pulls in Unit::convert for federation_unit_to_meters; compile
|
# Federation pulls in Unit::convert for federationUnitToMeters and
|
||||||
# Unit.cpp directly so the test doesn't have to link the whole IfcViewer
|
# Geolocation helpers (helmertMetersFromParameters, getWcs, getMapUnit)
|
||||||
# library (which would drag in Qt6::OpenGL, OpenCASCADE, etc.).
|
# for computeModelGeoref; compile both directly so the test doesn't
|
||||||
|
# have to link the whole IfcViewer library (which would drag in
|
||||||
|
# Qt6::OpenGL, OpenCASCADE, etc.).
|
||||||
${IFCVIEWER_SRC}/Unit.cpp
|
${IFCVIEWER_SRC}/Unit.cpp
|
||||||
|
${IFCVIEWER_SRC}/Geolocation.cpp
|
||||||
)
|
)
|
||||||
set_target_properties(test_federation PROPERTIES AUTOMOC ON)
|
set_target_properties(test_federation PROPERTIES AUTOMOC ON)
|
||||||
target_include_directories(test_federation PRIVATE ${IFCVIEWER_SRC})
|
target_include_directories(test_federation PRIVATE ${IFCVIEWER_SRC})
|
||||||
|
|||||||
Reference in New Issue
Block a user