From bba2f3081641600977a5b3e256a86d700d6782f6 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 9 May 2026 22:04:00 +1000 Subject: [PATCH] ifcviewer: multi-selection + box-select with active highlight MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SelectionState (new) owns the multi-set, the "active" id (last single- clicked), and a per-object_id flags SSBO bound at binding=3. Main shader reads sel_flags[v_object_id] for the in-set tint and a separate u_active_id uniform for a stronger tint on the active. Click semantics: plain replaces, Shift/Ctrl toggles. LMB-drag past 5px boxes the rect through a pick-pass readback — plain replaces, Shift adds, Ctrl removes; box-select preserves the active. Drag promotes regardless of start point so a press on geometry doesn't disqualify it. Sidecar fast-path bulk-loads instances, so noteObjectId is also called from the apply path — without it the flags buffer was sized to 1 slot while object_ids were in the 100k+ range and the in-set bit was lost. Co-Authored-By: Claude Opus 4.7 --- src/ifcviewer-full/MainWindow.cpp | 24 +++- src/ifcviewer/OverlayRenderer.cpp | 99 +++++++++++-- src/ifcviewer/OverlayRenderer.h | 10 ++ src/ifcviewer/Selection.cpp | 184 ++++++++++++++++++++++++ src/ifcviewer/Selection.h | 126 +++++++++++++++++ src/ifcviewer/ViewportWindow.cpp | 225 ++++++++++++++++++++++++++---- src/ifcviewer/ViewportWindow.h | 34 ++++- 7 files changed, 659 insertions(+), 43 deletions(-) create mode 100644 src/ifcviewer/Selection.cpp create mode 100644 src/ifcviewer/Selection.h diff --git a/src/ifcviewer-full/MainWindow.cpp b/src/ifcviewer-full/MainWindow.cpp index 4ac9225985..252278dced 100644 --- a/src/ifcviewer-full/MainWindow.cpp +++ b/src/ifcviewer-full/MainWindow.cpp @@ -911,20 +911,32 @@ void MainWindow::onAllLoadsFinished() { } void MainWindow::onObjectPicked(uint32_t object_id) { - viewport_->setSelectedObjectId(object_id); - + // Signal originates from the viewport's SelectionState — viewport + // selection is already authoritative. Pushing setSelectedObjectId + // back here would clobber a multi-select set with {object_id}. auto it = tree_items_.find(object_id); if (it != tree_items_.end()) { element_tree_->blockSignals(true); element_tree_->setCurrentItem(it->second); element_tree_->blockSignals(false); + } else { + // No active (e.g. selection cleared, or active id is in a model + // whose tree node hasn't materialised) — drop the tree highlight + // so the panel doesn't lie about what's active. + element_tree_->blockSignals(true); + element_tree_->setCurrentItem(nullptr); + element_tree_->blockSignals(false); } populateProperties(object_id); - if (object_id != 0) { - const double v = volumeOfObjects(*viewport_, {object_id}); - qInfo("Volume of object %u: %.6f m^3", object_id, v); + // Volume readout: report for the full selection so multi-select + // matches the highlighted set. + const auto& selection = viewport_->selection().selectionIds(); + if (!selection.empty()) { + std::vector ids(selection.begin(), selection.end()); + const double v = volumeOfObjects(*viewport_, ids); + qInfo("Volume of %zu selected object(s): %.6f m^3", ids.size(), v); } } @@ -933,6 +945,8 @@ void MainWindow::onTreeSelectionChanged() { if (items.isEmpty()) return; uint32_t object_id = items.first()->data(0, Qt::UserRole).toUInt(); + // Tree stays single-select per UX choice — clicking a tree item + // replaces the viewport's multi-set with just that item. viewport_->setSelectedObjectId(object_id); populateProperties(object_id); } diff --git a/src/ifcviewer/OverlayRenderer.cpp b/src/ifcviewer/OverlayRenderer.cpp index e477e73717..228b2ee49c 100644 --- a/src/ifcviewer/OverlayRenderer.cpp +++ b/src/ifcviewer/OverlayRenderer.cpp @@ -382,6 +382,10 @@ void OverlayRenderer::setOverlayLabels(const std::vector