/******************************************************************************** * * * This file is part of IfcOpenShell. * * * * IfcOpenShell is free software: you can redistribute it and/or modify * * it under the terms of the Lesser GNU General Public License as published by * * the Free Software Foundation, either version 3.0 of the License, or * * (at your option) any later version. * * * * IfcOpenShell is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * Lesser GNU General Public License for more details. * * * * You should have received a copy of the Lesser GNU General Public License * * along with this program. If not, see . * * * ********************************************************************************/ #ifndef SCENELOADER_H #define SCENELOADER_H #include #include #include #include #include #include #include #include #include #include #include #include "Federation.h" #include "ViewportWindow.h" #include "GeometryStreamer.h" #include "SidecarCache.h" // Drives IFC file loading into a ViewportWindow. Owns the per-model // GeometryStreamer, the load queue, the sidecar read thread, and the // next-free object_id counter used to rebase cached models onto the // current session's ID space. // // Consumers (MainWindow, MinimalWindow) observe progress through signals // and never touch the streamer, sidecar thread, or queue directly. // Sidecar *writes* are intentionally left to the consumer: they need the // consumer's element metadata (guid/name/type strings) which SceneLoader // does not retain. class SceneLoader : public QObject { Q_OBJECT public: explicit SceneLoader(ViewportWindow* viewport, QObject* parent = nullptr); ~SceneLoader(); // Returns the model_ids assigned to the enqueued paths, in order. // Callers can use these to set up per-model UI state (tree roots, etc.) // before any load signal fires. std::vector addFiles(const QStringList& paths); void cancelCurrentLoad(); bool isLoading() const { return loading_model_id_ != 0 || !load_queue_.empty(); } bool isLoadingModel(uint32_t mid) const { return loading_model_id_ == mid; } size_t modelCount() const { return models_.size(); } // Drop the loader's tracking for `mid` — its streamer, file path, georef // cache, and queue slot if still pending. Caller is responsible for the // viewport / UI cleanup; this only releases the loader's own state. // Refuses while the model is the active load (use cancelCurrentLoad first). void removeModel(uint32_t mid); QString filePath(uint32_t mid) const; QString displayName(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); // The placement_transformation (in metres, column-major 4x4) of the // first instance the loader saw for `mid` — captured from the streamer's // first InstanceChunk during a stream load, or from the cached // InstanceCpu[0] on a sidecar hit. Returns nullptr until at least one // instance has been observed. Used by the federation false-origin // auto-guess to anchor the model without re-parsing the IFC. const Eigen::Matrix4d* firstPlacement(uint32_t mid) const; signals: void progressChanged(int percent); void loadStarted(uint32_t mid, QString display_name); // Fired once per sidecar hit, before loadedFromSidecar, with the full // packed element set. Consumer is responsible for decoding + tree/ // property-map population. Moved arguments — avoid unnecessary copies. void sidecarElementsReady(uint32_t mid, std::vector elements, std::string string_table); void loadedFromSidecar(uint32_t mid, qint64 elapsed_ms); // Fired after a sidecar-hit model has its .rdb/.ifc opened as a // property data source in the background. Consumers can refresh // any UI that queries ifcFile(mid) for attributes/properties. void dataSourceReady(uint32_t mid); // Fired repeatedly while streaming, as the worker thread produces // elements. Each batch contains whatever accumulated since the last // poll tick. void streamedElementsReady(uint32_t mid, std::vector elements); // Fired once after the streamer finishes and the viewport has been // finalized. Consumer may synchronously perform work that needs all // elements to be known (e.g. sidecar write) — SceneLoader will only // start the next queued load after all slots return. void loadedFromStream(uint32_t mid, qint64 elapsed_ms); void loadCancelled(uint32_t mid); void loadError(uint32_t mid, QString message); void allLoadsFinished(); private slots: void onStreamerProgressChanged(int percent); void onStreamerMeshReady(MeshChunk chunk); void onStreamerInstanceReady(InstanceChunk chunk); void onStreamerFinished(); void onStreamerCancelled(); void onStreamerError(const QString& msg); void onElementPollTick(); private: struct Model { uint32_t id = 0; QString file_path; QString display_name; GeometryStreamer* streamer = nullptr; QElapsedTimer load_timer; // Cached on first SceneLoader::modelGeoref(mid) call once the // streamer has its IFC file loaded. ModelGeoref georef; bool has_georef = false; // The first instance's placement_transformation (in metres) — set // once per model from either the sidecar's InstanceCpu[0] or the // streamer's first InstanceChunk. Eigen::Matrix4d first_placement = Eigen::Matrix4d::Identity(); bool has_first_placement = false; }; void startNextLoad(); void connectStreamer(GeometryStreamer* streamer); void joinSidecarThread(); void joinDataSourceThreads(); void applySidecarData(uint32_t mid, SidecarData data); void startDataSourceLoad(uint32_t mid); ViewportWindow* viewport_ = nullptr; std::map models_; std::deque load_queue_; uint32_t next_model_id_ = 1; uint32_t next_object_id_ = 1; uint32_t loading_model_id_ = 0; std::thread sidecar_read_thread_; // One thread per sidecar-hit model while its .rdb/.ifc opens in the // background. Joined only at destruction so a slow SPF parse on model // A never blocks the sidecar-hit path of model B. std::vector data_source_threads_; QTimer element_poll_timer_; }; #endif // SCENELOADER_H