From 8ab5c31e75b704c8269424a4065c9ea39dcb88c7 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 4 Jun 2026 11:11:14 +1000 Subject: [PATCH] refactor: merge ifcviewer-wgpu into ifcviewer, drop Wgpu prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The GL backend is gone (task #53). The wgpu/non-wgpu folder split and the Wgpu* class prefix were both disambiguation artefacts from the overlap period — now pure dead weight. ## Folder + library merge * `src/ifcviewer-wgpu/` → folded into `src/ifcviewer/` (git mv tracks every file as a rename so blame/log history survives). * `src/ifcviewer-wgpu-minimal/` → `src/ifcviewer-minimal/` (the exe was already named `IfcViewerMinimal`; this just brings the folder + CMake target name into line). * `src/ifcviewer-wgpu/tests/test_wgpu_{selection,visibility}.cpp` → `src/ifcviewer/tests/test_{selection,visibility}.cpp`, folded into the existing `add_ifcviewer_unit_test(...)` helper. * The `IfcViewerWgpu` static library is dissolved — its sources become part of the unified `IfcViewer` static library, which now bundles scene/loader + renderer in one target. The pre-merge circular dependency (IfcViewer linking IfcViewerWgpu just to get the ViewportWindow.h include path that SceneLoader.h needs) goes away. * The wgpu-native FetchContent block, the Cocoa/QuartzCore link on Apple, the OBJCXX-enabled `.mm` source, and the wgpu-native runtime install all move into `src/ifcviewer/CMakeLists.txt` unchanged. ## Type renames (Wgpu prefix dropped from every Wgpu* identifier) WgpuAreaMeasurement → AreaMeasurement WgpuBufferPool → BufferPool WgpuLengthMeasurement → LengthMeasurement WgpuMetalSurface → MetalSurface WgpuModelGpuData → ModelGpuData WgpuOverlayFrame → OverlayFrame WgpuOverlayRenderer → OverlayRenderer WgpuSectionPlane → SectionPlane WgpuSelectionState → SelectionState WgpuStreamingLoader → StreamingLoader WgpuStreamingThread → StreamingThread WgpuViewportWindow → ViewportWindow WgpuVisibilityState → VisibilityState CMake target IfcViewerWgpuMinimal → IfcViewerMinimal (exe name was already this since wgpu shipped as default). Deliberately kept: `onWgpuLog` (wgpu-native log callback — names a binding to an external API, not one of *our* types), and the WGPU* enum/struct prefixes from wgpu-native's own headers. `WgpuMemProbe` lives in the separate `src/wgpu-mem-probe/` standalone diagnostic project and isn't touched. ## Include-path updates Every `#include "../ifcviewer-wgpu/Wgpu.h"` → `"../ifcviewer/.h"`, every in-directory `#include "Wgpu.h"` → `".h"`. Includes from sibling subdirectories (modules/, etc.) are updated to point at `../../../ifcviewer/` instead of `../../../ifcviewer-wgpu/`. ## cmake/CMakeLists.txt simplification The redundant `add_subdirectory(ifcviewer-wgpu)` blocks (one inside the BUILD_BONSAIVIEWER fan-in, one in the BONSAIVIEWER-less standalone block) collapse into a single unconditional `add_subdirectory(../src/ifcviewer ifcviewer)`. The standalone block keeps only `wgpu-mem-probe` (the diagnostic tool, unrelated to the viewer lib). ## Verification * Full build green: `IfcViewer` static lib, `IfcViewerMinimal` exe, `BonsaiViewer` exe, all four pre-existing ifcviewer unit tests, and the two new-location tests (`test_selection`, `test_visibility`). * No stray `Wgpu` identifier remains across `src/ifcviewer/`, `src/bonsaiviewer/`, `src/ifcviewer-minimal/` (verified by grep). * Renames tracked by git as `R` entries — `git log --follow` on ViewportWindow.cpp etc. continues to show history through the move. Co-Authored-By: Claude Opus 4.7 --- cmake/CMakeLists.txt | 20 +- src/bonsaiviewer/CMakeLists.txt | 7 +- src/bonsaiviewer/MainWindow.cpp | 8 +- src/bonsaiviewer/Measurement.cpp | 66 ++-- src/bonsaiviewer/Measurement.h | 30 +- src/bonsaiviewer/SessionState.cpp | 2 +- src/bonsaiviewer/SessionState.h | 4 +- src/bonsaiviewer/modules/models/Commands.cpp | 4 +- src/bonsaiviewer/modules/models/Commands.h | 4 +- src/bonsaiviewer/modules/models/Panel.cpp | 2 +- src/bonsaiviewer/modules/models/Panel.h | 6 +- src/bonsaiviewer/modules/project/Commands.cpp | 24 +- src/bonsaiviewer/modules/project/Commands.h | 12 +- .../modules/viewport/Commands.cpp | 28 +- src/bonsaiviewer/modules/viewport/Commands.h | 28 +- src/bonsaiviewer/modules/viewport/Panel.cpp | 4 +- src/bonsaiviewer/modules/viewport/Panel.h | 6 +- src/bonsaiviewer/modules/viewport/View.cpp | 40 +- src/bonsaiviewer/modules/viewport/View.h | 6 +- .../CMakeLists.txt | 20 +- .../main.cpp | 6 +- src/ifcviewer-wgpu/CMakeLists.txt | 194 ---------- src/ifcviewer-wgpu/tests/CMakeLists.txt | 35 -- .../AreaMeasurement.cpp} | 38 +- .../AreaMeasurement.h} | 22 +- .../BufferPool.cpp} | 20 +- .../BufferPool.h} | 10 +- src/ifcviewer/CMakeLists.txt | 139 ++++++- .../LengthMeasurement.cpp} | 42 +-- .../LengthMeasurement.h} | 18 +- .../MetalSurface_mac.h} | 4 +- .../MetalSurface_mac.mm} | 4 +- .../ModelGpuData.h} | 16 +- .../OverlayRenderer.cpp} | 74 ++-- .../OverlayRenderer.h} | 34 +- src/ifcviewer/SceneLoader.cpp | 2 +- src/ifcviewer/SceneLoader.h | 10 +- .../SelectionState.h} | 2 +- .../StreamingLoader.cpp} | 2 +- .../StreamingLoader.h} | 0 .../StreamingThread.cpp} | 20 +- .../StreamingThread.h} | 6 +- .../ViewportWindow.cpp} | 348 +++++++++--------- .../ViewportWindow.h} | 82 ++--- .../VisibilityState.h} | 2 +- src/ifcviewer/tests/CMakeLists.txt | 5 + .../tests/test_selection.cpp} | 40 +- .../tests/test_visibility.cpp} | 24 +- 48 files changed, 705 insertions(+), 815 deletions(-) rename src/{ifcviewer-wgpu-minimal => ifcviewer-minimal}/CMakeLists.txt (80%) rename src/{ifcviewer-wgpu-minimal => ifcviewer-minimal}/main.cpp (97%) delete mode 100644 src/ifcviewer-wgpu/CMakeLists.txt delete mode 100644 src/ifcviewer-wgpu/tests/CMakeLists.txt rename src/{ifcviewer-wgpu/WgpuAreaMeasurement.cpp => ifcviewer/AreaMeasurement.cpp} (94%) rename src/{ifcviewer-wgpu/WgpuAreaMeasurement.h => ifcviewer/AreaMeasurement.h} (87%) rename src/{ifcviewer-wgpu/WgpuBufferPool.cpp => ifcviewer/BufferPool.cpp} (94%) rename src/{ifcviewer-wgpu/WgpuBufferPool.h => ifcviewer/BufferPool.h} (97%) rename src/{ifcviewer-wgpu/WgpuLengthMeasurement.cpp => ifcviewer/LengthMeasurement.cpp} (96%) rename src/{ifcviewer-wgpu/WgpuLengthMeasurement.h => ifcviewer/LengthMeasurement.h} (89%) rename src/{ifcviewer-wgpu/WgpuMetalSurface_mac.h => ifcviewer/MetalSurface_mac.h} (86%) rename src/{ifcviewer-wgpu/WgpuMetalSurface_mac.mm => ifcviewer/MetalSurface_mac.mm} (94%) rename src/{ifcviewer-wgpu/WgpuModelGpuData.h => ifcviewer/ModelGpuData.h} (97%) rename src/{ifcviewer-wgpu/WgpuOverlayRenderer.cpp => ifcviewer/OverlayRenderer.cpp} (97%) rename src/{ifcviewer-wgpu/WgpuOverlayRenderer.h => ifcviewer/OverlayRenderer.h} (94%) rename src/{ifcviewer-wgpu/WgpuSelectionState.h => ifcviewer/SelectionState.h} (99%) rename src/{ifcviewer-wgpu/WgpuStreamingLoader.cpp => ifcviewer/StreamingLoader.cpp} (99%) rename src/{ifcviewer-wgpu/WgpuStreamingLoader.h => ifcviewer/StreamingLoader.h} (100%) rename src/{ifcviewer-wgpu/WgpuStreamingThread.cpp => ifcviewer/StreamingThread.cpp} (89%) rename src/{ifcviewer-wgpu/WgpuStreamingThread.h => ifcviewer/StreamingThread.h} (97%) rename src/{ifcviewer-wgpu/WgpuViewportWindow.cpp => ifcviewer/ViewportWindow.cpp} (97%) rename src/{ifcviewer-wgpu/WgpuViewportWindow.h => ifcviewer/ViewportWindow.h} (96%) rename src/{ifcviewer-wgpu/WgpuVisibilityState.h => ifcviewer/VisibilityState.h} (98%) rename src/{ifcviewer-wgpu/tests/test_wgpu_selection.cpp => ifcviewer/tests/test_selection.cpp} (91%) rename src/{ifcviewer-wgpu/tests/test_wgpu_visibility.cpp => ifcviewer/tests/test_visibility.cpp} (88%) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 23d11cb2ce..1ccb4931e9 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -80,7 +80,7 @@ option(BUILD_BONSAIVIEWER_WGPU "Build the experimental wgpu backend (fetches wgp # whenever BUILD_BONSAIVIEWER is on so the link target exists. if(BUILD_BONSAIVIEWER AND NOT BUILD_BONSAIVIEWER_WGPU) message(STATUS "BUILD_BONSAIVIEWER implies BUILD_BONSAIVIEWER_WGPU " - "(SceneLoader uses WgpuViewportWindow); auto-enabling.") + "(SceneLoader uses ViewportWindow); auto-enabling.") set(BUILD_BONSAIVIEWER_WGPU ON) endif() option(BUILD_PACKAGE "" OFF) @@ -700,22 +700,18 @@ if(BUILD_BONSAIVIEWER_TESTS) endif() if(BUILD_BONSAIVIEWER) - # Wgpu subprojects added first because IfcViewer's SceneLoader now - # links against IfcViewerWgpu; the target must exist when IfcViewer's - # CMakeLists runs. - if(BUILD_BONSAIVIEWER_WGPU) - add_subdirectory(../src/ifcviewer-wgpu ifcviewer-wgpu) - add_subdirectory(../src/ifcviewer-wgpu-minimal ifcviewer-wgpu-minimal) - endif() + # IfcViewer is the unified scene + render lib since the wgpu/ifcviewer + # merge — wgpu-native is fetched inside its CMakeLists.txt. add_subdirectory(../src/ifcviewer ifcviewer) + if(BUILD_BONSAIVIEWER_WGPU) + add_subdirectory(../src/ifcviewer-minimal ifcviewer-minimal) + endif() add_subdirectory(../src/bonsaiviewer bonsaiviewer) endif() -# Standalone wgpu build (no GL viewer / no bonsai) still supported for -# fast iteration on the port. +# Standalone wgpu diagnostic tool (no bonsai) — fast iteration on +# wgpu-native probing without the full bonsai build. if(BUILD_BONSAIVIEWER_WGPU AND NOT BUILD_BONSAIVIEWER) - add_subdirectory(../src/ifcviewer-wgpu ifcviewer-wgpu) - add_subdirectory(../src/ifcviewer-wgpu-minimal ifcviewer-wgpu-minimal) add_subdirectory(../src/wgpu-mem-probe wgpu-mem-probe) endif() diff --git a/src/bonsaiviewer/CMakeLists.txt b/src/bonsaiviewer/CMakeLists.txt index 401b6455f5..7a19ea418f 100644 --- a/src/bonsaiviewer/CMakeLists.txt +++ b/src/bonsaiviewer/CMakeLists.txt @@ -134,11 +134,10 @@ if(APPLE) endif() target_link_libraries(BonsaiViewer PRIVATE - # IfcViewer still ships SceneLoader / Federation / GeometryStreamer / - # SidecarBuilder until the GL ViewportWindow is deleted. IfcViewerWgpu - # ships the live render path. Both link cleanly side-by-side. + # IfcViewer is the unified scene + render lib (SceneLoader, Federation, + # GeometryStreamer, SidecarBuilder, ViewportWindow, OverlayRenderer, + # BufferPool, …) since the wgpu/ifcviewer merge. IfcViewer - IfcViewerWgpu Qt${QT_VERSION}::Core Qt${QT_VERSION}::CorePrivate Qt${QT_VERSION}::Gui diff --git a/src/bonsaiviewer/MainWindow.cpp b/src/bonsaiviewer/MainWindow.cpp index 8c5458ff2b..5cfb1d0d45 100644 --- a/src/bonsaiviewer/MainWindow.cpp +++ b/src/bonsaiviewer/MainWindow.cpp @@ -22,7 +22,7 @@ #include "../ifcviewer/AppSettings.h" #include "../ifcviewer/Federation.h" -#include "../ifcviewer-wgpu/WgpuViewportWindow.h" +#include "../ifcviewer/ViewportWindow.h" #include "SessionState.h" #include "components/Buttons.h" #include "components/Panel.h" @@ -529,8 +529,8 @@ void MainWindow::setupLoader() { QMessageBox::warning(this, "Bonsai Viewer", message); }); - connect(viewport_widget_->viewport(), &WgpuViewportWindow::frameStatsUpdated, this, - [this](const WgpuViewportWindow::FrameStats& s) { + connect(viewport_widget_->viewport(), &ViewportWindow::frameStatsUpdated, this, + [this](const ViewportWindow::FrameStats& s) { if (!status_perf_label_->isVisible()) return; status_perf_label_->setText( QString("%1 fps | %2 ms | %3/%4 obj | %5/%6 tri | %7 draws") @@ -542,7 +542,7 @@ void MainWindow::setupLoader() { .arg(s.total_triangles) .arg(s.gl_draw_calls)); }); - connect(viewport_widget_->viewport(), &WgpuViewportWindow::objectPicked, + connect(viewport_widget_->viewport(), &ViewportWindow::objectPicked, this, [this](uint32_t object_id) { session_state_->setSelectedObjectId(object_id); session_state_->notifySelectionChanged(); diff --git a/src/bonsaiviewer/Measurement.cpp b/src/bonsaiviewer/Measurement.cpp index 8cf495b2e7..104689f497 100644 --- a/src/bonsaiviewer/Measurement.cpp +++ b/src/bonsaiviewer/Measurement.cpp @@ -20,7 +20,7 @@ #include "Measurement.h" -#include "WgpuViewportWindow.h" +#include "ViewportWindow.h" #include #include @@ -35,7 +35,7 @@ namespace { -double meshLocalVolume(const WgpuViewportWindow::MeshTriangles& tris) { +double meshLocalVolume(const ViewportWindow::MeshTriangles& tris) { // Signed tetrahedra from the origin: V = sum( a · (b × c) ) / 6. // Absolute value at the end so winding convention doesn't matter. double sum = 0.0; @@ -67,7 +67,7 @@ double det3(const double M[16]) { } // namespace -double volumeOfObjects(WgpuViewportWindow& vp, +double volumeOfObjects(ViewportWindow& vp, const std::vector& object_ids) { if (object_ids.empty()) return 0.0; @@ -77,14 +77,14 @@ double volumeOfObjects(WgpuViewportWindow& vp, std::unordered_map> by_mesh; by_mesh.reserve(object_ids.size()); for (uint32_t oid : object_ids) { - WgpuViewportWindow::InstanceLookup lk; + ViewportWindow::InstanceLookup lk; if (!vp.findInstance(oid, lk)) continue; const uint64_t key = (uint64_t(lk.model_id) << 32) | lk.mesh_id; by_mesh[key].push_back(std::abs(det3(lk.placement_transformation))); } double total = 0.0; - WgpuViewportWindow::MeshTriangles tris; + ViewportWindow::MeshTriangles tris; for (const auto& [key, dets] : by_mesh) { const uint32_t model_id = uint32_t(key >> 32); const uint32_t mesh_id = uint32_t(key & 0xffffffffu); @@ -96,7 +96,7 @@ double volumeOfObjects(WgpuViewportWindow& vp, } std::vector> -volumesPerObject(WgpuViewportWindow& vp, +volumesPerObject(ViewportWindow& vp, const std::vector& object_ids) { std::vector> out; if (object_ids.empty()) return out; @@ -108,9 +108,9 @@ volumesPerObject(WgpuViewportWindow& vp, std::unordered_map mesh_vol_local; mesh_vol_local.reserve(object_ids.size()); - WgpuViewportWindow::MeshTriangles tris; + ViewportWindow::MeshTriangles tris; for (uint32_t oid : object_ids) { - WgpuViewportWindow::InstanceLookup lk; + ViewportWindow::InstanceLookup lk; if (!vp.findInstance(oid, lk)) continue; const uint64_t key = (uint64_t(lk.model_id) << 32) | lk.mesh_id; @@ -238,7 +238,7 @@ constexpr double kCoplanarDot = 0.9999; // ~0.81° tolerance AreaMeasurement::AreaMeasurement() = default; -void AreaMeasurement::clear(WgpuViewportWindow& vp) { +void AreaMeasurement::clear(ViewportWindow& vp) { mesh_cache_.clear(); selected_.clear(); total_area_m2_ = 0.0; @@ -246,7 +246,7 @@ void AreaMeasurement::clear(WgpuViewportWindow& vp) { vp.setOverlayLabels({}); } -void AreaMeasurement::rebuildHighlight(WgpuViewportWindow& vp) { +void AreaMeasurement::rebuildHighlight(ViewportWindow& vp) { // Push every selected triangle's three world-space vertices to the // overlay. Mesh-local positions × per-instance composed transform. std::vector world_xyz; @@ -287,7 +287,7 @@ void AreaMeasurement::rebuildHighlight(WgpuViewportWindow& vp) { by_object[object_id].push_back(&sel); } - std::vector labels; + std::vector labels; for (const auto& [obj_id, sels] : by_object) { if (sels.empty()) continue; // All tris belonging to one object share its mesh + transform. @@ -350,7 +350,7 @@ void AreaMeasurement::rebuildHighlight(WgpuViewportWindow& vp) { // Centroid → world via the instance's composed transform. const float* M = any.composed_transform; - WgpuOverlayRenderer::Label lbl; + OverlayRenderer::Label lbl; lbl.world_pos[0] = float(M[0]*cx + M[4]*cy + M[8]*cz + M[12]); lbl.world_pos[1] = float(M[1]*cx + M[5]*cy + M[9]*cz + M[13]); lbl.world_pos[2] = float(M[2]*cx + M[6]*cy + M[10]*cz + M[14]); @@ -361,14 +361,14 @@ void AreaMeasurement::rebuildHighlight(WgpuViewportWindow& vp) { vp.setOverlayLabels(labels); } -AreaMeasurement::MeshCache* AreaMeasurement::meshCache(WgpuViewportWindow& vp, +AreaMeasurement::MeshCache* AreaMeasurement::meshCache(ViewportWindow& vp, uint32_t model_id, uint32_t mesh_id) { const uint64_t key = (uint64_t(model_id) << 32) | uint64_t(mesh_id); auto it = mesh_cache_.find(key); if (it != mesh_cache_.end()) return &it->second; - WgpuViewportWindow::MeshTriangles tris; + ViewportWindow::MeshTriangles tris; if (!vp.readbackMeshTriangles(model_id, mesh_id, tris)) return nullptr; MeshCache c; @@ -397,8 +397,8 @@ AreaMeasurement::MeshCache* AreaMeasurement::meshCache(WgpuViewportWindow& vp, return &mesh_cache_.emplace(key, std::move(c)).first->second; } -void AreaMeasurement::onPick(WgpuViewportWindow& vp, int x, int y, bool alt) { - WgpuViewportWindow::MeshLocalPick pick; +void AreaMeasurement::onPick(ViewportWindow& vp, int x, int y, bool alt) { + ViewportWindow::MeshLocalPick pick; if (!vp.pickMeshLocalAt(x, y, pick)) return; MeshCache* cache = meshCache(vp, pick.model_id, pick.mesh_id); @@ -607,10 +607,10 @@ constexpr float DOT_HALO = 1.0f; constexpr float DASH_PERIOD = 9.0f; // px constexpr float DASH_ON_RATIO = 0.55f; // 5 on, 4 off -WgpuOverlayRenderer::LineGroup makeGroup(std::vector xyz, +OverlayRenderer::LineGroup makeGroup(std::vector xyz, float r, float g, float b, bool dashed = false) { - WgpuOverlayRenderer::LineGroup gp; + OverlayRenderer::LineGroup gp; gp.world_xyz = std::move(xyz); gp.color[0] = r; gp.color[1] = g; gp.color[2] = b; gp.color[3] = 1.0f; gp.stroke_color[0] = 0.0f; gp.stroke_color[1] = 0.0f; @@ -635,10 +635,10 @@ void pushSeg(std::vector& xyz, xyz.insert(xyz.end(), b.begin(), b.end()); } -WgpuOverlayRenderer::Label makeLabel(const std::array& a, +OverlayRenderer::Label makeLabel(const std::array& a, const std::array& b, const QString& text) { - WgpuOverlayRenderer::Label lbl; + OverlayRenderer::Label lbl; lbl.world_pos[0] = 0.5f * (a[0] + b[0]); lbl.world_pos[1] = 0.5f * (a[1] + b[1]); lbl.world_pos[2] = 0.5f * (a[2] + b[2]); @@ -646,7 +646,7 @@ WgpuOverlayRenderer::Label makeLabel(const std::array& a, return lbl; } -void pushDots(WgpuViewportWindow& vp, const std::vector& xyz) { +void pushDots(ViewportWindow& vp, const std::vector& xyz) { vp.setOverlayPoints(xyz, /*inner*/ 1.0f, 1.0f, 1.0f, 1.0f, /*size*/ DOT_SIZE, @@ -656,7 +656,7 @@ void pushDots(WgpuViewportWindow& vp, const std::vector& xyz) { } // namespace -void LengthMeasurement::clear(WgpuViewportWindow& vp) { +void LengthMeasurement::clear(ViewportWindow& vp) { points_.clear(); normals_.clear(); vp.setOverlayPoints({}, 0,0,0,0, 0, 0,0,0,0, 0); @@ -665,8 +665,8 @@ void LengthMeasurement::clear(WgpuViewportWindow& vp) { vp.setHudText(QString()); } -void LengthMeasurement::onPick(WgpuViewportWindow& vp, int x, int y, bool /*alt*/) { - WgpuViewportWindow::MeshLocalPick pick; +void LengthMeasurement::onPick(ViewportWindow& vp, int x, int y, bool /*alt*/) { + ViewportWindow::MeshLocalPick pick; if (!vp.pickMeshLocalAt(x, y, pick)) return; points_.push_back({pick.world_pos[0], pick.world_pos[1], pick.world_pos[2]}); normals_.push_back({pick.world_normal[0], pick.world_normal[1], pick.world_normal[2]}); @@ -676,14 +676,14 @@ void LengthMeasurement::onPick(WgpuViewportWindow& vp, int x, int y, bool /*alt* rebuildOverlay(vp); } -void LengthMeasurement::removeLastPoint(WgpuViewportWindow& vp) { +void LengthMeasurement::removeLastPoint(ViewportWindow& vp) { if (points_.empty()) return; points_.pop_back(); if (!normals_.empty()) normals_.pop_back(); rebuildOverlay(vp); } -void LengthMeasurement::rebuildOverlay(WgpuViewportWindow& vp) { +void LengthMeasurement::rebuildOverlay(ViewportWindow& vp) { if (points_.size() == 1 && normals_.size() == 1) { rebuildLaserOverlay(vp); return; @@ -694,8 +694,8 @@ void LengthMeasurement::rebuildOverlay(WgpuViewportWindow& vp) { for (const auto& p : points_) pushDot(pts_xyz, p); pushDots(vp, pts_xyz); - std::vector groups; - std::vector labels; + std::vector groups; + std::vector labels; const size_t n = points_.size(); if (n == 2) { @@ -823,7 +823,7 @@ const char* dominantAxisLabel(const float v[3]) { } // namespace -void LengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) { +void LengthMeasurement::rebuildLaserOverlay(ViewportWindow& vp) { const auto& wp = first_pick_.world_pos; // float[3] world click const auto& n = first_pick_.world_normal; // float[3] world normal @@ -855,8 +855,8 @@ void LengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) { n[0]*t1[1] - n[1]*t1[0], }; - std::vector groups; - std::vector labels; + std::vector groups; + std::vector labels; QStringList hud_lines; hud_lines << QStringLiteral("Laser measure (click another point for distance)"); double enh[3] = {0.0, 0.0, 0.0}; @@ -873,7 +873,7 @@ void LengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) { // co-normal neighbours, then project each patch vertex into the // (t1, t2) basis to get the bounding extent of the face. Stops // exactly at the face edge (no overshoot into adjacent geometry). - WgpuViewportWindow::MeshTriangles tris; + ViewportWindow::MeshTriangles tris; bool have_extent = false; double min_t1 = 0.0, max_t1 = 0.0, min_t2 = 0.0, max_t2 = 0.0; if (vp.readbackMeshTriangles(first_pick_.model_id, first_pick_.mesh_id, tris)) { @@ -1008,7 +1008,7 @@ void LengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) { wp[1] + NUDGE * n[1], wp[2] + NUDGE * n[2], }; - WgpuViewportWindow::RaycastHit hit; + ViewportWindow::RaycastHit hit; if (vp.raycast(ro, n, hit)) { const double dist = double(hit.distance) + double(NUDGE); const std::array a = {wp[0], wp[1], wp[2]}; diff --git a/src/bonsaiviewer/Measurement.h b/src/bonsaiviewer/Measurement.h index e535a5b3c6..83f2dbfba5 100644 --- a/src/bonsaiviewer/Measurement.h +++ b/src/bonsaiviewer/Measurement.h @@ -22,7 +22,7 @@ #define IFCINTERFACE_MEASUREMENT_H #include -#include "WgpuViewportWindow.h" +#include "ViewportWindow.h" #include #include #include @@ -36,7 +36,7 @@ // taken as the absolute value of the signed-tetrahedra sum, so winding // convention does not matter. Returns 0.0 for empty input or when nothing // resolves. Recomputes from scratch on every call — no cache. -double volumeOfObjects(WgpuViewportWindow& vp, +double volumeOfObjects(ViewportWindow& vp, const std::vector& object_ids); // Per-object volumes (m³). Same algorithm as volumeOfObjects but @@ -45,11 +45,11 @@ double volumeOfObjects(WgpuViewportWindow& vp, // Used by MainWindow's volume readout to drive both the total HUD and // the per-object overlay labels. std::vector> -volumesPerObject(WgpuViewportWindow& vp, +volumesPerObject(ViewportWindow& vp, const std::vector& object_ids); // Click-to-accumulate area measurement. Each pick resolves the screen -// click to a (instance, triangle) using WgpuViewportWindow's primitives, +// click to a (instance, triangle) using ViewportWindow's primitives, // expands it into the connected coplanar patch (BFS over shared edges, // dot(normal, seed_normal) > 0.9999), then either adds or removes that // patch from the running set depending on whether the seed triangle was @@ -58,7 +58,7 @@ volumesPerObject(WgpuViewportWindow& vp, // separate patches and their areas are summed. // // On every pick the world-space triangles of the running set are pushed -// to WgpuViewportWindow::setHighlightTriangles for in-viewport shading. +// to ViewportWindow::setHighlightTriangles for in-viewport shading. // State is cleared on construction, on clear(), and is expected to be // reset by the host (e.g. when the viewport's area tool toggles off). class AreaMeasurement { @@ -68,11 +68,11 @@ public: // Main entry point: handle one click in area-tool mode. alt = true // suppresses BFS expansion. Logs the per-click delta and running total // via qInfo. Misses are silent. - void onPick(WgpuViewportWindow& vp, int x, int y, bool alt); + void onPick(ViewportWindow& vp, int x, int y, bool alt); // Wipe all accumulated triangles, per-mesh adjacency caches, and the // viewport overlay. - void clear(WgpuViewportWindow& vp); + void clear(ViewportWindow& vp); double totalArea() const { return total_area_m2_; } size_t triangleCount() const { return selected_.size(); } @@ -89,7 +89,7 @@ private: // edge_key (min<<32 | max) → list of triangle indices touching it. std::unordered_map> edges; }; - MeshCache* meshCache(WgpuViewportWindow& vp, uint32_t model_id, uint32_t mesh_id); + MeshCache* meshCache(ViewportWindow& vp, uint32_t model_id, uint32_t mesh_id); // Per-selected-triangle record. The composed transform is captured at // pick time so the overlay rebuild doesn't have to re-query the @@ -109,7 +109,7 @@ private: return (uint64_t(object_id) << 32) | uint64_t(tri); } - void rebuildHighlight(WgpuViewportWindow& vp); + void rebuildHighlight(ViewportWindow& vp); std::unordered_map mesh_cache_; std::unordered_map selected_; @@ -138,15 +138,15 @@ class LengthMeasurement { public: LengthMeasurement(); - void onPick(WgpuViewportWindow& vp, int x, int y, bool alt); - void removeLastPoint(WgpuViewportWindow& vp); - void clear(WgpuViewportWindow& vp); + void onPick(ViewportWindow& vp, int x, int y, bool alt); + void removeLastPoint(ViewportWindow& vp); + void clear(ViewportWindow& vp); size_t pointCount() const { return points_.size(); } private: - void rebuildOverlay(WgpuViewportWindow& vp); - void rebuildLaserOverlay(WgpuViewportWindow& vp); + void rebuildOverlay(ViewportWindow& vp); + void rebuildLaserOverlay(ViewportWindow& vp); QString formatReadout() const; std::vector> points_; @@ -156,7 +156,7 @@ private: // updated afterwards. Used by the 1-pt laser BFS to re-locate the // mesh-local position of points_[0] without re-picking. Stays valid // while points_[0] does (pop_back never touches the first element). - WgpuViewportWindow::MeshLocalPick first_pick_{}; + ViewportWindow::MeshLocalPick first_pick_{}; }; #endif // IFCINTERFACE_MEASUREMENT_H diff --git a/src/bonsaiviewer/SessionState.cpp b/src/bonsaiviewer/SessionState.cpp index eb41396ec9..91189e9680 100644 --- a/src/bonsaiviewer/SessionState.cpp +++ b/src/bonsaiviewer/SessionState.cpp @@ -42,7 +42,7 @@ SessionState::SessionState(QObject* parent) this, &SessionState::notifyFederationChanged); } -void SessionState::createLoader(WgpuViewportWindow* viewport) { +void SessionState::createLoader(ViewportWindow* viewport) { Q_ASSERT(!loader_); loader_ = new SceneLoader(viewport, this); loader_->setShouldReadSidecar(true); diff --git a/src/bonsaiviewer/SessionState.h b/src/bonsaiviewer/SessionState.h index 1e48ef451a..902a933ccb 100644 --- a/src/bonsaiviewer/SessionState.h +++ b/src/bonsaiviewer/SessionState.h @@ -28,7 +28,7 @@ class Federation; class SceneLoader; -class WgpuViewportWindow; +class ViewportWindow; namespace bonsaiviewer { @@ -44,7 +44,7 @@ public: // Owned by SessionState once it can be tied to a viewport. Wires // loader → element registry signals internally. Call exactly once. - void createLoader(WgpuViewportWindow* viewport); + void createLoader(ViewportWindow* viewport); Federation* federation() const { return federation_; } SceneLoader* loader() const { return loader_; } diff --git a/src/bonsaiviewer/modules/models/Commands.cpp b/src/bonsaiviewer/modules/models/Commands.cpp index 6496c1b200..3736d68293 100644 --- a/src/bonsaiviewer/modules/models/Commands.cpp +++ b/src/bonsaiviewer/modules/models/Commands.cpp @@ -31,7 +31,7 @@ #include "../../../ifcviewer/Federation.h" #include "../../../ifcviewer/SceneLoader.h" #include "../../../ifcviewer/SidecarBuilder.h" -#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h" +#include "../../../ifcviewer/ViewportWindow.h" #include "../../../ifcgeom/Serializer.h" #include "../../../serializers/document_serializer_plugin.h" @@ -166,7 +166,7 @@ void removeGroup(SessionState& s, QWidget& host, const QString& group_id) { s.setStatusMessage("Models", "Group removed"); } -void removeModel(SessionState& s, WgpuViewportWindow& vp, QWidget& host, const QString& fed_id) { +void removeModel(SessionState& s, ViewportWindow& vp, QWidget& host, const QString& fed_id) { const Federation::Model* model = s.federation()->findById(fed_id); const QString label = model ? model->display_name : fed_id; const auto choice = QMessageBox::question( diff --git a/src/bonsaiviewer/modules/models/Commands.h b/src/bonsaiviewer/modules/models/Commands.h index 42988d9f89..702e70eb82 100644 --- a/src/bonsaiviewer/modules/models/Commands.h +++ b/src/bonsaiviewer/modules/models/Commands.h @@ -28,7 +28,7 @@ #include class QWidget; -class WgpuViewportWindow; +class ViewportWindow; namespace bonsaiviewer { class SessionState; } namespace bonsaiviewer::modules::models { @@ -58,7 +58,7 @@ void renameGroup(SessionState& s, QWidget& host, const QString& group_id); void moveGroup(SessionState& s, const QString& id, const QString& parent_group_id); void moveModels(SessionState& s, const QStringList& ids, const QString& parent_group_id); void removeGroup(SessionState& s, QWidget& host, const QString& group_id); -void removeModel(SessionState& s, WgpuViewportWindow& vp, QWidget& host, const QString& fed_id); +void removeModel(SessionState& s, ViewportWindow& vp, QWidget& host, const QString& fed_id); void addModel(SessionState& s, QWidget& host); // Connector picker → pull_models_interactive → addCloudModel + load. // Reachable from AddModelDialog's CloudModel button; the underlying call diff --git a/src/bonsaiviewer/modules/models/Panel.cpp b/src/bonsaiviewer/modules/models/Panel.cpp index 6f1ad67b59..ebda8d2a93 100644 --- a/src/bonsaiviewer/modules/models/Panel.cpp +++ b/src/bonsaiviewer/modules/models/Panel.cpp @@ -220,7 +220,7 @@ private: } // namespace ModelsPanel::ModelsPanel(bonsaiviewer::SessionState* session_state, - WgpuViewportWindow* viewport, + ViewportWindow* viewport, QWidget* parent) : components::Panel("Models", nullptr, parent, true) , session_state_(session_state) diff --git a/src/bonsaiviewer/modules/models/Panel.h b/src/bonsaiviewer/modules/models/Panel.h index 5ff3675113..dadea2ec98 100644 --- a/src/bonsaiviewer/modules/models/Panel.h +++ b/src/bonsaiviewer/modules/models/Panel.h @@ -26,7 +26,7 @@ #include "../../components/Panel.h" class QTreeView; -class WgpuViewportWindow; +class ViewportWindow; namespace bonsaiviewer { class SessionState; } namespace bonsaiviewer::modules::models { @@ -41,7 +41,7 @@ class ModelsPanel : public components::Panel { Q_OBJECT public: explicit ModelsPanel(bonsaiviewer::SessionState* session_state, - WgpuViewportWindow* viewport, + ViewportWindow* viewport, QWidget* parent = nullptr); // Owned externally (the View constructs and owns the model). The panel @@ -52,7 +52,7 @@ private: void applyColumnLayout(); bonsaiviewer::SessionState* session_state_ = nullptr; - WgpuViewportWindow* viewport_ = nullptr; + ViewportWindow* viewport_ = nullptr; QTreeView* tree_ = nullptr; FederationItemModel* model_ = nullptr; }; diff --git a/src/bonsaiviewer/modules/project/Commands.cpp b/src/bonsaiviewer/modules/project/Commands.cpp index 972f9a9303..f0e626c7f2 100644 --- a/src/bonsaiviewer/modules/project/Commands.cpp +++ b/src/bonsaiviewer/modules/project/Commands.cpp @@ -30,7 +30,7 @@ #include "../models/Commands.h" #include "../../../ifcviewer/Federation.h" #include "../../../ifcviewer/SceneLoader.h" -#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h" +#include "../../../ifcviewer/ViewportWindow.h" #include #include @@ -53,7 +53,7 @@ namespace { // Pure helper — clears the loaded scene without emitting any signals. The // caller (newProject / openProject) emits projectReset / projectOpened once // the whole flow finishes. -void clearScene(SessionState& s, WgpuViewportWindow& vp) { +void clearScene(SessionState& s, ViewportWindow& vp) { vp.setSelectedObjectId(0); s.setSelectedObjectId(0); for (uint32_t mid : s.modelIds()) { @@ -88,7 +88,7 @@ bool confirmDiscardIfDirty(SessionState& s, QWidget& host) { // - if no scene entry exists yet (initial open), queue a load. // Per spec, connector errors are not surfaced to the user; the connector // has already shown its own UI. -void resolveCloudModels(SessionState& s, WgpuViewportWindow& vp) { +void resolveCloudModels(SessionState& s, ViewportWindow& vp) { auto* fed = s.federation(); QHash connector_to_fed_ids; for (const auto& m : fed->models()) { @@ -99,7 +99,7 @@ void resolveCloudModels(SessionState& s, WgpuViewportWindow& vp) { auto* registry = s.connectorRegistry(); QPointer sguard(&s); - QPointer vguard(&vp); + QPointer vguard(&vp); for (auto it = connector_to_fed_ids.constBegin(); it != connector_to_fed_ids.constEnd(); ++it) { @@ -203,7 +203,7 @@ bool isIfcfedUnchanged(const QString& current_path, const QString& candidate_pat return a.readAll() == b.readAll(); } -bool openProjectAt(SessionState& s, QWidget& host, WgpuViewportWindow& vp, const QString& path) { +bool openProjectAt(SessionState& s, QWidget& host, ViewportWindow& vp, const QString& path) { SceneLoader* loader = s.loader(); if (loader && loader->isLoading()) { QMessageBox::information( @@ -268,7 +268,7 @@ bool saveProjectTo(SessionState& s, QWidget& host, const QString& path) { } // namespace -bool newProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) { +bool newProject(SessionState& s, QWidget& host, ViewportWindow& vp) { SceneLoader* loader = s.loader(); if (loader && loader->isLoading()) { QMessageBox::information( @@ -285,7 +285,7 @@ bool newProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) { return true; } -bool openProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) { +bool openProject(SessionState& s, QWidget& host, ViewportWindow& vp) { QFileDialog file_dialog(&host, "Open Project"); file_dialog.setFileMode(QFileDialog::ExistingFile); file_dialog.setNameFilter("IFC Federation (*.ifcfed);;All Files (*)"); @@ -297,12 +297,12 @@ bool openProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) { return openProjectAt(s, host, vp, path); } -bool openProjectPath(SessionState& s, QWidget& host, WgpuViewportWindow& vp, const QString& path) { +bool openProjectPath(SessionState& s, QWidget& host, ViewportWindow& vp, const QString& path) { if (path.isEmpty()) return false; return openProjectAt(s, host, vp, path); } -bool openCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) { +bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) { SceneLoader* loader = s.loader(); if (loader && loader->isLoading()) { QMessageBox::information( @@ -341,7 +341,7 @@ bool openCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) { QPointer sguard(&s); QPointer hguard(&host); - QPointer vguard(&vp); + QPointer vguard(&vp); proc->call("pull_ifcfed_interactive", QJsonValue(), [sguard, hguard, vguard, connector_id](const QJsonValue& result) { @@ -371,7 +371,7 @@ bool openCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) { return true; } -bool syncCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) { +bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) { auto* fed = s.federation(); // Per spec, sync has two independent phases — refreshing the .ifcfed @@ -429,7 +429,7 @@ bool syncCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) { QPointer sguard(&s); QPointer hguard(&host); - QPointer vguard(&vp); + QPointer vguard(&vp); const QString current_path = fed->filePath(); proc->call("pull_ifcfed", fed->manifest(), diff --git a/src/bonsaiviewer/modules/project/Commands.h b/src/bonsaiviewer/modules/project/Commands.h index ebc3d2c9f3..12dccdc057 100644 --- a/src/bonsaiviewer/modules/project/Commands.h +++ b/src/bonsaiviewer/modules/project/Commands.h @@ -24,7 +24,7 @@ #include class QWidget; -class WgpuViewportWindow; +class ViewportWindow; namespace bonsaiviewer { class SessionState; } namespace bonsaiviewer::modules::project::commands { @@ -32,21 +32,21 @@ namespace bonsaiviewer::modules::project::commands { // User-facing commands. Each owns its own dialogs and confirmations; each // emits exactly one notify() at the end (projectReset / projectOpened / // projectSaved) so views refresh once per command. -bool newProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp); -bool openProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp); +bool newProject(SessionState& s, QWidget& host, ViewportWindow& vp); +bool openProject(SessionState& s, QWidget& host, ViewportWindow& vp); // Open a specific .ifcfed by path, bypassing the file dialog. Used by the // "Open Recent" menu. Same dirty-check / load / cloud-resolve flow as // openProject; returns false if the load failed or was cancelled. -bool openProjectPath(SessionState& s, QWidget& host, WgpuViewportWindow& vp, const QString& path); +bool openProjectPath(SessionState& s, QWidget& host, ViewportWindow& vp, const QString& path); // Pick a connector, then call pull_ifcfed_interactive and open the resulting // .ifcfed as a fresh project. Non-local models in the loaded federation are // resolved asynchronously via pull_models. -bool openCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp); +bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp); // pull_ifcfed using the current project's .ifcfed.manifest. Re-downloads // the .ifcfed from the same cloud target it came from (typically without // user interaction), then opens it like a fresh project — discarding any // local edits after the usual dirty-check prompt. -bool syncCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp); +bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp); bool saveProject(SessionState& s, QWidget& host); bool saveProjectAs(SessionState& s, QWidget& host); // Push the current federation to the cloud target named in its manifest diff --git a/src/bonsaiviewer/modules/viewport/Commands.cpp b/src/bonsaiviewer/modules/viewport/Commands.cpp index 7464acd7db..fd8fa30dd5 100644 --- a/src/bonsaiviewer/modules/viewport/Commands.cpp +++ b/src/bonsaiviewer/modules/viewport/Commands.cpp @@ -22,11 +22,11 @@ #include "../../SessionState.h" #include "../../../ifcviewer/Federation.h" -#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h" +#include "../../../ifcviewer/ViewportWindow.h" namespace bonsaiviewer::modules::viewport::commands { -void setHome(SessionState& session, WgpuViewportWindow& vp) { +void setHome(SessionState& session, ViewportWindow& vp) { auto camera = vp.cameraState(); Federation::HomeView home_view; home_view.target = camera.target; @@ -37,7 +37,7 @@ void setHome(SessionState& session, WgpuViewportWindow& vp) { session.setStatusMessage("Camera", "Home view updated"); } -void goHome(SessionState& session, WgpuViewportWindow& vp) { +void goHome(SessionState& session, ViewportWindow& vp) { Federation* federation = session.federation(); if (!federation->hasHomeView()) { session.setStatusMessage("Camera", "No home view set for this project"); @@ -50,52 +50,52 @@ void goHome(SessionState& session, WgpuViewportWindow& vp) { session.setStatusMessage("Camera", "Home view restored"); } -void viewSelected(WgpuViewportWindow& vp) { +void viewSelected(ViewportWindow& vp) { vp.focusOnSelectedObject(); } -void fly(SessionState& session, WgpuViewportWindow& vp) { +void fly(SessionState& session, ViewportWindow& vp) { vp.requestActivate(); vp.enterFpsMode(); session.setStatusMessage("Mode", "Fly mode active"); } -void toggleSection(SessionState& session, WgpuViewportWindow& vp) { +void toggleSection(SessionState& session, ViewportWindow& vp) { vp.toggleSectionTool(); session.setStatusMessage("Section", vp.sectionToolActive() ? "Section tool active" : "Section tool off"); } -void clearSection(SessionState& session, WgpuViewportWindow& vp) { +void clearSection(SessionState& session, ViewportWindow& vp) { vp.clearSectionPlanes(); session.setStatusMessage("Section", "Section planes cleared"); } -void toggleDistance(WgpuViewportWindow& vp) { +void toggleDistance(ViewportWindow& vp) { vp.toggleLengthTool(); } -void toggleArea(WgpuViewportWindow& vp) { +void toggleArea(ViewportWindow& vp) { vp.toggleAreaTool(); } -void toggleVolume(WgpuViewportWindow& vp) { +void toggleVolume(ViewportWindow& vp) { vp.toggleVolumeTool(); } -void hideSelected(WgpuViewportWindow& vp) { +void hideSelected(ViewportWindow& vp) { vp.hideSelectedElements(); } -void isolateSelected(WgpuViewportWindow& vp) { +void isolateSelected(ViewportWindow& vp) { vp.isolateSelectedElements(); } -void showAll(WgpuViewportWindow& vp) { +void showAll(ViewportWindow& vp) { vp.showAllElements(); } -void invertVisibility(WgpuViewportWindow& vp) { +void invertVisibility(ViewportWindow& vp) { vp.invertElementVisibility(); } diff --git a/src/bonsaiviewer/modules/viewport/Commands.h b/src/bonsaiviewer/modules/viewport/Commands.h index 8f8e25f048..0233873faa 100644 --- a/src/bonsaiviewer/modules/viewport/Commands.h +++ b/src/bonsaiviewer/modules/viewport/Commands.h @@ -21,27 +21,27 @@ #ifndef IFCINTERFACE_MODULES_VIEWPORT_COMMANDS_H #define IFCINTERFACE_MODULES_VIEWPORT_COMMANDS_H -class WgpuViewportWindow; +class ViewportWindow; namespace bonsaiviewer { class SessionState; } namespace bonsaiviewer::modules::viewport::commands { -void setHome(SessionState& session, WgpuViewportWindow& vp); -void goHome(SessionState& session, WgpuViewportWindow& vp); -void viewSelected(WgpuViewportWindow& vp); +void setHome(SessionState& session, ViewportWindow& vp); +void goHome(SessionState& session, ViewportWindow& vp); +void viewSelected(ViewportWindow& vp); -void fly(SessionState& session, WgpuViewportWindow& vp); -void toggleSection(SessionState& session, WgpuViewportWindow& vp); -void clearSection(SessionState& session, WgpuViewportWindow& vp); +void fly(SessionState& session, ViewportWindow& vp); +void toggleSection(SessionState& session, ViewportWindow& vp); +void clearSection(SessionState& session, ViewportWindow& vp); -void toggleDistance(WgpuViewportWindow& vp); -void toggleArea(WgpuViewportWindow& vp); -void toggleVolume(WgpuViewportWindow& vp); +void toggleDistance(ViewportWindow& vp); +void toggleArea(ViewportWindow& vp); +void toggleVolume(ViewportWindow& vp); -void hideSelected(WgpuViewportWindow& vp); -void isolateSelected(WgpuViewportWindow& vp); -void showAll(WgpuViewportWindow& vp); -void invertVisibility(WgpuViewportWindow& vp); +void hideSelected(ViewportWindow& vp); +void isolateSelected(ViewportWindow& vp); +void showAll(ViewportWindow& vp); +void invertVisibility(ViewportWindow& vp); } // namespace bonsaiviewer::modules::viewport::commands diff --git a/src/bonsaiviewer/modules/viewport/Panel.cpp b/src/bonsaiviewer/modules/viewport/Panel.cpp index 1aa5a978ff..ca6c694028 100644 --- a/src/bonsaiviewer/modules/viewport/Panel.cpp +++ b/src/bonsaiviewer/modules/viewport/Panel.cpp @@ -20,7 +20,7 @@ #include "Panel.h" -#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h" +#include "../../../ifcviewer/ViewportWindow.h" #include #include @@ -47,7 +47,7 @@ ViewportPanel::ViewportPanel(QWidget* parent) frame_layout->setContentsMargins(0, 0, 0, 0); frame_layout->setSpacing(0); - viewport_ = new WgpuViewportWindow(); + viewport_ = new ViewportWindow(); viewport_container_ = QWidget::createWindowContainer(viewport_, frame); viewport_container_->setMinimumSize(400, 300); viewport_container_->setFocusPolicy(Qt::StrongFocus); diff --git a/src/bonsaiviewer/modules/viewport/Panel.h b/src/bonsaiviewer/modules/viewport/Panel.h index c2bb1d97bb..6d6db34080 100644 --- a/src/bonsaiviewer/modules/viewport/Panel.h +++ b/src/bonsaiviewer/modules/viewport/Panel.h @@ -23,7 +23,7 @@ #include -class WgpuViewportWindow; +class ViewportWindow; namespace bonsaiviewer::modules::viewport { @@ -33,10 +33,10 @@ class ViewportPanel : public QWidget { public: explicit ViewportPanel(QWidget* parent = nullptr); - WgpuViewportWindow* viewport() const { return viewport_; } + ViewportWindow* viewport() const { return viewport_; } private: - WgpuViewportWindow* viewport_ = nullptr; + ViewportWindow* viewport_ = nullptr; QWidget* viewport_container_ = nullptr; }; diff --git a/src/bonsaiviewer/modules/viewport/View.cpp b/src/bonsaiviewer/modules/viewport/View.cpp index 8d5cbc4d00..0c40cf6c33 100644 --- a/src/bonsaiviewer/modules/viewport/View.cpp +++ b/src/bonsaiviewer/modules/viewport/View.cpp @@ -25,8 +25,8 @@ #include "../models/Commands.h" #include "../../../ifcviewer/Federation.h" #include "../../../ifcviewer/SceneLoader.h" -#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h" -#include "../../../ifcviewer-wgpu/WgpuOverlayRenderer.h" +#include "../../../ifcviewer/ViewportWindow.h" +#include "../../../ifcviewer/OverlayRenderer.h" #include "../../Measurement.h" #include @@ -37,7 +37,7 @@ namespace bonsaiviewer::modules::viewport { ViewportView::ViewportView(bonsaiviewer::SessionState* session_state, - WgpuViewportWindow* viewport, + ViewportWindow* viewport, QObject* parent) : QObject(parent) , session_state_(session_state) @@ -73,54 +73,54 @@ ViewportView::ViewportView(bonsaiviewer::SessionState* session_state, }); // Measurement tools — input-driven, share the View's lifetime. - connect(viewport_, &WgpuViewportWindow::surfacePickedInTool, this, + connect(viewport_, &ViewportWindow::surfacePickedInTool, this, [this](int x, int y, int modifiers) { const bool alt = (modifiers & Qt::AltModifier) != 0; switch (viewport_->toolMode()) { - case WgpuViewportWindow::ToolMode::Area: + case ViewportWindow::ToolMode::Area: area_measurement_->onPick(*viewport_, x, y, alt); viewport_->setHudText(QString("Area: %1 m² (%2 tris)") .arg(area_measurement_->totalArea(), 0, 'f', 4) .arg(area_measurement_->triangleCount())); break; - case WgpuViewportWindow::ToolMode::Length: + case ViewportWindow::ToolMode::Length: length_measurement_->onPick(*viewport_, x, y, alt); break; - case WgpuViewportWindow::ToolMode::NoTool: - case WgpuViewportWindow::ToolMode::Volume: + case ViewportWindow::ToolMode::NoTool: + case ViewportWindow::ToolMode::Volume: break; } }); - connect(viewport_, &WgpuViewportWindow::toolModeChanged, this, - [this](WgpuViewportWindow::ToolMode mode) { + connect(viewport_, &ViewportWindow::toolModeChanged, this, + [this](ViewportWindow::ToolMode mode) { area_measurement_->clear(*viewport_); length_measurement_->clear(*viewport_); switch (mode) { - case WgpuViewportWindow::ToolMode::NoTool: + case ViewportWindow::ToolMode::NoTool: viewport_->setHudText(QString()); viewport_->setOverlayLabels({}); session_state_->setStatusMessage("Measure", "Measurement tool off"); break; - case WgpuViewportWindow::ToolMode::Length: + case ViewportWindow::ToolMode::Length: viewport_->setHudText("Length tool: click first point"); session_state_->setStatusMessage("Measure", "Length tool: LMB add point, Backspace remove last, Esc exits"); break; - case WgpuViewportWindow::ToolMode::Area: + case ViewportWindow::ToolMode::Area: viewport_->setHudText("Area: 0.0000 m² (0 tris)"); session_state_->setStatusMessage("Measure", "Area tool: LMB add, Alt+LMB single tri, click again to remove, Esc exits"); break; - case WgpuViewportWindow::ToolMode::Volume: + case ViewportWindow::ToolMode::Volume: session_state_->setStatusMessage("Measure", "Volume tool: click / box-select objects, Esc exits"); updateVolumeReadout(); break; } }); - connect(viewport_, &WgpuViewportWindow::toolBackspacePressed, this, [this]() { - if (viewport_->toolMode() == WgpuViewportWindow::ToolMode::Length) { + connect(viewport_, &ViewportWindow::toolBackspacePressed, this, [this]() { + if (viewport_->toolMode() == ViewportWindow::ToolMode::Length) { length_measurement_->removeLastPoint(*viewport_); } }); - connect(viewport_, &WgpuViewportWindow::objectPicked, this, [this](uint32_t) { + connect(viewport_, &ViewportWindow::objectPicked, this, [this](uint32_t) { updateVolumeReadout(); }); @@ -238,7 +238,7 @@ void ViewportView::guessFederatedFalseOriginFromFirstModel(uint32_t mid) { } void ViewportView::updateVolumeReadout() { - if (viewport_->toolMode() != WgpuViewportWindow::ToolMode::Volume) return; + if (viewport_->toolMode() != ViewportWindow::ToolMode::Volume) return; const auto& sel = viewport_->selection().selectionIds(); if (sel.empty()) { @@ -251,13 +251,13 @@ void ViewportView::updateVolumeReadout() { const auto per_obj = volumesPerObject(*viewport_, ids); double total = 0.0; - std::vector labels; + std::vector labels; labels.reserve(per_obj.size()); for (const auto& [oid, v] : per_obj) { total += v; QVector3D mn, mx; if (!viewport_->computeObjectAabb(oid, mn, mx)) continue; - WgpuOverlayRenderer::Label lbl; + OverlayRenderer::Label lbl; const QVector3D c = (mn + mx) * 0.5f; lbl.world_pos[0] = c.x(); lbl.world_pos[1] = c.y(); diff --git a/src/bonsaiviewer/modules/viewport/View.h b/src/bonsaiviewer/modules/viewport/View.h index f8fca48670..56e356afc4 100644 --- a/src/bonsaiviewer/modules/viewport/View.h +++ b/src/bonsaiviewer/modules/viewport/View.h @@ -25,7 +25,7 @@ #include namespace bonsaiviewer { class SessionState; } -class WgpuViewportWindow; +class ViewportWindow; class AreaMeasurement; class LengthMeasurement; @@ -44,7 +44,7 @@ class ViewportView : public QObject { public: explicit ViewportView(bonsaiviewer::SessionState* session_state, - WgpuViewportWindow* viewport, + ViewportWindow* viewport, QObject* parent = nullptr); ~ViewportView() override; @@ -57,7 +57,7 @@ private: void updateVolumeReadout(); bonsaiviewer::SessionState* session_state_ = nullptr; - WgpuViewportWindow* viewport_ = nullptr; + ViewportWindow* viewport_ = nullptr; std::unique_ptr area_measurement_; std::unique_ptr length_measurement_; }; diff --git a/src/ifcviewer-wgpu-minimal/CMakeLists.txt b/src/ifcviewer-minimal/CMakeLists.txt similarity index 80% rename from src/ifcviewer-wgpu-minimal/CMakeLists.txt rename to src/ifcviewer-minimal/CMakeLists.txt index f68a316a0c..61ea4c9735 100644 --- a/src/ifcviewer-wgpu-minimal/CMakeLists.txt +++ b/src/ifcviewer-minimal/CMakeLists.txt @@ -17,22 +17,22 @@ # # ################################################################################ -message("Running CMakeLists.txt in /src/ifcviewer-wgpu-minimal") +message("Running CMakeLists.txt in /src/ifcviewer-minimal") find_package(Qt${QT_VERSION} COMPONENTS Widgets REQUIRED PATHS ${QT_DIR}) -file(GLOB IFCVIEWER_WGPU_MIN_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) +file(GLOB IFCVIEWER_MIN_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) -add_executable(IfcViewerWgpuMinimal ${IFCVIEWER_WGPU_MIN_CPP_FILES}) +add_executable(IfcViewerMinimal ${IFCVIEWER_MIN_CPP_FILES}) -set_target_properties(IfcViewerWgpuMinimal PROPERTIES +set_target_properties(IfcViewerMinimal PROPERTIES AUTOMOC ON WIN32_EXECUTABLE ON MACOSX_BUNDLE ON ) -target_link_libraries(IfcViewerWgpuMinimal PRIVATE - IfcViewerWgpu +target_link_libraries(IfcViewerMinimal PRIVATE + IfcViewer Qt${QT_VERSION}::Widgets ) @@ -40,19 +40,19 @@ target_link_libraries(IfcViewerWgpuMinimal PRIVATE # run directly out of build-viewer-wgpu/. The patched SONAME means DT_NEEDED # is just the basename, so a single rpath entry suffices. if(UNIX AND NOT APPLE AND WGPU_NATIVE_LIB_DIR) - set_target_properties(IfcViewerWgpuMinimal PROPERTIES + set_target_properties(IfcViewerMinimal PROPERTIES BUILD_RPATH "${WGPU_NATIVE_LIB_DIR}" ) endif() # BUNDLE DESTINATION must be "." (install-prefix root) on macOS — Qt's -# deploy generator emits `macdeployqt IfcViewerWgpuMinimal.app` (no +# deploy generator emits `macdeployqt IfcViewerMinimal.app` (no # path prefix), which resolves only when the .app sits at the install # root. With `bin/` it can't find the bundle and the install/strip # step fails. -install(TARGETS IfcViewerWgpuMinimal +install(TARGETS IfcViewerMinimal EXPORT ${IFCOPENSHELL_EXPORT_TARGETS} RUNTIME DESTINATION bin BUNDLE DESTINATION . ) -ifcopenshell_deploy_qt_runtime(IfcViewerWgpuMinimal) +ifcopenshell_deploy_qt_runtime(IfcViewerMinimal) diff --git a/src/ifcviewer-wgpu-minimal/main.cpp b/src/ifcviewer-minimal/main.cpp similarity index 97% rename from src/ifcviewer-wgpu-minimal/main.cpp rename to src/ifcviewer-minimal/main.cpp index debc4fb117..24fd316e8b 100644 --- a/src/ifcviewer-wgpu-minimal/main.cpp +++ b/src/ifcviewer-minimal/main.cpp @@ -23,7 +23,7 @@ #include #include -#include "WgpuViewportWindow.h" +#include "ViewportWindow.h" // Stage-1 driver: opens a single window with the wgpu viewport embedded, // clears to background colour, and exits on close. The shape mirrors @@ -31,7 +31,7 @@ // benchmark-comparable binary. int main(int argc, char* argv[]) { QApplication app(argc, argv); - app.setApplicationName("IfcViewerWgpuMinimal"); + app.setApplicationName("IfcViewerMinimal"); app.setOrganizationName("IfcOpenShell"); QCommandLineParser parser; @@ -56,7 +56,7 @@ int main(int argc, char* argv[]) { "browser constraints."}); parser.process(app); - auto* viewport = new WgpuViewportWindow; + auto* viewport = new ViewportWindow; viewport->resize(1280, 800); if (parser.isSet("no-hiz")) viewport->hiz_enabled_ = false; if (parser.isSet("web-limits")) viewport->web_limits_ = true; diff --git a/src/ifcviewer-wgpu/CMakeLists.txt b/src/ifcviewer-wgpu/CMakeLists.txt deleted file mode 100644 index d304a9d5b7..0000000000 --- a/src/ifcviewer-wgpu/CMakeLists.txt +++ /dev/null @@ -1,194 +0,0 @@ -################################################################################ -# # -# 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 . # -# # -################################################################################ - -message("Running CMakeLists.txt in /src/ifcviewer-wgpu") - -set(QT_VERSION 6 CACHE STRING "Qt version") -find_package(Qt${QT_VERSION} COMPONENTS Core Gui REQUIRED PATHS ${QT_DIR}) - -# Eigen3 for the federation hooks (setModelTransformation etc.) and the -# per-model coordinate-operation matrices stored on WgpuModelGpuData. -find_package(Eigen3 REQUIRED) - -# wgpu-native — fetched as a pre-built binary release from upstream. -# Pin the version with WGPU_NATIVE_VERSION; bump to pull a newer release. -set(WGPU_NATIVE_VERSION "v29.0.0.0" CACHE STRING "wgpu-native release tag") - -# Pick the right release archive for the host platform. -if(CMAKE_SYSTEM_NAME STREQUAL "Windows") - if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64") - set(_wgpu_archive "wgpu-windows-aarch64-msvc-release.zip") - else() - set(_wgpu_archive "wgpu-windows-x86_64-msvc-release.zip") - endif() - set(_wgpu_lib "wgpu_native.dll.lib") - set(_wgpu_runtime "wgpu_native.dll") -elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64") - set(_wgpu_archive "wgpu-macos-aarch64-release.zip") - else() - set(_wgpu_archive "wgpu-macos-x86_64-release.zip") - endif() - set(_wgpu_lib "libwgpu_native.dylib") -else() # Linux + BSDs - if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64") - set(_wgpu_archive "wgpu-linux-aarch64-release.zip") - else() - set(_wgpu_archive "wgpu-linux-x86_64-release.zip") - endif() - set(_wgpu_lib "libwgpu_native.so") -endif() - -include(FetchContent) -FetchContent_Declare( - wgpu_native - URL https://github.com/gfx-rs/wgpu-native/releases/download/${WGPU_NATIVE_VERSION}/${_wgpu_archive} - DOWNLOAD_NO_PROGRESS FALSE -) -FetchContent_MakeAvailable(wgpu_native) - -# Release archive layout: include/webgpu/*.h and lib/. -# -# The Linux .so shipped in the v29 release has no DT_SONAME, which causes -# CMake to bake the relative IMPORTED_LOCATION path into DT_NEEDED. We patch -# the SONAME in once at configure time so dependents get a clean -# libwgpu_native.so reference, and pin the executable's rpath to the lib dir. -if(UNIX AND NOT APPLE) - find_program(PATCHELF_EXECUTABLE patchelf) - if(PATCHELF_EXECUTABLE) - execute_process( - COMMAND ${PATCHELF_EXECUTABLE} --set-soname "${_wgpu_lib}" - "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}" - RESULT_VARIABLE _patchelf_rc - ) - if(NOT _patchelf_rc EQUAL 0) - message(WARNING "patchelf --set-soname failed on libwgpu_native.so") - endif() - else() - message(WARNING - "patchelf not found; libwgpu_native.so will be linked with a " - "relative DT_NEEDED. Install patchelf to fix.") - endif() -endif() - -add_library(wgpu_native SHARED IMPORTED GLOBAL) -set_target_properties(wgpu_native PROPERTIES - IMPORTED_LOCATION "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}" - INTERFACE_INCLUDE_DIRECTORIES "${wgpu_native_SOURCE_DIR}/include" -) -if(WIN32) - # On Windows the .lib is the import library; the .dll is the runtime. - set_target_properties(wgpu_native PROPERTIES - IMPORTED_IMPLIB "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}" - IMPORTED_LOCATION "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_runtime}" - ) -endif() - -# Expose the lib dir so dependents can put it on their rpath. -set(WGPU_NATIVE_LIB_DIR "${wgpu_native_SOURCE_DIR}/lib" CACHE INTERNAL - "Directory containing the wgpu-native shared library") - -file(GLOB IFCVIEWER_WGPU_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) -file(GLOB IFCVIEWER_WGPU_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h) -set(IFCVIEWER_WGPU_FILES ${IFCVIEWER_WGPU_CPP_FILES} ${IFCVIEWER_WGPU_H_FILES}) - -# Cocoa bridge for the CAMetalLayer surface attach — Objective-C++. -# Only compiled into the target on Apple platforms; CMake handles `.mm` -# natively once OBJCXX is enabled. -if(APPLE) - enable_language(OBJCXX) - list(APPEND IFCVIEWER_WGPU_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/WgpuMetalSurface_mac.mm - ) -endif() - -# Intentional source-level borrowing from the GL backend until ifcviewer-core -# is extracted (task #12). SidecarCache + InstancedGeometry have zero Qt / -# OCCT / IFC-parse deps, so compiling them directly into IfcViewerWgpu is -# cheaper than dragging in the IfcViewer static lib (which would pull all -# of IfcGeom + IfcParse + OCCT + Qt OpenGL). -set(IFCVIEWER_SHARED_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../ifcviewer) -list(APPEND IFCVIEWER_WGPU_FILES - ${IFCVIEWER_SHARED_DIR}/SidecarCache.cpp -) - -add_library(IfcViewerWgpu STATIC ${IFCVIEWER_WGPU_FILES}) - -set_target_properties(IfcViewerWgpu PROPERTIES - AUTOMOC ON - VERSION "${PROJECT_VERSION}" - SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}" -) - -target_include_directories(IfcViewerWgpu - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} - PUBLIC ${IFCVIEWER_SHARED_DIR} # SidecarCache.h is reachable via WgpuViewportWindow.h -) - -target_link_libraries(IfcViewerWgpu PUBLIC - Qt${QT_VERSION}::Core - Qt${QT_VERSION}::Gui - wgpu_native - Eigen3::Eigen -) - -# Qt platform-handle access (QNativeInterface::QX11Application etc.) is in -# the public Gui headers in Qt 6.2+, no PRIVATE_INCLUDE_DIRS needed. - -if(UNIX AND NOT APPLE) - find_package(Threads REQUIRED) - target_link_libraries(IfcViewerWgpu PUBLIC Threads::Threads) -endif() - -# Cocoa + QuartzCore for WgpuMetalSurface_mac.mm (NSView, CAMetalLayer). -if(APPLE) - target_link_libraries(IfcViewerWgpu PUBLIC - "-framework Cocoa" - "-framework QuartzCore" - ) -endif() - -install(TARGETS IfcViewerWgpu EXPORT ${IFCOPENSHELL_EXPORT_TARGETS}) - -install(FILES ${IFCVIEWER_WGPU_H_FILES} - DESTINATION ${INCLUDEDIR}/ifcviewer-wgpu -) - -# Install the wgpu_native shared library so the deployed runtime can find it. -# At build/run-from-build-tree time CMake adds wgpu_native_SOURCE_DIR to the -# binary's rpath automatically (IMPORTED_LOCATION dirname). -# -# macOS: drop libwgpu_native.dylib straight into BonsaiViewer.app's -# Frameworks/. The exe has `LC_LOAD_DYLIB @rpath/libwgpu_native.dylib` -# (baked from the dylib's install_name), and BonsaiViewer's -# INSTALL_RPATH is set to @executable_path/../Frameworks — together -# they resolve at launch without depending on macdeployqt to follow -# non-Qt @rpath references. -if(WIN32) - install(FILES "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_runtime}" DESTINATION bin) -elseif(APPLE AND BUILD_BONSAIVIEWER) - install(FILES "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}" - DESTINATION "BonsaiViewer.app/Contents/Frameworks") -else() - install(FILES "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}" DESTINATION lib) -endif() - -if(BUILD_BONSAIVIEWER_TESTS) - add_subdirectory(tests) -endif() diff --git a/src/ifcviewer-wgpu/tests/CMakeLists.txt b/src/ifcviewer-wgpu/tests/CMakeLists.txt deleted file mode 100644 index 65de72e9d9..0000000000 --- a/src/ifcviewer-wgpu/tests/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -################################################################################ -# # -# 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 . # -# # -################################################################################ - -# Tier-1 unit tests for the wgpu backend's pure-CPU state machines (selection, -# visibility). Header-only subjects — the test binaries compile against the -# class definitions directly, no link to IfcViewerWgpu needed and no -# wgpu-native runtime involvement. - -set(IFCVIEWER_WGPU_SRC ${CMAKE_CURRENT_SOURCE_DIR}/..) - -function(add_ifcviewer_wgpu_unit_test name) - add_executable(${name} ${name}.cpp) - target_include_directories(${name} PRIVATE ${IFCVIEWER_WGPU_SRC}) - target_link_libraries(${name} PRIVATE Catch2::Catch2WithMain) - catch_discover_tests(${name}) -endfunction() - -add_ifcviewer_wgpu_unit_test(test_wgpu_selection) -add_ifcviewer_wgpu_unit_test(test_wgpu_visibility) diff --git a/src/ifcviewer-wgpu/WgpuAreaMeasurement.cpp b/src/ifcviewer/AreaMeasurement.cpp similarity index 94% rename from src/ifcviewer-wgpu/WgpuAreaMeasurement.cpp rename to src/ifcviewer/AreaMeasurement.cpp index 8acac21b38..623578725b 100644 --- a/src/ifcviewer-wgpu/WgpuAreaMeasurement.cpp +++ b/src/ifcviewer/AreaMeasurement.cpp @@ -17,10 +17,10 @@ * * ********************************************************************************/ -#include "WgpuAreaMeasurement.h" +#include "AreaMeasurement.h" -#include "WgpuOverlayRenderer.h" -#include "WgpuViewportWindow.h" +#include "OverlayRenderer.h" +#include "ViewportWindow.h" #include #include @@ -138,9 +138,9 @@ constexpr double kCoplanarDot = 0.9999; // ~0.81° tolerance, matches GL } // namespace -WgpuAreaMeasurement::WgpuAreaMeasurement() = default; +AreaMeasurement::AreaMeasurement() = default; -void WgpuAreaMeasurement::clear(WgpuViewportWindow& vp) { +void AreaMeasurement::clear(ViewportWindow& vp) { mesh_cache_.clear(); selected_.clear(); total_area_m2_ = 0.0; @@ -148,8 +148,8 @@ void WgpuAreaMeasurement::clear(WgpuViewportWindow& vp) { vp.setOverlayLabels({}); } -WgpuAreaMeasurement::MeshAdj* -WgpuAreaMeasurement::meshAdj(WgpuViewportWindow& vp, +AreaMeasurement::MeshAdj* +AreaMeasurement::meshAdj(ViewportWindow& vp, uint32_t model_id, uint32_t mesh_id) { const uint64_t key = (uint64_t(model_id) << 32) | uint64_t(mesh_id); auto it = mesh_cache_.find(key); @@ -159,7 +159,7 @@ WgpuAreaMeasurement::meshAdj(WgpuViewportWindow& vp, // them in the per-mesh cache (positions can be hundreds of KB each // and live in the viewport already), so just look them up freshly // each time the user picks a brand-new mesh. - WgpuViewportWindow::MeshTriangles tris; + ViewportWindow::MeshTriangles tris; if (!vp.readbackMeshTriangles(model_id, mesh_id, tris)) return nullptr; if (tris.indices.size() < 3) return nullptr; @@ -190,12 +190,12 @@ WgpuAreaMeasurement::meshAdj(WgpuViewportWindow& vp, return &mesh_cache_.emplace(key, std::move(a)).first->second; } -void WgpuAreaMeasurement::onPick(WgpuViewportWindow& vp, +void AreaMeasurement::onPick(ViewportWindow& vp, int x_phys, int y_phys, bool alt) { - WgpuViewportWindow::MeshLocalPick pick; + ViewportWindow::MeshLocalPick pick; if (!vp.pickMeshLocalAt(x_phys, y_phys, pick)) return; - WgpuViewportWindow::MeshTriangles tris; + ViewportWindow::MeshTriangles tris; if (!vp.readbackMeshTriangles(pick.model_id, pick.mesh_id, tris)) return; const size_t n_tris = tris.indices.size() / 3; if (n_tris == 0) return; @@ -285,7 +285,7 @@ void WgpuAreaMeasurement::onPick(WgpuViewportWindow& vp, total_area_m2_, selected_.size()); } -void WgpuAreaMeasurement::rebuildHighlightAndLabels(WgpuViewportWindow& vp) { +void AreaMeasurement::rebuildHighlightAndLabels(ViewportWindow& vp) { // 1) Highlight triangle list — each selected tri's three vertices // transformed by its captured composed_transform. Push as a // flat world-space tri list; the overlay tints them translucent @@ -295,20 +295,20 @@ void WgpuAreaMeasurement::rebuildHighlightAndLabels(WgpuViewportWindow& vp) { // Cache the latest MeshTriangles per (model,mesh) for this rebuild // to avoid repeated viewport lookups when many tris share a mesh. - std::unordered_map tris_cache; + std::unordered_map tris_cache; auto get_tris = [&](uint32_t model_id, uint32_t mesh_id) - -> WgpuViewportWindow::MeshTriangles* { + -> ViewportWindow::MeshTriangles* { const uint64_t k = (uint64_t(model_id) << 32) | uint64_t(mesh_id); auto it = tris_cache.find(k); if (it != tris_cache.end()) return &it->second; - WgpuViewportWindow::MeshTriangles t; + ViewportWindow::MeshTriangles t; if (!vp.readbackMeshTriangles(model_id, mesh_id, t)) return nullptr; return &tris_cache.emplace(k, std::move(t)).first->second; }; for (const auto& [key, sel] : selected_) { - WgpuViewportWindow::MeshTriangles* t = get_tris(sel.model_id, sel.mesh_id); + ViewportWindow::MeshTriangles* t = get_tris(sel.model_id, sel.mesh_id); if (!t) continue; if (size_t(sel.tri) * 3 + 2 >= t->indices.size()) continue; const float* M = sel.composed_transform; // column-major @@ -337,11 +337,11 @@ void WgpuAreaMeasurement::rebuildHighlightAndLabels(WgpuViewportWindow& vp) { by_object[object_id].push_back(&sel); } - std::vector labels; + std::vector labels; for (const auto& [obj_id, sels] : by_object) { if (sels.empty()) continue; const SelectedTri& any = *sels[0]; - WgpuViewportWindow::MeshTriangles* t = get_tris(any.model_id, any.mesh_id); + ViewportWindow::MeshTriangles* t = get_tris(any.model_id, any.mesh_id); if (!t) continue; MeshAdj* adj = meshAdj(vp, any.model_id, any.mesh_id); if (!adj) continue; @@ -393,7 +393,7 @@ void WgpuAreaMeasurement::rebuildHighlightAndLabels(WgpuViewportWindow& vp) { cx /= area; cy /= area; cz /= area; const float* M = any.composed_transform; - WgpuOverlayRenderer::Label lbl; + OverlayRenderer::Label lbl; lbl.world_pos[0] = float(M[0]*cx + M[4]*cy + M[8]*cz + M[12]); lbl.world_pos[1] = float(M[1]*cx + M[5]*cy + M[9]*cz + M[13]); lbl.world_pos[2] = float(M[2]*cx + M[6]*cy + M[10]*cz + M[14]); diff --git a/src/ifcviewer-wgpu/WgpuAreaMeasurement.h b/src/ifcviewer/AreaMeasurement.h similarity index 87% rename from src/ifcviewer-wgpu/WgpuAreaMeasurement.h rename to src/ifcviewer/AreaMeasurement.h index 13dd608875..a54096fb5e 100644 --- a/src/ifcviewer-wgpu/WgpuAreaMeasurement.h +++ b/src/ifcviewer/AreaMeasurement.h @@ -25,11 +25,11 @@ #include #include -class WgpuViewportWindow; +class ViewportWindow; // Click-to-accumulate area measurement for the wgpu viewport. Mirrors // src/bonsaiviewer/Measurement.h's AreaMeasurement: each pick resolves -// to (instance, triangle) via WgpuViewportWindow::pickMeshLocalAt, then +// to (instance, triangle) via ViewportWindow::pickMeshLocalAt, then // either adds or removes the connected coplanar patch (BFS over shared // edges, dot(normal, seed_normal) > 0.9999) depending on whether the // seed triangle was already in the running set. Alt-click skips the BFS. @@ -37,18 +37,18 @@ class WgpuViewportWindow; // separate patches. // // On every mutation the world-space triangles of the running set are -// pushed to WgpuViewportWindow::setHighlightTriangles for the +// pushed to ViewportWindow::setHighlightTriangles for the // translucent cyan patch shading, and per-component "X.XXXX m²" labels // are pushed to setOverlayLabels at each connected component's // area-weighted centroid. -class WgpuAreaMeasurement { +class AreaMeasurement { public: - WgpuAreaMeasurement(); + AreaMeasurement(); // Pixel coords are physical (post-DPR), to match - // WgpuViewportWindow::pickMeshLocalAt's convention. - void onPick(WgpuViewportWindow& vp, int x_phys, int y_phys, bool alt); - void clear(WgpuViewportWindow& vp); + // ViewportWindow::pickMeshLocalAt's convention. + void onPick(ViewportWindow& vp, int x_phys, int y_phys, bool alt); + void clear(ViewportWindow& vp); double totalArea() const { return total_area_m2_; } size_t triangleCount() const { return selected_.size(); } @@ -57,7 +57,7 @@ private: // Cached per-mesh derived data: triangle normals + areas + edge // adjacency. Computed once per (model, mesh) on first pick; the // raw positions + indices live in - // WgpuViewportWindow::readbackMeshTriangles' CPU shadow. + // ViewportWindow::readbackMeshTriangles' CPU shadow. struct MeshAdj { std::vector tri_normals; // 3 floats per tri (unit, mesh-local) std::vector tri_areas; // mesh-local area per tri @@ -65,7 +65,7 @@ private: std::unordered_map> edges; }; // Keyed by (model_id << 32) | mesh_id. - MeshAdj* meshAdj(WgpuViewportWindow& vp, + MeshAdj* meshAdj(ViewportWindow& vp, uint32_t model_id, uint32_t mesh_id); // Per-selected-triangle record. The composed transform is captured @@ -86,7 +86,7 @@ private: return (uint64_t(object_id) << 32) | uint64_t(tri); } - void rebuildHighlightAndLabels(WgpuViewportWindow& vp); + void rebuildHighlightAndLabels(ViewportWindow& vp); std::unordered_map mesh_cache_; std::unordered_map selected_; diff --git a/src/ifcviewer-wgpu/WgpuBufferPool.cpp b/src/ifcviewer/BufferPool.cpp similarity index 94% rename from src/ifcviewer-wgpu/WgpuBufferPool.cpp rename to src/ifcviewer/BufferPool.cpp index 015ce13e33..3bb686e7fb 100644 --- a/src/ifcviewer-wgpu/WgpuBufferPool.cpp +++ b/src/ifcviewer/BufferPool.cpp @@ -17,18 +17,18 @@ * * ********************************************************************************/ -#include "WgpuBufferPool.h" +#include "BufferPool.h" #include #include #include -WgpuBufferPool::~WgpuBufferPool() { +BufferPool::~BufferPool() { destroy(); } -void WgpuBufferPool::configure(WGPUInstance instance, WGPUDevice device, +void BufferPool::configure(WGPUInstance instance, WGPUDevice device, WGPUBufferUsage usage, uint64_t per_sub_buffer_capacity, const char* label_prefix) { @@ -41,7 +41,7 @@ void WgpuBufferPool::configure(WGPUInstance instance, WGPUDevice device, label_prefix_ = label_prefix ? label_prefix : ""; } -void WgpuBufferPool::destroy() { +void BufferPool::destroy() { for (auto& sp : sub_pools_) { if (sp.buffer) wgpuBufferRelease(sp.buffer); } @@ -55,7 +55,7 @@ void WgpuBufferPool::destroy() { label_prefix_.clear(); } -bool WgpuBufferPool::addSubBuffer() { +bool BufferPool::addSubBuffer() { if (!device_ || per_sub_buffer_capacity_ == 0) return false; if (growth_disabled_) return false; @@ -132,7 +132,7 @@ bool WgpuBufferPool::addSubBuffer() { return false; } -WgpuBufferPool::Slice WgpuBufferPool::alloc(uint64_t size, uint64_t align) { +BufferPool::Slice BufferPool::alloc(uint64_t size, uint64_t align) { Slice out; if (size == 0 || align == 0) return out; @@ -179,7 +179,7 @@ WgpuBufferPool::Slice WgpuBufferPool::alloc(uint64_t size, uint64_t align) { return out; } -void WgpuBufferPool::free(const Slice& s) { +void BufferPool::free(const Slice& s) { if (!s.valid()) return; if (s.sub_idx < 0 || size_t(s.sub_idx) >= sub_pools_.size()) return; SubPool& sp = sub_pools_[size_t(s.sub_idx)]; @@ -202,19 +202,19 @@ void WgpuBufferPool::free(const Slice& s) { } } -uint64_t WgpuBufferPool::total_capacity_bytes() const { +uint64_t BufferPool::total_capacity_bytes() const { uint64_t s = 0; for (const auto& sp : sub_pools_) s += sp.capacity; return s; } -uint64_t WgpuBufferPool::total_used_bytes() const { +uint64_t BufferPool::total_used_bytes() const { uint64_t s = 0; for (const auto& sp : sub_pools_) s += sp.used; return s; } -uint64_t WgpuBufferPool::largest_free_run_bytes() const { +uint64_t BufferPool::largest_free_run_bytes() const { uint64_t m = 0; for (const auto& sp : sub_pools_) { for (const auto& r : sp.free_ranges) { diff --git a/src/ifcviewer-wgpu/WgpuBufferPool.h b/src/ifcviewer/BufferPool.h similarity index 97% rename from src/ifcviewer-wgpu/WgpuBufferPool.h rename to src/ifcviewer/BufferPool.h index 532ce64823..d5036aa964 100644 --- a/src/ifcviewer-wgpu/WgpuBufferPool.h +++ b/src/ifcviewer/BufferPool.h @@ -46,7 +46,7 @@ // Allocator: per-sub-buffer sorted free list with adjacent-range // coalescing, first-fit across sub-buffers. Adequate for the chunk // workload (a few hundred allocations of broadly similar size). -class WgpuBufferPool { +class BufferPool { public: // A handle to a previously-allocated range. Includes the underlying // sub-buffer so callers (bind-group builders, queueWriteBuffer) can @@ -60,11 +60,11 @@ public: bool valid() const { return size > 0 && buffer != nullptr; } }; - WgpuBufferPool() = default; - ~WgpuBufferPool(); + BufferPool() = default; + ~BufferPool(); - WgpuBufferPool(const WgpuBufferPool&) = delete; - WgpuBufferPool& operator=(const WgpuBufferPool&) = delete; + BufferPool(const BufferPool&) = delete; + BufferPool& operator=(const BufferPool&) = delete; // Record the device + usage + sub-buffer size. Does NOT allocate any // sub-buffer here — that happens lazily on first alloc(). `instance` diff --git a/src/ifcviewer/CMakeLists.txt b/src/ifcviewer/CMakeLists.txt index 5d4f086edb..ea97fa4a4a 100644 --- a/src/ifcviewer/CMakeLists.txt +++ b/src/ifcviewer/CMakeLists.txt @@ -20,15 +20,105 @@ message("Running CMakeLists.txt in /src/ifcviewer") set(QT_VERSION 6 CACHE STRING "Qt version") -# IfcViewerLib always needs OpenGL in addition to Core/Gui/Widgets. We don't -# use the CACHE'd QT_COMPONENTS here because it may have been set by another -# target (e.g. qtviewer) without the OpenGL component. find_package(Qt${QT_VERSION} COMPONENTS Core Gui REQUIRED PATHS ${QT_DIR}) +# Eigen3 — used by Federation matrices, ViewportWindow's instance compose, +# the per-model coordinate-operation matrices stored on ModelGpuData, and +# everywhere a 4x4 transform shows up. +find_package(Eigen3 REQUIRED) + +# wgpu-native — fetched as a pre-built binary release from upstream. +# Pin the version with WGPU_NATIVE_VERSION; bump to pull a newer release. +set(WGPU_NATIVE_VERSION "v29.0.0.0" CACHE STRING "wgpu-native release tag") + +# Pick the right release archive for the host platform. +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64") + set(_wgpu_archive "wgpu-windows-aarch64-msvc-release.zip") + else() + set(_wgpu_archive "wgpu-windows-x86_64-msvc-release.zip") + endif() + set(_wgpu_lib "wgpu_native.dll.lib") + set(_wgpu_runtime "wgpu_native.dll") +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64") + set(_wgpu_archive "wgpu-macos-aarch64-release.zip") + else() + set(_wgpu_archive "wgpu-macos-x86_64-release.zip") + endif() + set(_wgpu_lib "libwgpu_native.dylib") +else() # Linux + BSDs + if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64") + set(_wgpu_archive "wgpu-linux-aarch64-release.zip") + else() + set(_wgpu_archive "wgpu-linux-x86_64-release.zip") + endif() + set(_wgpu_lib "libwgpu_native.so") +endif() + +include(FetchContent) +FetchContent_Declare( + wgpu_native + URL https://github.com/gfx-rs/wgpu-native/releases/download/${WGPU_NATIVE_VERSION}/${_wgpu_archive} + DOWNLOAD_NO_PROGRESS FALSE +) +FetchContent_MakeAvailable(wgpu_native) + +# Release archive layout: include/webgpu/*.h and lib/. +# +# The Linux .so shipped in the v29 release has no DT_SONAME, which causes +# CMake to bake the relative IMPORTED_LOCATION path into DT_NEEDED. We patch +# the SONAME in once at configure time so dependents get a clean +# libwgpu_native.so reference, and pin the executable's rpath to the lib dir. +if(UNIX AND NOT APPLE) + find_program(PATCHELF_EXECUTABLE patchelf) + if(PATCHELF_EXECUTABLE) + execute_process( + COMMAND ${PATCHELF_EXECUTABLE} --set-soname "${_wgpu_lib}" + "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}" + RESULT_VARIABLE _patchelf_rc + ) + if(NOT _patchelf_rc EQUAL 0) + message(WARNING "patchelf --set-soname failed on libwgpu_native.so") + endif() + else() + message(WARNING + "patchelf not found; libwgpu_native.so will be linked with a " + "relative DT_NEEDED. Install patchelf to fix.") + endif() +endif() + +add_library(wgpu_native SHARED IMPORTED GLOBAL) +set_target_properties(wgpu_native PROPERTIES + IMPORTED_LOCATION "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}" + INTERFACE_INCLUDE_DIRECTORIES "${wgpu_native_SOURCE_DIR}/include" +) +if(WIN32) + # On Windows the .lib is the import library; the .dll is the runtime. + set_target_properties(wgpu_native PROPERTIES + IMPORTED_IMPLIB "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}" + IMPORTED_LOCATION "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_runtime}" + ) +endif() + +# Expose the lib dir so dependents can put it on their rpath. +set(WGPU_NATIVE_LIB_DIR "${wgpu_native_SOURCE_DIR}/lib" CACHE INTERNAL + "Directory containing the wgpu-native shared library") + file(GLOB IFCVIEWER_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) -file(GLOB IFCVIEWER_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h) +file(GLOB IFCVIEWER_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h) set(IFCVIEWER_FILES ${IFCVIEWER_CPP_FILES} ${IFCVIEWER_H_FILES}) +# Cocoa bridge for the CAMetalLayer surface attach — Objective-C++. +# Only compiled into the target on Apple platforms; CMake handles `.mm` +# natively once OBJCXX is enabled. +if(APPLE) + enable_language(OBJCXX) + list(APPEND IFCVIEWER_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/MetalSurface_mac.mm + ) +endif() + add_library(IfcViewer STATIC ${IFCVIEWER_FILES}) set_target_properties(IfcViewer PROPERTIES @@ -38,9 +128,9 @@ set_target_properties(IfcViewer PROPERTIES ) if (WITH_MESH_OPTIMIZER) -find_package(meshoptimizer REQUIRED) -set(MESH_OPTIMIZER_LIB meshoptimizer::meshoptimizer) -target_compile_definitions(IfcViewer PUBLIC -DWITH_MESH_OPTIMIZER) + find_package(meshoptimizer REQUIRED) + set(MESH_OPTIMIZER_LIB meshoptimizer::meshoptimizer) + target_compile_definitions(IfcViewer PUBLIC -DWITH_MESH_OPTIMIZER) endif() # Consumers include headers as `#include "ViewportWindow.h"`, so expose this @@ -57,23 +147,52 @@ target_link_libraries(IfcViewer PUBLIC ${CGAL_LIBRARIES} Qt${QT_VERSION}::Core Qt${QT_VERSION}::Gui + Eigen3::Eigen + wgpu_native ${MESH_OPTIMIZER_LIB} - # SceneLoader drives WgpuViewportWindow; the wgpu lib also provides - # the include path for WgpuViewportWindow.h that SceneLoader.h pulls in. - IfcViewerWgpu ) +# Qt platform-handle access (QNativeInterface::QX11Application etc.) is in +# the public Gui headers in Qt 6.2+, no PRIVATE_INCLUDE_DIRS needed. + if(UNIX AND NOT APPLE) find_package(Threads REQUIRED) target_link_libraries(IfcViewer PUBLIC Threads::Threads) endif() +# Cocoa + QuartzCore for MetalSurface_mac.mm (NSView, CAMetalLayer). +if(APPLE) + target_link_libraries(IfcViewer PUBLIC + "-framework Cocoa" + "-framework QuartzCore" + ) +endif() + install(TARGETS IfcViewer EXPORT ${IFCOPENSHELL_EXPORT_TARGETS}) install(FILES ${IFCVIEWER_H_FILES} DESTINATION ${INCLUDEDIR}/ifcviewer ) +# Install the wgpu_native shared library so the deployed runtime can find it. +# At build/run-from-build-tree time CMake adds wgpu_native_SOURCE_DIR to the +# binary's rpath automatically (IMPORTED_LOCATION dirname). +# +# macOS: drop libwgpu_native.dylib straight into BonsaiViewer.app's +# Frameworks/. The exe has `LC_LOAD_DYLIB @rpath/libwgpu_native.dylib` +# (baked from the dylib's install_name), and BonsaiViewer's +# INSTALL_RPATH is set to @executable_path/../Frameworks — together +# they resolve at launch without depending on macdeployqt to follow +# non-Qt @rpath references. +if(WIN32) + install(FILES "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_runtime}" DESTINATION bin) +elseif(APPLE AND BUILD_BONSAIVIEWER) + install(FILES "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}" + DESTINATION "BonsaiViewer.app/Contents/Frameworks") +else() + install(FILES "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}" DESTINATION lib) +endif() + if(BUILD_BONSAIVIEWER_TESTS) add_subdirectory(tests) endif() diff --git a/src/ifcviewer-wgpu/WgpuLengthMeasurement.cpp b/src/ifcviewer/LengthMeasurement.cpp similarity index 96% rename from src/ifcviewer-wgpu/WgpuLengthMeasurement.cpp rename to src/ifcviewer/LengthMeasurement.cpp index 893d7840ee..7f0b620144 100644 --- a/src/ifcviewer-wgpu/WgpuLengthMeasurement.cpp +++ b/src/ifcviewer/LengthMeasurement.cpp @@ -17,9 +17,9 @@ * * ********************************************************************************/ -#include "WgpuLengthMeasurement.h" +#include "LengthMeasurement.h" -#include "WgpuOverlayRenderer.h" +#include "OverlayRenderer.h" #include #include @@ -292,10 +292,10 @@ constexpr float DOT_HALO = 1.0f; constexpr float DASH_PERIOD = 9.0f; // px constexpr float DASH_ON_RATIO = 0.55f; // 5 on, 4 off -WgpuOverlayRenderer::LineGroup makeGroup(std::vector xyz, +OverlayRenderer::LineGroup makeGroup(std::vector xyz, float r, float g, float b, bool dashed = false) { - WgpuOverlayRenderer::LineGroup gp; + OverlayRenderer::LineGroup gp; gp.world_xyz = std::move(xyz); gp.color[0] = r; gp.color[1] = g; gp.color[2] = b; gp.color[3] = 1.0f; gp.stroke_color[0] = 0.0f; gp.stroke_color[1] = 0.0f; @@ -318,10 +318,10 @@ void pushSeg(std::vector& xyz, xyz.insert(xyz.end(), b.begin(), b.end()); } -WgpuOverlayRenderer::Label makeLabel(const std::array& a, +OverlayRenderer::Label makeLabel(const std::array& a, const std::array& b, const QString& text) { - WgpuOverlayRenderer::Label lbl; + OverlayRenderer::Label lbl; lbl.world_pos[0] = 0.5f * (a[0] + b[0]); lbl.world_pos[1] = 0.5f * (a[1] + b[1]); lbl.world_pos[2] = 0.5f * (a[2] + b[2]); @@ -329,7 +329,7 @@ WgpuOverlayRenderer::Label makeLabel(const std::array& a, return lbl; } -void pushDots(WgpuViewportWindow& vp, const std::vector& xyz) { +void pushDots(ViewportWindow& vp, const std::vector& xyz) { vp.setOverlayPoints(xyz, /*inner*/ 1.0f, 1.0f, 1.0f, 1.0f, /*size*/ DOT_SIZE, @@ -348,9 +348,9 @@ const char* dominantAxisLabel(const float v[3]) { } // namespace -WgpuLengthMeasurement::WgpuLengthMeasurement() = default; +LengthMeasurement::LengthMeasurement() = default; -void WgpuLengthMeasurement::clear(WgpuViewportWindow& vp) { +void LengthMeasurement::clear(ViewportWindow& vp) { points_.clear(); normals_.clear(); vp.setOverlayPoints({}, 0,0,0,0, 0, 0,0,0,0, 0); @@ -359,9 +359,9 @@ void WgpuLengthMeasurement::clear(WgpuViewportWindow& vp) { vp.setHudText(QString()); } -void WgpuLengthMeasurement::onPick(WgpuViewportWindow& vp, +void LengthMeasurement::onPick(ViewportWindow& vp, int x_phys, int y_phys, bool /*alt*/) { - WgpuViewportWindow::MeshLocalPick pick; + ViewportWindow::MeshLocalPick pick; if (!vp.pickMeshLocalAt(x_phys, y_phys, pick)) return; points_.push_back({pick.world_pos[0], pick.world_pos[1], pick.world_pos[2]}); normals_.push_back({pick.world_normal[0], pick.world_normal[1], pick.world_normal[2]}); @@ -371,14 +371,14 @@ void WgpuLengthMeasurement::onPick(WgpuViewportWindow& vp, rebuildOverlay(vp); } -void WgpuLengthMeasurement::removeLastPoint(WgpuViewportWindow& vp) { +void LengthMeasurement::removeLastPoint(ViewportWindow& vp) { if (points_.empty()) return; points_.pop_back(); if (!normals_.empty()) normals_.pop_back(); rebuildOverlay(vp); } -void WgpuLengthMeasurement::rebuildOverlay(WgpuViewportWindow& vp) { +void LengthMeasurement::rebuildOverlay(ViewportWindow& vp) { if (points_.size() == 1 && normals_.size() == 1) { rebuildLaserOverlay(vp); return; @@ -389,8 +389,8 @@ void WgpuLengthMeasurement::rebuildOverlay(WgpuViewportWindow& vp) { for (const auto& p : points_) pushDot(pts_xyz, p); pushDots(vp, pts_xyz); - std::vector groups; - std::vector labels; + std::vector groups; + std::vector labels; const size_t n = points_.size(); if (n == 2) { @@ -489,7 +489,7 @@ void WgpuLengthMeasurement::rebuildOverlay(WgpuViewportWindow& vp) { vp.setHudText(formatReadout()); } -void WgpuLengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) { +void LengthMeasurement::rebuildLaserOverlay(ViewportWindow& vp) { const auto& wp = first_pick_.world_pos; const auto& n = first_pick_.world_normal; @@ -519,8 +519,8 @@ void WgpuLengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) { n[0]*t1[1] - n[1]*t1[0], }; - std::vector groups; - std::vector labels; + std::vector groups; + std::vector labels; QStringList hud_lines; hud_lines << QStringLiteral("Laser measure (click another point for distance)"); double enh[3] = {0.0, 0.0, 0.0}; @@ -532,7 +532,7 @@ void WgpuLengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) { } // Coplanar-patch BFS for face extent. - WgpuViewportWindow::MeshTriangles tris; + ViewportWindow::MeshTriangles tris; bool have_extent = false; double min_t1 = 0.0, max_t1 = 0.0, min_t2 = 0.0, max_t2 = 0.0; if (vp.readbackMeshTriangles(first_pick_.model_id, first_pick_.mesh_id, tris)) { @@ -658,7 +658,7 @@ void WgpuLengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) { wp[1] + NUDGE * n[1], wp[2] + NUDGE * n[2], }; - WgpuViewportWindow::RaycastHit hit; + ViewportWindow::RaycastHit hit; if (vp.raycast(ro, n, hit)) { const double dist = double(hit.distance) + double(NUDGE); const std::array a = {wp[0], wp[1], wp[2]}; @@ -682,7 +682,7 @@ void WgpuLengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) { vp.setHudText(hud_lines.join('\n')); } -QString WgpuLengthMeasurement::formatReadout() const { +QString LengthMeasurement::formatReadout() const { const size_t n = points_.size(); if (n == 0) return QStringLiteral("Length tool: click first point"); if (n == 1) return QStringLiteral("1 point (click another)"); diff --git a/src/ifcviewer-wgpu/WgpuLengthMeasurement.h b/src/ifcviewer/LengthMeasurement.h similarity index 89% rename from src/ifcviewer-wgpu/WgpuLengthMeasurement.h rename to src/ifcviewer/LengthMeasurement.h index 690977866a..5562801cb4 100644 --- a/src/ifcviewer-wgpu/WgpuLengthMeasurement.h +++ b/src/ifcviewer/LengthMeasurement.h @@ -20,7 +20,7 @@ #ifndef WGPULENGTHMEASUREMENT_H #define WGPULENGTHMEASUREMENT_H -#include "WgpuViewportWindow.h" +#include "ViewportWindow.h" #include @@ -45,21 +45,21 @@ // Pushes the running set as overlay points + the connecting polyline + // per-segment labels to the viewport; the multi-line HUD carries the // adaptive readout. -class WgpuLengthMeasurement { +class LengthMeasurement { public: - WgpuLengthMeasurement(); + LengthMeasurement(); // Pixel coords are physical (post-DPR). `alt` is currently unused // (kept for API symmetry with the Area tool). - void onPick(WgpuViewportWindow& vp, int x_phys, int y_phys, bool alt); - void removeLastPoint(WgpuViewportWindow& vp); - void clear(WgpuViewportWindow& vp); + void onPick(ViewportWindow& vp, int x_phys, int y_phys, bool alt); + void removeLastPoint(ViewportWindow& vp); + void clear(ViewportWindow& vp); size_t pointCount() const { return points_.size(); } private: - void rebuildOverlay(WgpuViewportWindow& vp); - void rebuildLaserOverlay(WgpuViewportWindow& vp); + void rebuildOverlay(ViewportWindow& vp); + void rebuildLaserOverlay(ViewportWindow& vp); QString formatReadout() const; std::vector> points_; @@ -69,7 +69,7 @@ private: // updated afterwards. Used by the 1-pt laser BFS to locate the // mesh-local position of points_[0] without re-picking. Stays valid // while points_[0] does (pop_back never touches the first element). - WgpuViewportWindow::MeshLocalPick first_pick_{}; + ViewportWindow::MeshLocalPick first_pick_{}; }; #endif // WGPULENGTHMEASUREMENT_H diff --git a/src/ifcviewer-wgpu/WgpuMetalSurface_mac.h b/src/ifcviewer/MetalSurface_mac.h similarity index 86% rename from src/ifcviewer-wgpu/WgpuMetalSurface_mac.h rename to src/ifcviewer/MetalSurface_mac.h index 3c8f75a25b..ff9fa467cb 100644 --- a/src/ifcviewer-wgpu/WgpuMetalSurface_mac.h +++ b/src/ifcviewer/MetalSurface_mac.h @@ -1,11 +1,11 @@ /** - * Objective-C++ bridge between WgpuViewportWindow (pure C++) and Cocoa / + * Objective-C++ bridge between ViewportWindow (pure C++) and Cocoa / * QuartzCore (Objective-C). Compiled only on macOS — see CMakeLists.txt. * * Qt's QWindow::winId() returns the backing NSView* (as a WId) on macOS; * we need a CAMetalLayer attached to that view to hand to wgpu-native * via WGPUSurfaceSourceMetalLayer. Doing that requires Objective-C, so - * the actual layer attach lives in WgpuMetalSurface_mac.mm. + * the actual layer attach lives in MetalSurface_mac.mm. */ #ifndef WGPU_METAL_SURFACE_MAC_H diff --git a/src/ifcviewer-wgpu/WgpuMetalSurface_mac.mm b/src/ifcviewer/MetalSurface_mac.mm similarity index 94% rename from src/ifcviewer-wgpu/WgpuMetalSurface_mac.mm rename to src/ifcviewer/MetalSurface_mac.mm index d4ac05fc77..523fda473e 100644 --- a/src/ifcviewer-wgpu/WgpuMetalSurface_mac.mm +++ b/src/ifcviewer/MetalSurface_mac.mm @@ -1,9 +1,9 @@ /** * Objective-C++ implementation of the Cocoa bridge declared in - * WgpuMetalSurface_mac.h. Compiled only on macOS. + * MetalSurface_mac.h. Compiled only on macOS. */ -#include "WgpuMetalSurface_mac.h" +#include "MetalSurface_mac.h" #if defined(__APPLE__) diff --git a/src/ifcviewer-wgpu/WgpuModelGpuData.h b/src/ifcviewer/ModelGpuData.h similarity index 97% rename from src/ifcviewer-wgpu/WgpuModelGpuData.h rename to src/ifcviewer/ModelGpuData.h index a107eb5e67..a4cbd7e0c4 100644 --- a/src/ifcviewer-wgpu/WgpuModelGpuData.h +++ b/src/ifcviewer/ModelGpuData.h @@ -32,7 +32,7 @@ #include #include "InstancedGeometry.h" -#include "WgpuBufferPool.h" +#include "BufferPool.h" // Per-model wgpu state. Mirrors the GL backend's ModelGpuData but with // wgpu handles. Stage 2 only allocates and uploads the four core buffers; @@ -49,7 +49,7 @@ // // At INSTANCED_VERTEX_STRIDE_BYTES = 12 B/vertex this caps a chunk at // ~1.4 M vertices. 16 MB is the sweet spot once background-thread I/O -// (WgpuStreamingThread) is in place: scatter-gather per-mesh seeks +// (StreamingThread) is in place: scatter-gather per-mesh seeks // happen on the worker, not the render thread, so smaller chunks // (and thus more per-frame loads as orbit shifts) no longer stall // rendering. The win is much finer pool-allocation granularity — @@ -64,7 +64,7 @@ // and 16 MB. static constexpr uint64_t WGPU_CHUNK_VERTEX_BYTES_LIMIT = 16ull * 1024 * 1024; -struct WgpuModelGpuData { +struct ModelGpuData { // std430 layout: 16 bytes per entry, naturally aligned. base_vertex is // CHUNK-LOCAL — the bound vertex_storage on that chunk's bind group // gives the right slice when the shader indexes vertices[]. @@ -77,7 +77,7 @@ struct WgpuModelGpuData { static_assert(sizeof(VisibleDrawGpu) == 16, "VisibleDrawGpu must be 16 bytes"); // Per-chunk state. Each chunk references a vertex range and an - // index range inside WgpuViewportWindow::pool_, plus a small set of + // index range inside ViewportWindow::pool_, plus a small set of // per-frame buffers (visible_draws, prefix_sums, uniform) and a bind // group that binds the pool ranges alongside the model-shared // mesh/instance storage. Rendering issues one drawcall per non-empty @@ -91,12 +91,12 @@ struct WgpuModelGpuData { // populates pool ranges at applyCachedModel time. struct Chunk { // Pool-allocated vertex + index bytes. Both slices land in the - // shared WgpuViewportWindow::pool_; the slice tells us which + // shared ViewportWindow::pool_; the slice tells us which // sub-buffer they live in (the pool may span multiple sub-buffers // when scenes exceed wgpu's single-buffer cap). When non-resident, // both .size are 0. - WgpuBufferPool::Slice vertex_slice; - WgpuBufferPool::Slice index_slice; + BufferPool::Slice vertex_slice; + BufferPool::Slice index_slice; WGPUBuffer visible_draws_buffer = nullptr; WGPUBuffer prefix_sums_buffer = nullptr; @@ -357,6 +357,6 @@ struct WgpuModelGpuData { // Release every wgpu handle in `m` (including per-chunk and per-model pool // ranges via `pool.free()`) and clear its size mirrors. Safe to call // repeatedly; idempotent on already-released entries. -void releaseWgpuModelGpuData(WgpuModelGpuData& m, WgpuBufferPool& pool); +void releaseWgpuModelGpuData(ModelGpuData& m, BufferPool& pool); #endif // WGPUMODELGPUDATA_H diff --git a/src/ifcviewer-wgpu/WgpuOverlayRenderer.cpp b/src/ifcviewer/OverlayRenderer.cpp similarity index 97% rename from src/ifcviewer-wgpu/WgpuOverlayRenderer.cpp rename to src/ifcviewer/OverlayRenderer.cpp index bffda18430..7791c09a2d 100644 --- a/src/ifcviewer-wgpu/WgpuOverlayRenderer.cpp +++ b/src/ifcviewer/OverlayRenderer.cpp @@ -17,7 +17,7 @@ * * ********************************************************************************/ -#include "WgpuOverlayRenderer.h" +#include "OverlayRenderer.h" #include #include @@ -439,11 +439,11 @@ fn fs_main(in: VsOut) -> @location(0) vec4 { // Construction / destruction // ----------------------------------------------------------------------------- -WgpuOverlayRenderer::~WgpuOverlayRenderer() { +OverlayRenderer::~OverlayRenderer() { destroy(); } -bool WgpuOverlayRenderer::init(WGPUInstance instance, WGPUDevice device, +bool OverlayRenderer::init(WGPUInstance instance, WGPUDevice device, WGPUQueue queue, WGPUTextureFormat surface_format, int sample_count) { instance_ = instance; @@ -461,7 +461,7 @@ bool WgpuOverlayRenderer::init(WGPUInstance instance, WGPUDevice device, return true; } -void WgpuOverlayRenderer::destroy() { +void OverlayRenderer::destroy() { // Axis indicator if (axis_bind_group_) { wgpuBindGroupRelease(axis_bind_group_); axis_bind_group_ = nullptr; } if (axis_pivot_pipeline_) { wgpuRenderPipelineRelease(axis_pivot_pipeline_); axis_pivot_pipeline_ = nullptr; } @@ -545,7 +545,7 @@ void WgpuOverlayRenderer::destroy() { // Axis indicator // ----------------------------------------------------------------------------- -bool WgpuOverlayRenderer::buildAxisIndicator() { +bool OverlayRenderer::buildAxisIndicator() { // Bonsai decorator palette (src/bonsai/bonsai/bim/ui.py:593+): // decorator_color_error = (1.000, 0.200, 0.322) — red → +X // decorator_color_selected = (0.545, 0.863, 0.000) — green → +Y @@ -719,8 +719,8 @@ bool WgpuOverlayRenderer::buildAxisIndicator() { && axis_corner_pipeline_; } -void WgpuOverlayRenderer::encodePivot(WGPURenderPassEncoder pass, - const WgpuOverlayFrame& f, +void OverlayRenderer::encodePivot(WGPURenderPassEncoder pass, + const OverlayFrame& f, bool visible) { if (!visible || !axis_pivot_pipeline_ || !axis_pivot_xray_pipeline_) return; if (f.viewport_h_px <= 0) return; @@ -760,9 +760,9 @@ void WgpuOverlayRenderer::encodePivot(WGPURenderPassEncoder pass, wgpuRenderPassEncoderDraw(pass, 18, 1, 0, 0); } -void WgpuOverlayRenderer::encodeCornerAxis(WGPUCommandEncoder enc, +void OverlayRenderer::encodeCornerAxis(WGPUCommandEncoder enc, WGPUTextureView surface_view, - const WgpuOverlayFrame& f) { + const OverlayFrame& f) { if (!axis_corner_pipeline_ || !surface_view) return; const int dpr = std::max(1, f.device_pixel_ratio); const uint32_t gizmo_size = uint32_t(110 * dpr); @@ -829,7 +829,7 @@ void WgpuOverlayRenderer::encodeCornerAxis(WGPUCommandEncoder enc, // Section plane visualizer // ----------------------------------------------------------------------------- -bool WgpuOverlayRenderer::buildSectionVisualizer() { +bool OverlayRenderer::buildSectionVisualizer() { struct Seg { std::array s, e; std::array c; @@ -967,9 +967,9 @@ bool WgpuOverlayRenderer::buildSectionVisualizer() { return section_pipeline_ != nullptr; } -void WgpuOverlayRenderer::encodeSectionGizmos(WGPURenderPassEncoder pass, - const WgpuOverlayFrame& f, - const std::vector& planes) { +void OverlayRenderer::encodeSectionGizmos(WGPURenderPassEncoder pass, + const OverlayFrame& f, + const std::vector& planes) { if (!section_pipeline_ || planes.empty()) return; wgpuRenderPassEncoderSetPipeline(pass, section_pipeline_); @@ -978,7 +978,7 @@ void WgpuOverlayRenderer::encodeSectionGizmos(WGPURenderPassEncoder pass, const int n = std::min(int(planes.size()), kMaxSectionPlanes); for (int i = 0; i < n; ++i) { - const WgpuSectionPlane& p = planes[i]; + const SectionPlane& p = planes[i]; // Stable in-plane basis: pick the world axis least parallel to n // so the cross-product stays well-conditioned at any orientation. @@ -1021,7 +1021,7 @@ void WgpuOverlayRenderer::encodeSectionGizmos(WGPURenderPassEncoder pass, // Marquee // ----------------------------------------------------------------------------- -bool WgpuOverlayRenderer::buildMarquee() { +bool OverlayRenderer::buildMarquee() { struct Seg { std::array s, e; }; static const Seg segs[] = { { {0, 0}, {1, 0} }, @@ -1183,9 +1183,9 @@ bool WgpuOverlayRenderer::buildMarquee() { return marquee_pipeline_ != nullptr && marquee_fill_pipeline_ != nullptr; } -void WgpuOverlayRenderer::encodeMarquee(WGPUCommandEncoder enc, +void OverlayRenderer::encodeMarquee(WGPUCommandEncoder enc, WGPUTextureView surface_view, - const WgpuOverlayFrame& f, + const OverlayFrame& f, QPoint start_logical_px, QPoint current_logical_px, bool active) { @@ -1258,7 +1258,7 @@ void WgpuOverlayRenderer::encodeMarquee(WGPUCommandEncoder enc, // Overlay lines // ----------------------------------------------------------------------------- -bool WgpuOverlayRenderer::buildOverlayLines() { +bool OverlayRenderer::buildOverlayLines() { // Empty initial buffers — both grow on demand inside setOverlayLines. // Use a tiny starter capacity so the very first set call doesn't have // to special-case "buffer is null." @@ -1379,7 +1379,7 @@ bool WgpuOverlayRenderer::buildOverlayLines() { return overlay_line_pipeline_ != nullptr; } -void WgpuOverlayRenderer::setOverlayLines(const std::vector& groups) { +void OverlayRenderer::setOverlayLines(const std::vector& groups) { overlay_line_draws_.clear(); if (groups.empty()) return; @@ -1493,8 +1493,8 @@ void WgpuOverlayRenderer::setOverlayLines(const std::vector& groups) } } -void WgpuOverlayRenderer::encodeOverlayLines(WGPURenderPassEncoder pass, - const WgpuOverlayFrame& f) { +void OverlayRenderer::encodeOverlayLines(WGPURenderPassEncoder pass, + const OverlayFrame& f) { if (!overlay_line_pipeline_ || overlay_line_draws_.empty()) return; if (f.viewport_w_px <= 0 || f.viewport_h_px <= 0) return; @@ -1529,7 +1529,7 @@ void WgpuOverlayRenderer::encodeOverlayLines(WGPURenderPassEncoder pass, // Overlay points // ----------------------------------------------------------------------------- -bool WgpuOverlayRenderer::buildOverlayPoints() { +bool OverlayRenderer::buildOverlayPoints() { { WGPUBufferDescriptor bdesc = {}; bdesc.usage = WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst; @@ -1642,7 +1642,7 @@ bool WgpuOverlayRenderer::buildOverlayPoints() { return overlay_point_pipeline_ != nullptr; } -void WgpuOverlayRenderer::setOverlayPoints(const std::vector& world_xyz, +void OverlayRenderer::setOverlayPoints(const std::vector& world_xyz, float r, float g, float b, float a, float pixel_size, float stroke_r, float stroke_g, @@ -1715,8 +1715,8 @@ void WgpuOverlayRenderer::setOverlayPoints(const std::vector& world_xyz, &inner_norm, sizeof(inner_norm)); } -void WgpuOverlayRenderer::encodeOverlayPoints(WGPURenderPassEncoder pass, - const WgpuOverlayFrame& f) { +void OverlayRenderer::encodeOverlayPoints(WGPURenderPassEncoder pass, + const OverlayFrame& f) { if (!overlay_point_pipeline_ || overlay_point_vertex_count_ == 0) return; if (f.viewport_w_px <= 0 || f.viewport_h_px <= 0) return; @@ -1738,7 +1738,7 @@ void WgpuOverlayRenderer::encodeOverlayPoints(WGPURenderPassEncoder pass, // Highlight triangles (translucent world-space triangle list) // ----------------------------------------------------------------------------- -bool WgpuOverlayRenderer::buildHighlightTriangles() { +bool OverlayRenderer::buildHighlightTriangles() { { WGPUBufferDescriptor bdesc = {}; bdesc.usage = WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst; @@ -1853,7 +1853,7 @@ bool WgpuOverlayRenderer::buildHighlightTriangles() { return highlight_pipeline_ != nullptr; } -void WgpuOverlayRenderer::setHighlightTriangles( +void OverlayRenderer::setHighlightTriangles( const std::vector& world_xyz, float r, float g, float b, float a) { highlight_color_[0] = r; @@ -1881,8 +1881,8 @@ void WgpuOverlayRenderer::setHighlightTriangles( highlight_vertex_count_ = uint32_t(n_floats / 3); } -void WgpuOverlayRenderer::encodeHighlightTriangles(WGPURenderPassEncoder pass, - const WgpuOverlayFrame& f) { +void OverlayRenderer::encodeHighlightTriangles(WGPURenderPassEncoder pass, + const OverlayFrame& f) { if (!highlight_pipeline_ || highlight_vertex_count_ == 0) return; // Pack mat4 + vec4 into the slot. mat4 is column-major 16 floats. uint8_t slot[80] = {}; @@ -1901,7 +1901,7 @@ void WgpuOverlayRenderer::encodeHighlightTriangles(WGPURenderPassEncoder pass, // Labels + HUD text (textured quads, content-cached) // ----------------------------------------------------------------------------- -bool WgpuOverlayRenderer::buildLabels() { +bool OverlayRenderer::buildLabels() { { WGPUSamplerDescriptor sd = {}; sd.minFilter = WGPUFilterMode_Linear; @@ -2001,7 +2001,7 @@ bool WgpuOverlayRenderer::buildLabels() { return label_pipeline_ != nullptr; } -void WgpuOverlayRenderer::releaseLabelTextures() { +void OverlayRenderer::releaseLabelTextures() { for (auto it = label_tex_cache_.begin(); it != label_tex_cache_.end(); ++it) { if (it.value().bind_group) wgpuBindGroupRelease(it.value().bind_group); if (it.value().view) wgpuTextureViewRelease(it.value().view); @@ -2010,16 +2010,16 @@ void WgpuOverlayRenderer::releaseLabelTextures() { label_tex_cache_.clear(); } -void WgpuOverlayRenderer::setOverlayLabels(const std::vector