mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 01:41:57 +00:00
ifcviewer: key sidecar on path stem, drop staleness check
Previously readSidecar/writeSidecar were keyed on (path, file_size) with staleness rejected at read time. Switch to pure path-stem keying: foo.ifc and foo.ifcdb/ both resolve to foo.ifcview, so the same cache serves either source format. Staleness is user-managed (delete the sidecar to force a rebuild), which also lets sidecars be copied or moved independently of the source. v8 header drops the source_file_size field. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -295,7 +295,7 @@ void MainWindow::writeSidecarForModel(uint32_t mid) {
|
||||
viewport_->applyLodExtension(mid, sd);
|
||||
|
||||
QElapsedTimer t; t.start();
|
||||
bool ok = writeSidecar(loader_->filePath(mid).toStdString(), sd, loader_->fileSize(mid));
|
||||
bool ok = writeSidecar(loader_->filePath(mid).toStdString(), sd);
|
||||
qDebug(" Sidecar write: %lld ms (%s)", t.elapsed(), ok ? "ok" : "FAILED");
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ engine with a Qt6 interface and OpenGL 4.5 rendering.
|
||||
| `InstancedGeometry.h` | Shared structs: `MeshInfo`, `InstanceCpu`, `InstanceGpu`, chunk records |
|
||||
| `BvhAccel.h/cpp` | Median-split BVH builder; operates on instance world-AABBs |
|
||||
| `LodBuilder.h/cpp` | Post-stream decimation of unique meshes via meshoptimizer (`simplifySloppy`) |
|
||||
| `SidecarCache.h/cpp` | Raw binary `.ifcview` (v7) sidecar read/write |
|
||||
| `SidecarCache.h/cpp` | Raw binary `.ifcview` (v8) sidecar read/write |
|
||||
| `AppSettings.h/cpp` | Persisted preferences (geometry library, stats overlay, backface culling) |
|
||||
| `SettingsWindow.h/cpp` | Settings dialog |
|
||||
| `CMakeLists.txt` | Build configuration |
|
||||
@@ -293,14 +293,13 @@ while stack not empty:
|
||||
Depth 64 is enough for billions of items on any balanced tree. The stack
|
||||
is on the C++ stack, zero per-frame allocation.
|
||||
|
||||
#### Sidecar format (`.ifcview`, v7)
|
||||
#### Sidecar format (`.ifcview`, v8)
|
||||
|
||||
Raw memory dump, Blender-`.blend`-style — no serialisation, no parsing.
|
||||
Stores everything needed to skip the `IfcGeom::Iterator` pass:
|
||||
|
||||
```
|
||||
SidecarHeader (magic "IFVW", version, endian, ...)
|
||||
uint64_t source_file_size
|
||||
uint32_t + uint8_t[] vertex data (12 B/vert quantized; per-mesh basis in MeshInfo)
|
||||
uint32_t + uint32_t[] index data (mesh-local)
|
||||
uint32_t + MeshInfo[] per-unique-mesh metadata (56 B each, incl. LOD1 slice)
|
||||
@@ -309,8 +308,10 @@ uint32_t + PackedElementInfo[] element tree records
|
||||
uint32_t + char[] string table
|
||||
```
|
||||
|
||||
Staleness check: `source_file_size` vs actual file size. Mismatched →
|
||||
reject and rebuild. Endianness marker rejects cross-arch caches.
|
||||
Sidecar path is the source stem + `.ifcview` — `foo.ifc` and `foo.ifcdb/`
|
||||
both map to `foo.ifcview`, so the same cache serves either source format.
|
||||
Staleness is user-managed: delete the sidecar to force a rebuild.
|
||||
Endianness marker rejects cross-arch caches.
|
||||
|
||||
Sidecars store the raw `object_id` / `model_id` values from the session
|
||||
that wrote them. On load they are rebased onto the current session's ID
|
||||
|
||||
@@ -54,12 +54,6 @@ QString SceneLoader::displayName(uint32_t mid) const {
|
||||
return it == models_.end() ? QString() : it->second.display_name;
|
||||
}
|
||||
|
||||
uint64_t SceneLoader::fileSize(uint32_t mid) const {
|
||||
auto it = models_.find(mid);
|
||||
if (it == models_.end()) return 0;
|
||||
return static_cast<uint64_t>(QFileInfo(it->second.file_path).size());
|
||||
}
|
||||
|
||||
ifcopenshell::file* SceneLoader::ifcFile(uint32_t mid) const {
|
||||
auto it = models_.find(mid);
|
||||
return it == models_.end() ? nullptr : it->second.streamer->ifcFile();
|
||||
@@ -124,14 +118,13 @@ void SceneLoader::startNextLoad() {
|
||||
emit loadStarted(model.id, model.display_name);
|
||||
|
||||
std::string ifc_path = model.file_path.toStdString();
|
||||
uint64_t file_size = this->fileSize(model.id);
|
||||
uint32_t mid = loading_model_id_;
|
||||
|
||||
// Sidecar read on a background thread so the UI stays responsive.
|
||||
joinSidecarThread();
|
||||
sidecar_read_thread_ = std::thread([this, ifc_path, file_size, mid]() {
|
||||
sidecar_read_thread_ = std::thread([this, ifc_path, mid]() {
|
||||
QElapsedTimer rt; rt.start();
|
||||
auto cached = readSidecar(ifc_path, file_size);
|
||||
auto cached = readSidecar(ifc_path);
|
||||
qDebug(" Sidecar read: %lld ms (%s)", rt.elapsed(), ifc_path.c_str());
|
||||
auto result = std::make_shared<std::optional<SidecarData>>(std::move(cached));
|
||||
QMetaObject::invokeMethod(this, [this, mid, result]() {
|
||||
|
||||
@@ -63,7 +63,6 @@ public:
|
||||
|
||||
QString filePath(uint32_t mid) const;
|
||||
QString displayName(uint32_t mid) const;
|
||||
uint64_t fileSize(uint32_t mid) const;
|
||||
ifcopenshell::file* ifcFile(uint32_t mid) const;
|
||||
|
||||
signals:
|
||||
|
||||
@@ -17,16 +17,12 @@
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
// v6 layout (all multi-byte fields native-endian; endianness marker in header).
|
||||
// Same sequence as v5; the only change is that vertex data is now raw bytes
|
||||
// at the 16 B/vertex quantized layout (see InstancedGeometry.h).
|
||||
//
|
||||
// v8 layout (all multi-byte fields native-endian; endianness marker in header).
|
||||
//
|
||||
// SidecarHeader (16 bytes)
|
||||
// uint64_t source_file_size
|
||||
//
|
||||
// uint32_t num_vertex_bytes
|
||||
// uint8_t[] vertex data (16 B/vertex: pos u16x3 + pad2 + oct-normal i16x2 + rgba8)
|
||||
// uint8_t[] vertex data (12 B/vertex: pos u16x3 + oct-normal i8x2 + rgba8)
|
||||
// uint32_t num_indices
|
||||
// uint32_t[] index data (mesh-local indices; base_vertex applied at draw time)
|
||||
//
|
||||
@@ -53,8 +49,20 @@ struct SidecarHeader {
|
||||
uint32_t reserved;
|
||||
};
|
||||
|
||||
// foo.ifc -> foo.ifcview
|
||||
// foo.ifcdb/ -> foo.ifcview
|
||||
// foo.ifcdb -> foo.ifcview
|
||||
// foo (no ext) -> foo.ifcview
|
||||
static std::string sidecarPath(const std::string& ifc_path) {
|
||||
return ifc_path + ".ifcview";
|
||||
std::string p = ifc_path;
|
||||
while (!p.empty() && (p.back() == '/' || p.back() == '\\')) p.pop_back();
|
||||
auto slash = p.find_last_of("/\\");
|
||||
auto dot = p.find_last_of('.');
|
||||
std::string stem = (dot != std::string::npos &&
|
||||
(slash == std::string::npos || dot > slash))
|
||||
? p.substr(0, dot)
|
||||
: p;
|
||||
return stem + ".ifcview";
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
@@ -74,16 +82,13 @@ static bool readVec(FILE* f, std::vector<T>& v) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool writeSidecar(const std::string& ifc_path,
|
||||
const SidecarData& data,
|
||||
uint64_t ifc_file_size) {
|
||||
bool writeSidecar(const std::string& ifc_path, const SidecarData& data) {
|
||||
std::string path = sidecarPath(ifc_path);
|
||||
FILE* f = fopen(path.c_str(), "wb");
|
||||
if (!f) return false;
|
||||
|
||||
SidecarHeader hdr = { SIDECAR_MAGIC, SIDECAR_VERSION, SIDECAR_ENDIAN, 0 };
|
||||
if (fwrite(&hdr, sizeof(hdr), 1, f) != 1) { fclose(f); return false; }
|
||||
if (fwrite(&ifc_file_size, 8, 1, f) != 1) { fclose(f); return false; }
|
||||
|
||||
if (!writeVec(f, data.vertices)) { fclose(f); return false; }
|
||||
if (!writeVec(f, data.indices)) { fclose(f); return false; }
|
||||
@@ -101,8 +106,7 @@ bool writeSidecar(const std::string& ifc_path,
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<SidecarData> readSidecar(const std::string& ifc_path,
|
||||
uint64_t ifc_file_size) {
|
||||
std::optional<SidecarData> readSidecar(const std::string& ifc_path) {
|
||||
std::string path = sidecarPath(ifc_path);
|
||||
FILE* f = fopen(path.c_str(), "rb");
|
||||
if (!f) return std::nullopt;
|
||||
@@ -115,10 +119,6 @@ std::optional<SidecarData> readSidecar(const std::string& ifc_path,
|
||||
hdr.version != SIDECAR_VERSION ||
|
||||
hdr.endian != SIDECAR_ENDIAN) return fail();
|
||||
|
||||
uint64_t stored_size;
|
||||
if (fread(&stored_size, 8, 1, f) != 1) return fail();
|
||||
if (stored_size != ifc_file_size) return fail();
|
||||
|
||||
SidecarData data;
|
||||
if (!readVec(f, data.vertices)) return fail();
|
||||
if (!readVec(f, data.indices)) return fail();
|
||||
|
||||
@@ -41,7 +41,11 @@ static constexpr uint32_t SIDECAR_MAGIC = 0x49465657; // "IFVW"
|
||||
// color u8x4). Dequant basis is per-mesh MeshInfo.local_aabb_min/max.
|
||||
// v7 = VBO vertices shrunk to 12 B/vertex (normal oct i8x2 replaces i16x2,
|
||||
// eliminating 2-byte pad + saving 2 bytes on normal).
|
||||
static constexpr uint32_t SIDECAR_VERSION = 7;
|
||||
// v8 = source_file_size field dropped from header. Sidecar is keyed purely
|
||||
// on path stem (foo.ifc and foo.ifcdb/ both map to foo.ifcview) so the
|
||||
// same cache serves either source format. Staleness is user-managed
|
||||
// (delete the sidecar to force a rebuild).
|
||||
static constexpr uint32_t SIDECAR_VERSION = 8;
|
||||
static constexpr uint32_t SIDECAR_ENDIAN = 0x01020304;
|
||||
|
||||
// Fixed-size element record. Strings are stored as (offset, length) pairs
|
||||
@@ -76,12 +80,11 @@ struct SidecarData {
|
||||
std::string string_table;
|
||||
};
|
||||
|
||||
// v4 writer/reader are stubbed for Commit A — no disk I/O happens.
|
||||
bool writeSidecar(const std::string& ifc_path,
|
||||
const SidecarData& data,
|
||||
uint64_t ifc_file_size);
|
||||
// Sidecar is keyed on the path stem: foo.ifc and foo.ifcdb/ both resolve to
|
||||
// foo.ifcview alongside the source. No staleness check — callers delete the
|
||||
// file to invalidate.
|
||||
bool writeSidecar(const std::string& ifc_path, const SidecarData& data);
|
||||
|
||||
std::optional<SidecarData> readSidecar(const std::string& ifc_path,
|
||||
uint64_t ifc_file_size);
|
||||
std::optional<SidecarData> readSidecar(const std::string& ifc_path);
|
||||
|
||||
#endif // SIDECARCACHE_H
|
||||
|
||||
Reference in New Issue
Block a user