diff --git a/src/ifcviewer/ViewportWindow.cpp b/src/ifcviewer/ViewportWindow.cpp index 09f8bac763..8e91d8dbe9 100644 --- a/src/ifcviewer/ViewportWindow.cpp +++ b/src/ifcviewer/ViewportWindow.cpp @@ -1547,6 +1547,20 @@ void ViewportWindow::showAllElements() { visibility_.showAll(); } +void ViewportWindow::invertElementVisibility() { + std::unordered_set inverted; + for (const auto& [mid, m] : models_gpu_) { + if (m.hidden) continue; + for (const InstanceCpu& inst : m.instances) { + if (inst.object_id == 0) continue; + if (!visibility_.isHidden(inst.object_id)) { + inverted.insert(inst.object_id); + } + } + } + visibility_.setHidden(inverted); +} + void ViewportWindow::setCamera(float tx, float ty, float tz, float dist, float yaw, float pitch) { camera_target_ = QVector3D(tx, ty, tz); @@ -1641,16 +1655,34 @@ void ViewportWindow::frameAabb(const QVector3D& mn, const QVector3D& mx, void ViewportWindow::focusOnSelectedObject() { if (camera_mode_ == CameraMode::Fps) return; - // F frames the *active* object so it's predictable across multi-select - // states. Framing the union of every selected member would be more - // expansive but ambiguous in a large set. - const uint32_t target = selection_.activeObjectId(); - QVector3D mn, mx; - if (!computeObjectAabb(target, mn, mx)) { + const auto& sel = selection_.selectionIds(); + if (sel.empty()) { qDebug("Focus: no object selected or no AABB available"); return; } - frameAabb(mn, mx, 1.30f); // a bit of headroom around small objects + + bool found = false; + QVector3D lo(std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max()); + QVector3D hi(-std::numeric_limits::max(), + -std::numeric_limits::max(), + -std::numeric_limits::max()); + for (uint32_t object_id : sel) { + QVector3D mn, mx; + if (!computeObjectAabb(object_id, mn, mx)) continue; + for (int a = 0; a < 3; ++a) { + if (mn[a] < lo[a]) lo[a] = mn[a]; + if (mx[a] > hi[a]) hi[a] = mx[a]; + } + found = true; + } + + if (!found) { + qDebug("Focus: no object selected or no AABB available"); + return; + } + frameAabb(lo, hi, 1.30f); } void ViewportWindow::viewAll() { @@ -1758,23 +1790,6 @@ void ViewportWindow::keyPressEvent(QKeyEvent* event) { } return; } - // K toggles the section tool. - if (key == Qt::Key_K - && event->modifiers() == Qt::NoModifier - && !event->isAutoRepeat()) { - toggleSectionTool(); - return; - } - // Shift+K clears every section plane (and the selection). - if (key == Qt::Key_K - && event->modifiers() == Qt::ShiftModifier - && !event->isAutoRepeat()) { - clearSectionPlanes(); - section_plane_selected_ = -1; - section_drag_active_ = false; - section_drag_index_ = -1; - return; - } // While the section tool is active: Esc exits the tool, Delete removes // the selected plane. if (section_tool_active_) { diff --git a/src/ifcviewer/ViewportWindow.h b/src/ifcviewer/ViewportWindow.h index cb1d7f577f..bfabc16bbf 100644 --- a/src/ifcviewer/ViewportWindow.h +++ b/src/ifcviewer/ViewportWindow.h @@ -351,6 +351,9 @@ public: // Model-level `hidden` flags are not touched — a hidden model stays // hidden, matching the user's expectation of "show all *elements*". void showAllElements(); + // Flip element-level visibility across every live element in visible + // models. Model-hidden objects are skipped and remain model-hidden. + void invertElementVisibility(); // Extended pick: returns the object id, world-space hit point, and // world-space surface normal at (x, y). Renders the same pick pass as @@ -399,10 +402,9 @@ public: void setBenchmarkFrames(int n); QString cameraString() const; - // Move camera_target_ to the selected object's world-AABB centroid and - // dolly camera_distance_ so the object's bounding sphere fits the - // current viewport. Yaw/pitch are preserved. No-op if no object is - // selected or its AABB is unknown. + // Move camera_target_ to the selected set's world-AABB centroid and + // dolly camera_distance_ so the union fits the current viewport. + // Yaw/pitch are preserved. No-op if no selected object has an AABB. void focusOnSelectedObject(); // Frame the union of all finalized models. No-op if the scene is empty. void viewAll(); diff --git a/src/interface/MainWindow.cpp b/src/interface/MainWindow.cpp index bdb88f1d05..87aaf25e70 100644 --- a/src/interface/MainWindow.cpp +++ b/src/interface/MainWindow.cpp @@ -110,6 +110,12 @@ void MainWindow::setupChrome() { bind_shortcut(QKeySequence(Qt::SHIFT | Qt::Key_F), [this]() { viewport_controller_->setFlyMode(); }); + bind_shortcut(QKeySequence(Qt::Key_K), [this]() { + viewport_controller_->toggleSectionMode(); + }); + bind_shortcut(QKeySequence(Qt::SHIFT | Qt::Key_K), [this]() { + viewport_controller_->clearSectionPlanes(); + }); bind_shortcut(QKeySequence(Qt::Key_H), [this]() { viewport_controller_->hideSelectedElements(); }); @@ -212,7 +218,7 @@ QWidget* MainWindow::buildNavigateRibbonPage() { }); auto* view_selected = components::buttons::makeButton("View Selected", ":/icons/cube-scan-solid.svg", this); connect(view_selected, &QToolButton::clicked, this, [this]() { - if (viewport_widget_) viewport_widget_->viewport()->focusOnSelectedObject(); + viewport_controller_->focusSelectedObject(); }); auto* plan_view = components::buttons::makeButton("Plan", ":/icons/planimetry.svg", this); @@ -243,10 +249,14 @@ QWidget* MainWindow::buildNavigateRibbonPage() { connect(fly_mode, &QToolButton::clicked, this, [this]() { viewport_controller_->setFlyMode(); }); + auto* section_mode = components::buttons::makeButton("Section", ":/icons/cube-cut-with-curve.svg", this); + connect(section_mode, &QToolButton::clicked, this, [this]() { + viewport_controller_->toggleSectionMode(); + }); row->addWidget(components::buttons::makeButtonGroup("CAMERA", {set_home, go_home, view_all, view_selected}, this)); row->addWidget(components::buttons::makeButtonGroup("ORIENTATION", {plan_view, front_view, side_view, align_object, projection_button}, this)); - row->addWidget(components::buttons::makeButtonGroup("MODE", {fly_mode}, this)); + row->addWidget(components::buttons::makeButtonGroup("MODE", {fly_mode, section_mode}, this)); row->addStretch(1); return page; } @@ -272,7 +282,7 @@ QWidget* MainWindow::buildInspectRibbonPage() { }); auto* invert_selection = components::buttons::makeButton("Invert", ":/icons/intersect.svg", this); connect(invert_selection, &QToolButton::clicked, this, [this]() { - session_state_->setStatusMessage("Selection", "Invert selection coming soon"); + viewport_controller_->invertSelection(); }); auto* distance = components::buttons::makeButton("Distance", ":/icons/select-edge3d.svg", this); diff --git a/src/interface/icons/cube-cut-with-curve.svg b/src/interface/icons/cube-cut-with-curve.svg new file mode 100644 index 0000000000..2e609d5365 --- /dev/null +++ b/src/interface/icons/cube-cut-with-curve.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/interface/icons/rotate-camera-right.svg b/src/interface/icons/rotate-camera-right.svg deleted file mode 100644 index fca1c4e747..0000000000 --- a/src/interface/icons/rotate-camera-right.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/interface/interface_resources.qrc b/src/interface/interface_resources.qrc index feb89ef781..1925486895 100644 --- a/src/interface/interface_resources.qrc +++ b/src/interface/interface_resources.qrc @@ -22,8 +22,8 @@ icons/building.svg icons/cellar.svg icons/perspective-view.svg - icons/rotate-camera-right.svg icons/drone.svg + icons/cube-cut-with-curve.svg icons/folder-plus.svg icons/folder-minus.svg icons/folder.svg diff --git a/src/interface/modules/viewport/Controller.cpp b/src/interface/modules/viewport/Controller.cpp index edfe8fb04f..8fd0b67dfa 100644 --- a/src/interface/modules/viewport/Controller.cpp +++ b/src/interface/modules/viewport/Controller.cpp @@ -235,6 +235,20 @@ void ViewportController::setFlyMode() { session_state_->setStatusMessage("Mode", "Fly mode active"); } +void ViewportController::toggleSectionMode() { + viewport_->toggleSectionTool(); + if (viewport_->sectionToolActive()) { + session_state_->setStatusMessage("Section", "Section tool active"); + } else { + session_state_->setStatusMessage("Section", "Section tool off"); + } +} + +void ViewportController::clearSectionPlanes() { + viewport_->clearSectionPlanes(); + session_state_->setStatusMessage("Section", "Section planes cleared"); +} + void ViewportController::toggleDistanceMode() { viewport_->toggleLengthTool(); } @@ -247,6 +261,10 @@ void ViewportController::toggleVolumeMode() { viewport_->toggleVolumeTool(); } +void ViewportController::focusSelectedObject() { + viewport_->focusOnSelectedObject(); +} + void ViewportController::hideSelectedElements() { viewport_->hideSelectedElements(); } @@ -259,6 +277,10 @@ void ViewportController::showAllElements() { viewport_->showAllElements(); } +void ViewportController::invertSelection() { + viewport_->invertElementVisibility(); +} + void ViewportController::updateVolumeReadout() { if (viewport_->toolMode() != ViewportWindow::ToolMode::Volume) return; diff --git a/src/interface/modules/viewport/Controller.h b/src/interface/modules/viewport/Controller.h index 56c39d2032..20f569d797 100644 --- a/src/interface/modules/viewport/Controller.h +++ b/src/interface/modules/viewport/Controller.h @@ -44,12 +44,16 @@ public: void setHomeView(); void goHomeView(); void setFlyMode(); + void toggleSectionMode(); + void clearSectionPlanes(); void toggleDistanceMode(); void toggleAreaMode(); void toggleVolumeMode(); + void focusSelectedObject(); void hideSelectedElements(); void isolateSelectedElements(); void showAllElements(); + void invertSelection(); private: void applyCoordinateOperation(uint32_t mid);