mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-28 15:53:00 +00:00
Unify sidecar production via SidecarBuilder
Renamed HeadlessSidecarBuilder to SidecarBuilder and reused it for live loads. SceneLoader now constructs one per stream load, forwards meshReady /instanceReady chunks alongside the viewport upload, and finalizes + writes the sidecar at onStreamerFinished — no more GPU readback path via ViewportWindow::snapshotModel (removed). Same code path now produces sidecars for both live loads and the .rdbview offline export. Sidecar use is opt-in per direction via SceneLoader::setShouldReadSidecar and setShouldWriteSidecar; both default off so embedders that don't want caching get a pure-streaming loader. ifcviewer-full and ifcviewer-minimal opt in. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -470,8 +470,6 @@ void MainWindow::setupLoader() {
|
||||
session_state_->createLoader(viewport_widget_->viewport());
|
||||
viewport_view_ = new modules::viewport::ViewportView(
|
||||
session_state_, viewport_widget_->viewport(), this);
|
||||
modules::models::commands::addHandlers(
|
||||
*session_state_, *viewport_widget_->viewport(), *this);
|
||||
|
||||
// Load errors surface through SessionState as a session-level signal; the
|
||||
// status text + progress are already cleared there, we only show the modal.
|
||||
|
||||
@@ -36,6 +36,8 @@ SessionState::SessionState(QObject* parent)
|
||||
void SessionState::createLoader(ViewportWindow* viewport) {
|
||||
Q_ASSERT(!loader_);
|
||||
loader_ = new SceneLoader(viewport, this);
|
||||
loader_->setShouldReadSidecar(true);
|
||||
loader_->setShouldWriteSidecar(true);
|
||||
element_registry_->bindLoader(loader_);
|
||||
|
||||
auto format_elapsed = [](qint64 ms) {
|
||||
@@ -69,7 +71,6 @@ void SessionState::createLoader(ViewportWindow* viewport) {
|
||||
.arg(format_elapsed(elapsed_ms)));
|
||||
endProgress();
|
||||
emit modelGeometryReady(mid);
|
||||
emit modelGeometryStreamed(mid);
|
||||
});
|
||||
connect(loader_, &SceneLoader::loadCancelled, this, [this](uint32_t mid) {
|
||||
setStatusMessage("Cancelled", loader_->displayName(mid));
|
||||
|
||||
@@ -90,10 +90,6 @@ signals:
|
||||
// for both sidecar-cache and stream loads; subscribers that just need to
|
||||
// re-derive view state (e.g. ViewportView::refresh) listen to this.
|
||||
void modelGeometryReady(uint32_t model_id);
|
||||
// Fires only after a stream load — i.e. when no sidecar cache existed
|
||||
// yet. The sidecar-write subscriber listens to this so it doesn't
|
||||
// re-persist a cache that was just read from disk.
|
||||
void modelGeometryStreamed(uint32_t model_id);
|
||||
// Fires when SceneLoader reports a load failure. SessionState turns the
|
||||
// raw loader signal into a session-level one so views (e.g. the MessageBox)
|
||||
// can subscribe without touching the loader directly.
|
||||
|
||||
@@ -26,10 +26,8 @@
|
||||
#include "../../ElementRegistry.h"
|
||||
#include "../../SessionState.h"
|
||||
#include "../../../ifcviewer/Federation.h"
|
||||
#include "../../../ifcviewer/HeadlessSidecarBuilder.h"
|
||||
#include "../../../ifcviewer/LodBuilder.h"
|
||||
#include "../../../ifcviewer/SceneLoader.h"
|
||||
#include "../../../ifcviewer/SidecarCache.h"
|
||||
#include "../../../ifcviewer/SidecarBuilder.h"
|
||||
#include "../../../ifcviewer/ViewportWindow.h"
|
||||
#include "../../../ifcgeom/Serializer.h"
|
||||
#include "../../../serializers/document_serializer_plugin.h"
|
||||
@@ -173,11 +171,6 @@ void removeModel(SessionState& s, ViewportWindow& vp, QWidget& host, const QStri
|
||||
s.setStatusMessage("Models", "Model removed");
|
||||
}
|
||||
|
||||
void addHandlers(SessionState& s, ViewportWindow& vp, QObject& context) {
|
||||
QObject::connect(&s, &SessionState::modelGeometryStreamed, &context,
|
||||
[&s, &vp](uint32_t mid) { writeSidecarForLoadedModel(s, vp, mid); });
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
void loadModels(SessionState& s, const QStringList& paths, const QStringList& fed_ids) {
|
||||
@@ -432,7 +425,7 @@ void exportGeometryDatabase(SessionState& s, QWidget& host) {
|
||||
serializer->finalize();
|
||||
serializer.reset();
|
||||
|
||||
HeadlessSidecarBuilder builder;
|
||||
SidecarBuilder builder;
|
||||
if (!builder.build(input_path, tmp_anchor)) {
|
||||
throw ifcopenshell::exception(
|
||||
("Sidecar build failed: " + builder.lastError()).toStdString());
|
||||
@@ -531,64 +524,4 @@ void openSettings(SessionState& s, QWidget& host) {
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void writeSidecarForLoadedModel(SessionState& s, ViewportWindow& vp, uint32_t mid) {
|
||||
SceneLoader* loader = s.loader();
|
||||
if (!loader) return;
|
||||
|
||||
SidecarData sidecar_data;
|
||||
if (!vp.snapshotModel(mid, sidecar_data)) return;
|
||||
|
||||
if (const ModelGeoref* georef = loader->modelGeoref(mid)) {
|
||||
sidecar_data.has_coordinate_operation = georef->has_coordinate_operation ? 1 : 0;
|
||||
Eigen::Map<Eigen::Matrix<double, 4, 4, Eigen::ColMajor>>(
|
||||
sidecar_data.coordinate_operation_meters) = georef->coordinate_operation_meters;
|
||||
sidecar_data.project_length_to_meters = georef->units.project_length_to_meters;
|
||||
sidecar_data.map_unit_to_meters = georef->units.map_unit_to_meters;
|
||||
}
|
||||
|
||||
if (auto* element_registry = s.elementRegistry()) {
|
||||
for (const auto& info : element_registry->basicElementInfoForModel(mid)) {
|
||||
PackedElementInfo packed;
|
||||
packed.object_id = info.object_id;
|
||||
packed.model_id = info.model_id;
|
||||
packed.ifc_id = info.ifc_id;
|
||||
packed.parent_id = info.parent_id;
|
||||
|
||||
const std::string guid = info.guid.toStdString();
|
||||
packed.guid_offset = static_cast<uint32_t>(sidecar_data.string_table.size());
|
||||
packed.guid_length = static_cast<uint32_t>(guid.size());
|
||||
sidecar_data.string_table += guid;
|
||||
|
||||
const std::string name = info.name.toStdString();
|
||||
packed.name_offset = static_cast<uint32_t>(sidecar_data.string_table.size());
|
||||
packed.name_length = static_cast<uint32_t>(name.size());
|
||||
sidecar_data.string_table += name;
|
||||
|
||||
const std::string type = info.type.toStdString();
|
||||
packed.type_offset = static_cast<uint32_t>(sidecar_data.string_table.size());
|
||||
packed.type_length = static_cast<uint32_t>(type.size());
|
||||
sidecar_data.string_table += type;
|
||||
|
||||
sidecar_data.elements.push_back(packed);
|
||||
}
|
||||
}
|
||||
|
||||
QElapsedTimer lod_timer;
|
||||
lod_timer.start();
|
||||
buildLods(sidecar_data);
|
||||
const LodStats lod_stats = summariseLods(sidecar_data);
|
||||
qDebug(" LOD build: %lld ms — %u/%u meshes got LOD1 "
|
||||
"(%u tris -> %u tris for those meshes)",
|
||||
lod_timer.elapsed(),
|
||||
lod_stats.meshes_with_lod1, lod_stats.meshes_total,
|
||||
lod_stats.tris_lod0_for_lod1, lod_stats.tris_lod1);
|
||||
|
||||
vp.applyLodExtension(mid, sidecar_data);
|
||||
|
||||
QElapsedTimer sidecar_timer;
|
||||
sidecar_timer.start();
|
||||
const bool ok = writeSidecar(loader->filePath(mid).toStdString(), sidecar_data);
|
||||
qDebug(" Sidecar write: %lld ms (%s)", sidecar_timer.elapsed(), ok ? "ok" : "FAILED");
|
||||
}
|
||||
|
||||
} // namespace ifcviewerfull::modules::models::commands
|
||||
|
||||
@@ -47,17 +47,6 @@ void convertIfcToDatabase(SessionState& s, QWidget& host);
|
||||
void exportGeometryDatabase(SessionState& s, QWidget& host);
|
||||
void openSettings(SessionState& s, QWidget& host);
|
||||
|
||||
// Snapshots the in-memory geometry + element registry for a freshly streamed
|
||||
// model and persists it as a sidecar next to the source IFC. Called after
|
||||
// SceneLoader::loadedFromStream so subsequent loads can skip the stream phase.
|
||||
void writeSidecarForLoadedModel(SessionState& s, ViewportWindow& vp, uint32_t mid);
|
||||
|
||||
// Persistent post-load handler. Call once at app startup. Whenever the
|
||||
// session reports a model was streamed (not loaded from cache), persists a
|
||||
// sidecar so the next load skips the stream phase. Connection lifetime is
|
||||
// tied to `context`.
|
||||
void addHandlers(SessionState& s, ViewportWindow& vp, QObject& context);
|
||||
|
||||
// Internal building blocks shared by commands here and by ProjectController.
|
||||
// These NEVER call notify*() — the caller is responsible for emitting once
|
||||
// at the end of its execution.
|
||||
|
||||
Reference in New Issue
Block a user