mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 23:36:20 +00:00
Route bonsai through wgpu; delete the GL backend
Bonsai now drives the wgpu viewport for both sidecar and direct-IFC
loads. The GL viewer and its supporting state classes are gone.
SceneLoader rewire:
- Takes WgpuViewportWindow* instead of ViewportWindow*.
- Sidecar path reads metadata only (readSidecarMetadataOnly) and hands
the StreamingSidecar off to the new applyCachedModel. Field accesses
inside applySidecarData go through .meta.
- Direct-IFC path uses the wgpu A-path (upload{Mesh,Instance}Chunk +
finalizeModel). The applyLodExtension call is dropped — wgpu has no
live LOD1 splice; LOD1 still lands in the on-disk sidecar for the
next open.
Bonsai migration:
- ViewportWindow → WgpuViewportWindow across MainWindow, Measurement,
SessionState, and every modules/*/{Commands,Panel,View}.{h,cpp} —
116 sites total. Same s/OverlayRenderer::/WgpuOverlayRenderer::/
rename, 12 sites.
- Includes flipped from ../ifcviewer/ViewportWindow.h to
../ifcviewer-wgpu/WgpuViewportWindow.h. OverlayRenderer.h include
dropped (transitively reached via the viewport header).
- BonsaiViewer links IfcViewerWgpu in addition to IfcViewer for the
duration of the migration; the GL-side IfcViewer also publicly links
IfcViewerWgpu so SceneLoader can resolve WgpuViewportWindow.
GL backend deletion:
- src/ifcviewer/ViewportWindow.{cpp,h}, BvhAccel.*, OverlayRenderer.*,
Selection.*, Visibility.* all gone.
- src/ifcviewer-minimal/ removed entirely (MinimalWindow drove the GL
viewport).
- src/ifcviewer/tests: test_bvh_accel, test_selection, test_visibility
removed. The first has no replacement (wgpu doesn't use a per-instance
BVH); the latter two are ported separately. test_lod_builder,
test_sidecar_cache, test_instanced_geometry, test_federation remain
(backend-agnostic).
- IfcViewer's CMakeLists drops OpenGL, Qt::OpenGL, Qt::Widgets — none
of the surviving translation units reach for them.
Build flag plumbing:
- BUILD_BONSAIVIEWER now auto-enables BUILD_BONSAIVIEWER_WGPU since
SceneLoader requires the wgpu lib for its WgpuViewportWindow* arg.
- The wgpu subprojects add_subdirectory ahead of the GL one so
IfcViewerWgpu exists when IfcViewer's link evaluates.
- src/ifcviewer-minimal subdir reference removed from cmake/CMakeLists.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -121,7 +121,11 @@ set_target_properties(BonsaiViewer PROPERTIES
|
||||
)
|
||||
|
||||
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
|
||||
IfcViewerWgpu
|
||||
Qt${QT_VERSION}::Core
|
||||
Qt${QT_VERSION}::CorePrivate
|
||||
Qt${QT_VERSION}::Gui
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#include "../ifcviewer/AppSettings.h"
|
||||
#include "../ifcviewer/Federation.h"
|
||||
#include "../ifcviewer/ViewportWindow.h"
|
||||
#include "../ifcviewer-wgpu/WgpuViewportWindow.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(), &ViewportWindow::frameStatsUpdated, this,
|
||||
[this](const ViewportWindow::FrameStats& s) {
|
||||
connect(viewport_widget_->viewport(), &WgpuViewportWindow::frameStatsUpdated, this,
|
||||
[this](const WgpuViewportWindow::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(), &ViewportWindow::objectPicked,
|
||||
connect(viewport_widget_->viewport(), &WgpuViewportWindow::objectPicked,
|
||||
this, [this](uint32_t object_id) {
|
||||
session_state_->setSelectedObjectId(object_id);
|
||||
session_state_->notifySelectionChanged();
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
#include "Measurement.h"
|
||||
|
||||
#include "ViewportWindow.h"
|
||||
#include "WgpuViewportWindow.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <Eigen/Dense>
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
namespace {
|
||||
|
||||
double meshLocalVolume(const ViewportWindow::MeshTriangles& tris) {
|
||||
double meshLocalVolume(const WgpuViewportWindow::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(ViewportWindow& vp,
|
||||
double volumeOfObjects(WgpuViewportWindow& vp,
|
||||
const std::vector<uint32_t>& object_ids) {
|
||||
if (object_ids.empty()) return 0.0;
|
||||
|
||||
@@ -77,14 +77,14 @@ double volumeOfObjects(ViewportWindow& vp,
|
||||
std::unordered_map<uint64_t, std::vector<double>> by_mesh;
|
||||
by_mesh.reserve(object_ids.size());
|
||||
for (uint32_t oid : object_ids) {
|
||||
ViewportWindow::InstanceLookup lk;
|
||||
WgpuViewportWindow::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;
|
||||
ViewportWindow::MeshTriangles tris;
|
||||
WgpuViewportWindow::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(ViewportWindow& vp,
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint32_t, double>>
|
||||
volumesPerObject(ViewportWindow& vp,
|
||||
volumesPerObject(WgpuViewportWindow& vp,
|
||||
const std::vector<uint32_t>& object_ids) {
|
||||
std::vector<std::pair<uint32_t, double>> out;
|
||||
if (object_ids.empty()) return out;
|
||||
@@ -108,9 +108,9 @@ volumesPerObject(ViewportWindow& vp,
|
||||
std::unordered_map<uint64_t, double> mesh_vol_local;
|
||||
mesh_vol_local.reserve(object_ids.size());
|
||||
|
||||
ViewportWindow::MeshTriangles tris;
|
||||
WgpuViewportWindow::MeshTriangles tris;
|
||||
for (uint32_t oid : object_ids) {
|
||||
ViewportWindow::InstanceLookup lk;
|
||||
WgpuViewportWindow::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(ViewportWindow& vp) {
|
||||
void AreaMeasurement::clear(WgpuViewportWindow& vp) {
|
||||
mesh_cache_.clear();
|
||||
selected_.clear();
|
||||
total_area_m2_ = 0.0;
|
||||
@@ -246,7 +246,7 @@ void AreaMeasurement::clear(ViewportWindow& vp) {
|
||||
vp.setOverlayLabels({});
|
||||
}
|
||||
|
||||
void AreaMeasurement::rebuildHighlight(ViewportWindow& vp) {
|
||||
void AreaMeasurement::rebuildHighlight(WgpuViewportWindow& vp) {
|
||||
// Push every selected triangle's three world-space vertices to the
|
||||
// overlay. Mesh-local positions × per-instance composed transform.
|
||||
std::vector<float> world_xyz;
|
||||
@@ -287,7 +287,7 @@ void AreaMeasurement::rebuildHighlight(ViewportWindow& vp) {
|
||||
by_object[object_id].push_back(&sel);
|
||||
}
|
||||
|
||||
std::vector<OverlayRenderer::Label> labels;
|
||||
std::vector<WgpuOverlayRenderer::Label> 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(ViewportWindow& vp) {
|
||||
|
||||
// Centroid → world via the instance's composed transform.
|
||||
const float* M = any.composed_transform;
|
||||
OverlayRenderer::Label lbl;
|
||||
WgpuOverlayRenderer::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(ViewportWindow& vp) {
|
||||
vp.setOverlayLabels(labels);
|
||||
}
|
||||
|
||||
AreaMeasurement::MeshCache* AreaMeasurement::meshCache(ViewportWindow& vp,
|
||||
AreaMeasurement::MeshCache* AreaMeasurement::meshCache(WgpuViewportWindow& 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;
|
||||
|
||||
ViewportWindow::MeshTriangles tris;
|
||||
WgpuViewportWindow::MeshTriangles tris;
|
||||
if (!vp.readbackMeshTriangles(model_id, mesh_id, tris)) return nullptr;
|
||||
|
||||
MeshCache c;
|
||||
@@ -397,8 +397,8 @@ AreaMeasurement::MeshCache* AreaMeasurement::meshCache(ViewportWindow& vp,
|
||||
return &mesh_cache_.emplace(key, std::move(c)).first->second;
|
||||
}
|
||||
|
||||
void AreaMeasurement::onPick(ViewportWindow& vp, int x, int y, bool alt) {
|
||||
ViewportWindow::MeshLocalPick pick;
|
||||
void AreaMeasurement::onPick(WgpuViewportWindow& vp, int x, int y, bool alt) {
|
||||
WgpuViewportWindow::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
|
||||
|
||||
OverlayRenderer::LineGroup makeGroup(std::vector<float> xyz,
|
||||
WgpuOverlayRenderer::LineGroup makeGroup(std::vector<float> xyz,
|
||||
float r, float g, float b,
|
||||
bool dashed = false) {
|
||||
OverlayRenderer::LineGroup gp;
|
||||
WgpuOverlayRenderer::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<float>& xyz,
|
||||
xyz.insert(xyz.end(), b.begin(), b.end());
|
||||
}
|
||||
|
||||
OverlayRenderer::Label makeLabel(const std::array<float, 3>& a,
|
||||
WgpuOverlayRenderer::Label makeLabel(const std::array<float, 3>& a,
|
||||
const std::array<float, 3>& b,
|
||||
const QString& text) {
|
||||
OverlayRenderer::Label lbl;
|
||||
WgpuOverlayRenderer::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 @@ OverlayRenderer::Label makeLabel(const std::array<float, 3>& a,
|
||||
return lbl;
|
||||
}
|
||||
|
||||
void pushDots(ViewportWindow& vp, const std::vector<float>& xyz) {
|
||||
void pushDots(WgpuViewportWindow& vp, const std::vector<float>& xyz) {
|
||||
vp.setOverlayPoints(xyz,
|
||||
/*inner*/ 1.0f, 1.0f, 1.0f, 1.0f,
|
||||
/*size*/ DOT_SIZE,
|
||||
@@ -656,7 +656,7 @@ void pushDots(ViewportWindow& vp, const std::vector<float>& xyz) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void LengthMeasurement::clear(ViewportWindow& vp) {
|
||||
void LengthMeasurement::clear(WgpuViewportWindow& vp) {
|
||||
points_.clear();
|
||||
normals_.clear();
|
||||
vp.setOverlayPoints({}, 0,0,0,0, 0, 0,0,0,0, 0);
|
||||
@@ -665,8 +665,8 @@ void LengthMeasurement::clear(ViewportWindow& vp) {
|
||||
vp.setHudText(QString());
|
||||
}
|
||||
|
||||
void LengthMeasurement::onPick(ViewportWindow& vp, int x, int y, bool /*alt*/) {
|
||||
ViewportWindow::MeshLocalPick pick;
|
||||
void LengthMeasurement::onPick(WgpuViewportWindow& vp, int x, int y, bool /*alt*/) {
|
||||
WgpuViewportWindow::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(ViewportWindow& vp, int x, int y, bool /*alt*/) {
|
||||
rebuildOverlay(vp);
|
||||
}
|
||||
|
||||
void LengthMeasurement::removeLastPoint(ViewportWindow& vp) {
|
||||
void LengthMeasurement::removeLastPoint(WgpuViewportWindow& vp) {
|
||||
if (points_.empty()) return;
|
||||
points_.pop_back();
|
||||
if (!normals_.empty()) normals_.pop_back();
|
||||
rebuildOverlay(vp);
|
||||
}
|
||||
|
||||
void LengthMeasurement::rebuildOverlay(ViewportWindow& vp) {
|
||||
void LengthMeasurement::rebuildOverlay(WgpuViewportWindow& vp) {
|
||||
if (points_.size() == 1 && normals_.size() == 1) {
|
||||
rebuildLaserOverlay(vp);
|
||||
return;
|
||||
@@ -694,8 +694,8 @@ void LengthMeasurement::rebuildOverlay(ViewportWindow& vp) {
|
||||
for (const auto& p : points_) pushDot(pts_xyz, p);
|
||||
pushDots(vp, pts_xyz);
|
||||
|
||||
std::vector<OverlayRenderer::LineGroup> groups;
|
||||
std::vector<OverlayRenderer::Label> labels;
|
||||
std::vector<WgpuOverlayRenderer::LineGroup> groups;
|
||||
std::vector<WgpuOverlayRenderer::Label> 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(ViewportWindow& vp) {
|
||||
void LengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& 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(ViewportWindow& vp) {
|
||||
n[0]*t1[1] - n[1]*t1[0],
|
||||
};
|
||||
|
||||
std::vector<OverlayRenderer::LineGroup> groups;
|
||||
std::vector<OverlayRenderer::Label> labels;
|
||||
std::vector<WgpuOverlayRenderer::LineGroup> groups;
|
||||
std::vector<WgpuOverlayRenderer::Label> 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(ViewportWindow& 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).
|
||||
ViewportWindow::MeshTriangles tris;
|
||||
WgpuViewportWindow::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(ViewportWindow& vp) {
|
||||
wp[1] + NUDGE * n[1],
|
||||
wp[2] + NUDGE * n[2],
|
||||
};
|
||||
ViewportWindow::RaycastHit hit;
|
||||
WgpuViewportWindow::RaycastHit hit;
|
||||
if (vp.raycast(ro, n, hit)) {
|
||||
const double dist = double(hit.distance) + double(NUDGE);
|
||||
const std::array<float, 3> a = {wp[0], wp[1], wp[2]};
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#define IFCINTERFACE_MEASUREMENT_H
|
||||
|
||||
#include <cstddef>
|
||||
#include "ViewportWindow.h"
|
||||
#include "WgpuViewportWindow.h"
|
||||
#include <QString>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
@@ -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(ViewportWindow& vp,
|
||||
double volumeOfObjects(WgpuViewportWindow& vp,
|
||||
const std::vector<uint32_t>& object_ids);
|
||||
|
||||
// Per-object volumes (m³). Same algorithm as volumeOfObjects but
|
||||
@@ -45,11 +45,11 @@ double volumeOfObjects(ViewportWindow& vp,
|
||||
// Used by MainWindow's volume readout to drive both the total HUD and
|
||||
// the per-object overlay labels.
|
||||
std::vector<std::pair<uint32_t, double>>
|
||||
volumesPerObject(ViewportWindow& vp,
|
||||
volumesPerObject(WgpuViewportWindow& vp,
|
||||
const std::vector<uint32_t>& object_ids);
|
||||
|
||||
// Click-to-accumulate area measurement. Each pick resolves the screen
|
||||
// click to a (instance, triangle) using ViewportWindow's primitives,
|
||||
// click to a (instance, triangle) using WgpuViewportWindow'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(ViewportWindow& vp,
|
||||
// separate patches and their areas are summed.
|
||||
//
|
||||
// On every pick the world-space triangles of the running set are pushed
|
||||
// to ViewportWindow::setHighlightTriangles for in-viewport shading.
|
||||
// to WgpuViewportWindow::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(ViewportWindow& vp, int x, int y, bool alt);
|
||||
void onPick(WgpuViewportWindow& vp, int x, int y, bool alt);
|
||||
|
||||
// Wipe all accumulated triangles, per-mesh adjacency caches, and the
|
||||
// viewport overlay.
|
||||
void clear(ViewportWindow& vp);
|
||||
void clear(WgpuViewportWindow& 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<uint64_t, std::vector<uint32_t>> edges;
|
||||
};
|
||||
MeshCache* meshCache(ViewportWindow& vp, uint32_t model_id, uint32_t mesh_id);
|
||||
MeshCache* meshCache(WgpuViewportWindow& 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(ViewportWindow& vp);
|
||||
void rebuildHighlight(WgpuViewportWindow& vp);
|
||||
|
||||
std::unordered_map<uint64_t, MeshCache> mesh_cache_;
|
||||
std::unordered_map<uint64_t, SelectedTri> selected_;
|
||||
@@ -138,15 +138,15 @@ class LengthMeasurement {
|
||||
public:
|
||||
LengthMeasurement();
|
||||
|
||||
void onPick(ViewportWindow& vp, int x, int y, bool alt);
|
||||
void removeLastPoint(ViewportWindow& vp);
|
||||
void clear(ViewportWindow& vp);
|
||||
void onPick(WgpuViewportWindow& vp, int x, int y, bool alt);
|
||||
void removeLastPoint(WgpuViewportWindow& vp);
|
||||
void clear(WgpuViewportWindow& vp);
|
||||
|
||||
size_t pointCount() const { return points_.size(); }
|
||||
|
||||
private:
|
||||
void rebuildOverlay(ViewportWindow& vp);
|
||||
void rebuildLaserOverlay(ViewportWindow& vp);
|
||||
void rebuildOverlay(WgpuViewportWindow& vp);
|
||||
void rebuildLaserOverlay(WgpuViewportWindow& vp);
|
||||
QString formatReadout() const;
|
||||
|
||||
std::vector<std::array<float, 3>> 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).
|
||||
ViewportWindow::MeshLocalPick first_pick_{};
|
||||
WgpuViewportWindow::MeshLocalPick first_pick_{};
|
||||
};
|
||||
|
||||
#endif // IFCINTERFACE_MEASUREMENT_H
|
||||
|
||||
@@ -35,7 +35,7 @@ SessionState::SessionState(QObject* parent)
|
||||
{
|
||||
}
|
||||
|
||||
void SessionState::createLoader(ViewportWindow* viewport) {
|
||||
void SessionState::createLoader(WgpuViewportWindow* viewport) {
|
||||
Q_ASSERT(!loader_);
|
||||
loader_ = new SceneLoader(viewport, this);
|
||||
loader_->setShouldReadSidecar(true);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
class Federation;
|
||||
class SceneLoader;
|
||||
class ViewportWindow;
|
||||
class WgpuViewportWindow;
|
||||
|
||||
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(ViewportWindow* viewport);
|
||||
void createLoader(WgpuViewportWindow* viewport);
|
||||
|
||||
Federation* federation() const { return federation_; }
|
||||
SceneLoader* loader() const { return loader_; }
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#include "../../../ifcviewer/Federation.h"
|
||||
#include "../../../ifcviewer/SceneLoader.h"
|
||||
#include "../../../ifcviewer/SidecarBuilder.h"
|
||||
#include "../../../ifcviewer/ViewportWindow.h"
|
||||
#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h"
|
||||
#include "../../../ifcgeom/Serializer.h"
|
||||
#include "../../../serializers/document_serializer_plugin.h"
|
||||
|
||||
@@ -148,7 +148,7 @@ void removeGroup(SessionState& s, QWidget& host, const QString& group_id) {
|
||||
s.setStatusMessage("Models", "Group removed");
|
||||
}
|
||||
|
||||
void removeModel(SessionState& s, ViewportWindow& vp, QWidget& host, const QString& fed_id) {
|
||||
void removeModel(SessionState& s, WgpuViewportWindow& 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(
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include <cstdint>
|
||||
|
||||
class QWidget;
|
||||
class ViewportWindow;
|
||||
class WgpuViewportWindow;
|
||||
namespace bonsaiviewer { class SessionState; }
|
||||
|
||||
namespace bonsaiviewer::modules::models::commands {
|
||||
@@ -41,7 +41,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, ViewportWindow& vp, QWidget& host, const QString& fed_id);
|
||||
void removeModel(SessionState& s, WgpuViewportWindow& 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
|
||||
|
||||
@@ -220,7 +220,7 @@ private:
|
||||
} // namespace
|
||||
|
||||
ModelsPanel::ModelsPanel(bonsaiviewer::SessionState* session_state,
|
||||
ViewportWindow* viewport,
|
||||
WgpuViewportWindow* viewport,
|
||||
QWidget* parent)
|
||||
: components::Panel("Models", nullptr, parent, true)
|
||||
, session_state_(session_state)
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include "../../components/Panel.h"
|
||||
|
||||
class QTreeView;
|
||||
class ViewportWindow;
|
||||
class WgpuViewportWindow;
|
||||
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,
|
||||
ViewportWindow* viewport,
|
||||
WgpuViewportWindow* 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;
|
||||
ViewportWindow* viewport_ = nullptr;
|
||||
WgpuViewportWindow* viewport_ = nullptr;
|
||||
QTreeView* tree_ = nullptr;
|
||||
FederationItemModel* model_ = nullptr;
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#include "../models/Commands.h"
|
||||
#include "../../../ifcviewer/Federation.h"
|
||||
#include "../../../ifcviewer/SceneLoader.h"
|
||||
#include "../../../ifcviewer/ViewportWindow.h"
|
||||
#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
@@ -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, ViewportWindow& vp) {
|
||||
void clearScene(SessionState& s, WgpuViewportWindow& 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, ViewportWindow& vp) {
|
||||
void resolveCloudModels(SessionState& s, WgpuViewportWindow& vp) {
|
||||
auto* fed = s.federation();
|
||||
QHash<QString, QStringList> connector_to_fed_ids;
|
||||
for (const auto& m : fed->models()) {
|
||||
@@ -99,7 +99,7 @@ void resolveCloudModels(SessionState& s, ViewportWindow& vp) {
|
||||
|
||||
auto* registry = s.connectorRegistry();
|
||||
QPointer<SessionState> sguard(&s);
|
||||
QPointer<ViewportWindow> vguard(&vp);
|
||||
QPointer<WgpuViewportWindow> 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, ViewportWindow& vp, const QString& path) {
|
||||
bool openProjectAt(SessionState& s, QWidget& host, WgpuViewportWindow& 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, ViewportWindow& vp) {
|
||||
bool newProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) {
|
||||
SceneLoader* loader = s.loader();
|
||||
if (loader && loader->isLoading()) {
|
||||
QMessageBox::information(
|
||||
@@ -285,7 +285,7 @@ bool newProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool openProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
bool openProject(SessionState& s, QWidget& host, WgpuViewportWindow& 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, ViewportWindow& vp) {
|
||||
return openProjectAt(s, host, vp, path);
|
||||
}
|
||||
|
||||
bool openProjectPath(SessionState& s, QWidget& host, ViewportWindow& vp, const QString& path) {
|
||||
bool openProjectPath(SessionState& s, QWidget& host, WgpuViewportWindow& vp, const QString& path) {
|
||||
if (path.isEmpty()) return false;
|
||||
return openProjectAt(s, host, vp, path);
|
||||
}
|
||||
|
||||
bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
bool openCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) {
|
||||
SceneLoader* loader = s.loader();
|
||||
if (loader && loader->isLoading()) {
|
||||
QMessageBox::information(
|
||||
@@ -341,7 +341,7 @@ bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
|
||||
QPointer<SessionState> sguard(&s);
|
||||
QPointer<QWidget> hguard(&host);
|
||||
QPointer<ViewportWindow> vguard(&vp);
|
||||
QPointer<WgpuViewportWindow> 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, ViewportWindow& vp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
bool syncCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& 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, ViewportWindow& vp) {
|
||||
|
||||
QPointer<SessionState> sguard(&s);
|
||||
QPointer<QWidget> hguard(&host);
|
||||
QPointer<ViewportWindow> vguard(&vp);
|
||||
QPointer<WgpuViewportWindow> vguard(&vp);
|
||||
const QString current_path = fed->filePath();
|
||||
|
||||
proc->call("pull_ifcfed", fed->manifest(),
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include <QString>
|
||||
|
||||
class QWidget;
|
||||
class ViewportWindow;
|
||||
class WgpuViewportWindow;
|
||||
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, ViewportWindow& vp);
|
||||
bool openProject(SessionState& s, QWidget& host, ViewportWindow& vp);
|
||||
bool newProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp);
|
||||
bool openProject(SessionState& s, QWidget& host, WgpuViewportWindow& 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, ViewportWindow& vp, const QString& path);
|
||||
bool openProjectPath(SessionState& s, QWidget& host, WgpuViewportWindow& 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, ViewportWindow& vp);
|
||||
bool openCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& 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, ViewportWindow& vp);
|
||||
bool syncCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& 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
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
|
||||
#include "../../SessionState.h"
|
||||
#include "../../../ifcviewer/Federation.h"
|
||||
#include "../../../ifcviewer/ViewportWindow.h"
|
||||
#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h"
|
||||
|
||||
namespace bonsaiviewer::modules::viewport::commands {
|
||||
|
||||
void setHome(SessionState& session, ViewportWindow& vp) {
|
||||
void setHome(SessionState& session, WgpuViewportWindow& vp) {
|
||||
auto camera = vp.cameraState();
|
||||
Federation::HomeView home_view;
|
||||
home_view.target = camera.target;
|
||||
@@ -37,7 +37,7 @@ void setHome(SessionState& session, ViewportWindow& vp) {
|
||||
session.setStatusMessage("Camera", "Home view updated");
|
||||
}
|
||||
|
||||
void goHome(SessionState& session, ViewportWindow& vp) {
|
||||
void goHome(SessionState& session, WgpuViewportWindow& 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, ViewportWindow& vp) {
|
||||
session.setStatusMessage("Camera", "Home view restored");
|
||||
}
|
||||
|
||||
void viewSelected(ViewportWindow& vp) {
|
||||
void viewSelected(WgpuViewportWindow& vp) {
|
||||
vp.focusOnSelectedObject();
|
||||
}
|
||||
|
||||
void fly(SessionState& session, ViewportWindow& vp) {
|
||||
void fly(SessionState& session, WgpuViewportWindow& vp) {
|
||||
vp.requestActivate();
|
||||
vp.enterFpsMode();
|
||||
session.setStatusMessage("Mode", "Fly mode active");
|
||||
}
|
||||
|
||||
void toggleSection(SessionState& session, ViewportWindow& vp) {
|
||||
void toggleSection(SessionState& session, WgpuViewportWindow& vp) {
|
||||
vp.toggleSectionTool();
|
||||
session.setStatusMessage("Section",
|
||||
vp.sectionToolActive() ? "Section tool active" : "Section tool off");
|
||||
}
|
||||
|
||||
void clearSection(SessionState& session, ViewportWindow& vp) {
|
||||
void clearSection(SessionState& session, WgpuViewportWindow& vp) {
|
||||
vp.clearSectionPlanes();
|
||||
session.setStatusMessage("Section", "Section planes cleared");
|
||||
}
|
||||
|
||||
void toggleDistance(ViewportWindow& vp) {
|
||||
void toggleDistance(WgpuViewportWindow& vp) {
|
||||
vp.toggleLengthTool();
|
||||
}
|
||||
|
||||
void toggleArea(ViewportWindow& vp) {
|
||||
void toggleArea(WgpuViewportWindow& vp) {
|
||||
vp.toggleAreaTool();
|
||||
}
|
||||
|
||||
void toggleVolume(ViewportWindow& vp) {
|
||||
void toggleVolume(WgpuViewportWindow& vp) {
|
||||
vp.toggleVolumeTool();
|
||||
}
|
||||
|
||||
void hideSelected(ViewportWindow& vp) {
|
||||
void hideSelected(WgpuViewportWindow& vp) {
|
||||
vp.hideSelectedElements();
|
||||
}
|
||||
|
||||
void isolateSelected(ViewportWindow& vp) {
|
||||
void isolateSelected(WgpuViewportWindow& vp) {
|
||||
vp.isolateSelectedElements();
|
||||
}
|
||||
|
||||
void showAll(ViewportWindow& vp) {
|
||||
void showAll(WgpuViewportWindow& vp) {
|
||||
vp.showAllElements();
|
||||
}
|
||||
|
||||
void invertVisibility(ViewportWindow& vp) {
|
||||
void invertVisibility(WgpuViewportWindow& vp) {
|
||||
vp.invertElementVisibility();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,27 +21,27 @@
|
||||
#ifndef IFCINTERFACE_MODULES_VIEWPORT_COMMANDS_H
|
||||
#define IFCINTERFACE_MODULES_VIEWPORT_COMMANDS_H
|
||||
|
||||
class ViewportWindow;
|
||||
class WgpuViewportWindow;
|
||||
namespace bonsaiviewer { class SessionState; }
|
||||
|
||||
namespace bonsaiviewer::modules::viewport::commands {
|
||||
|
||||
void setHome(SessionState& session, ViewportWindow& vp);
|
||||
void goHome(SessionState& session, ViewportWindow& vp);
|
||||
void viewSelected(ViewportWindow& vp);
|
||||
void setHome(SessionState& session, WgpuViewportWindow& vp);
|
||||
void goHome(SessionState& session, WgpuViewportWindow& vp);
|
||||
void viewSelected(WgpuViewportWindow& vp);
|
||||
|
||||
void fly(SessionState& session, ViewportWindow& vp);
|
||||
void toggleSection(SessionState& session, ViewportWindow& vp);
|
||||
void clearSection(SessionState& session, ViewportWindow& vp);
|
||||
void fly(SessionState& session, WgpuViewportWindow& vp);
|
||||
void toggleSection(SessionState& session, WgpuViewportWindow& vp);
|
||||
void clearSection(SessionState& session, WgpuViewportWindow& vp);
|
||||
|
||||
void toggleDistance(ViewportWindow& vp);
|
||||
void toggleArea(ViewportWindow& vp);
|
||||
void toggleVolume(ViewportWindow& vp);
|
||||
void toggleDistance(WgpuViewportWindow& vp);
|
||||
void toggleArea(WgpuViewportWindow& vp);
|
||||
void toggleVolume(WgpuViewportWindow& vp);
|
||||
|
||||
void hideSelected(ViewportWindow& vp);
|
||||
void isolateSelected(ViewportWindow& vp);
|
||||
void showAll(ViewportWindow& vp);
|
||||
void invertVisibility(ViewportWindow& vp);
|
||||
void hideSelected(WgpuViewportWindow& vp);
|
||||
void isolateSelected(WgpuViewportWindow& vp);
|
||||
void showAll(WgpuViewportWindow& vp);
|
||||
void invertVisibility(WgpuViewportWindow& vp);
|
||||
|
||||
} // namespace bonsaiviewer::modules::viewport::commands
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
#include "Panel.h"
|
||||
|
||||
#include "../../../ifcviewer/ViewportWindow.h"
|
||||
#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h"
|
||||
|
||||
#include <QFrame>
|
||||
#include <QVBoxLayout>
|
||||
@@ -47,7 +47,7 @@ ViewportPanel::ViewportPanel(QWidget* parent)
|
||||
frame_layout->setContentsMargins(0, 0, 0, 0);
|
||||
frame_layout->setSpacing(0);
|
||||
|
||||
viewport_ = new ViewportWindow();
|
||||
viewport_ = new WgpuViewportWindow();
|
||||
viewport_container_ = QWidget::createWindowContainer(viewport_, frame);
|
||||
viewport_container_->setMinimumSize(400, 300);
|
||||
viewport_container_->setFocusPolicy(Qt::StrongFocus);
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class ViewportWindow;
|
||||
class WgpuViewportWindow;
|
||||
|
||||
namespace bonsaiviewer::modules::viewport {
|
||||
|
||||
@@ -33,10 +33,10 @@ class ViewportPanel : public QWidget {
|
||||
public:
|
||||
explicit ViewportPanel(QWidget* parent = nullptr);
|
||||
|
||||
ViewportWindow* viewport() const { return viewport_; }
|
||||
WgpuViewportWindow* viewport() const { return viewport_; }
|
||||
|
||||
private:
|
||||
ViewportWindow* viewport_ = nullptr;
|
||||
WgpuViewportWindow* viewport_ = nullptr;
|
||||
QWidget* viewport_container_ = nullptr;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
#include "../../SessionState.h"
|
||||
#include "../../../ifcviewer/Federation.h"
|
||||
#include "../../../ifcviewer/SceneLoader.h"
|
||||
#include "../../../ifcviewer/ViewportWindow.h"
|
||||
#include "../../../ifcviewer/OverlayRenderer.h"
|
||||
#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h"
|
||||
#include "../../../ifcviewer-wgpu/WgpuOverlayRenderer.h"
|
||||
#include "../../Measurement.h"
|
||||
|
||||
#include <Eigen/Dense>
|
||||
@@ -36,7 +36,7 @@
|
||||
namespace bonsaiviewer::modules::viewport {
|
||||
|
||||
ViewportView::ViewportView(bonsaiviewer::SessionState* session_state,
|
||||
ViewportWindow* viewport,
|
||||
WgpuViewportWindow* viewport,
|
||||
QObject* parent)
|
||||
: QObject(parent)
|
||||
, session_state_(session_state)
|
||||
@@ -59,54 +59,54 @@ ViewportView::ViewportView(bonsaiviewer::SessionState* session_state,
|
||||
connect(session_state_, &SessionState::modelGeometryReady, this, [this](uint32_t) { refresh(); });
|
||||
|
||||
// Measurement tools — input-driven, share the View's lifetime.
|
||||
connect(viewport_, &ViewportWindow::surfacePickedInTool, this,
|
||||
connect(viewport_, &WgpuViewportWindow::surfacePickedInTool, this,
|
||||
[this](int x, int y, int modifiers) {
|
||||
const bool alt = (modifiers & Qt::AltModifier) != 0;
|
||||
switch (viewport_->toolMode()) {
|
||||
case ViewportWindow::ToolMode::Area:
|
||||
case WgpuViewportWindow::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 ViewportWindow::ToolMode::Length:
|
||||
case WgpuViewportWindow::ToolMode::Length:
|
||||
length_measurement_->onPick(*viewport_, x, y, alt);
|
||||
break;
|
||||
case ViewportWindow::ToolMode::None:
|
||||
case ViewportWindow::ToolMode::Volume:
|
||||
case WgpuViewportWindow::ToolMode::NoTool:
|
||||
case WgpuViewportWindow::ToolMode::Volume:
|
||||
break;
|
||||
}
|
||||
});
|
||||
connect(viewport_, &ViewportWindow::toolModeChanged, this,
|
||||
[this](ViewportWindow::ToolMode mode) {
|
||||
connect(viewport_, &WgpuViewportWindow::toolModeChanged, this,
|
||||
[this](WgpuViewportWindow::ToolMode mode) {
|
||||
area_measurement_->clear(*viewport_);
|
||||
length_measurement_->clear(*viewport_);
|
||||
switch (mode) {
|
||||
case ViewportWindow::ToolMode::None:
|
||||
case WgpuViewportWindow::ToolMode::NoTool:
|
||||
viewport_->setHudText(QString());
|
||||
viewport_->setOverlayLabels({});
|
||||
session_state_->setStatusMessage("Measure", "Measurement tool off");
|
||||
break;
|
||||
case ViewportWindow::ToolMode::Length:
|
||||
case WgpuViewportWindow::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 ViewportWindow::ToolMode::Area:
|
||||
case WgpuViewportWindow::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 ViewportWindow::ToolMode::Volume:
|
||||
case WgpuViewportWindow::ToolMode::Volume:
|
||||
session_state_->setStatusMessage("Measure", "Volume tool: click / box-select objects, Esc exits");
|
||||
updateVolumeReadout();
|
||||
break;
|
||||
}
|
||||
});
|
||||
connect(viewport_, &ViewportWindow::toolBackspacePressed, this, [this]() {
|
||||
if (viewport_->toolMode() == ViewportWindow::ToolMode::Length) {
|
||||
connect(viewport_, &WgpuViewportWindow::toolBackspacePressed, this, [this]() {
|
||||
if (viewport_->toolMode() == WgpuViewportWindow::ToolMode::Length) {
|
||||
length_measurement_->removeLastPoint(*viewport_);
|
||||
}
|
||||
});
|
||||
connect(viewport_, &ViewportWindow::objectPicked, this, [this](uint32_t) {
|
||||
connect(viewport_, &WgpuViewportWindow::objectPicked, this, [this](uint32_t) {
|
||||
updateVolumeReadout();
|
||||
});
|
||||
|
||||
@@ -194,7 +194,7 @@ void ViewportView::maybeGuessFederatedFalseOrigin(uint32_t mid) {
|
||||
}
|
||||
|
||||
void ViewportView::updateVolumeReadout() {
|
||||
if (viewport_->toolMode() != ViewportWindow::ToolMode::Volume) return;
|
||||
if (viewport_->toolMode() != WgpuViewportWindow::ToolMode::Volume) return;
|
||||
|
||||
const auto& sel = viewport_->selection().selectionIds();
|
||||
if (sel.empty()) {
|
||||
@@ -207,13 +207,13 @@ void ViewportView::updateVolumeReadout() {
|
||||
const auto per_obj = volumesPerObject(*viewport_, ids);
|
||||
|
||||
double total = 0.0;
|
||||
std::vector<OverlayRenderer::Label> labels;
|
||||
std::vector<WgpuOverlayRenderer::Label> 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;
|
||||
OverlayRenderer::Label lbl;
|
||||
WgpuOverlayRenderer::Label lbl;
|
||||
const QVector3D c = (mn + mx) * 0.5f;
|
||||
lbl.world_pos[0] = c.x();
|
||||
lbl.world_pos[1] = c.y();
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include <memory>
|
||||
|
||||
namespace bonsaiviewer { class SessionState; }
|
||||
class ViewportWindow;
|
||||
class WgpuViewportWindow;
|
||||
class AreaMeasurement;
|
||||
class LengthMeasurement;
|
||||
|
||||
@@ -44,7 +44,7 @@ class ViewportView : public QObject {
|
||||
|
||||
public:
|
||||
explicit ViewportView(bonsaiviewer::SessionState* session_state,
|
||||
ViewportWindow* viewport,
|
||||
WgpuViewportWindow* viewport,
|
||||
QObject* parent = nullptr);
|
||||
~ViewportView() override;
|
||||
|
||||
@@ -57,7 +57,7 @@ private:
|
||||
void updateVolumeReadout();
|
||||
|
||||
bonsaiviewer::SessionState* session_state_ = nullptr;
|
||||
ViewportWindow* viewport_ = nullptr;
|
||||
WgpuViewportWindow* viewport_ = nullptr;
|
||||
std::unique_ptr<AreaMeasurement> area_measurement_;
|
||||
std::unique_ptr<LengthMeasurement> length_measurement_;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user