Interface mockup 16

This commit is contained in:
Dion Moult
2026-05-11 12:18:38 +10:00
parent 6df9aeda2c
commit a4f7db45cd
6 changed files with 186 additions and 30 deletions
+8 -17
View File
@@ -1690,16 +1690,6 @@ ViewportWindow::CameraState ViewportWindow::cameraState() const {
void ViewportWindow::keyPressEvent(QKeyEvent* event) {
const int key = event->key();
// Shift+F toggles FPS/fly mode. Checked before auto-repeat filtering so
// a held Shift+F doesn't thrash between modes.
if (key == Qt::Key_F
&& (event->modifiers() & Qt::ShiftModifier)
&& !event->isAutoRepeat()) {
if (camera_mode_ == CameraMode::Fps) exitFpsMode();
else enterFpsMode();
return;
}
if (camera_mode_ == CameraMode::Fps) {
if (key == Qt::Key_Escape && !event->isAutoRepeat()) {
exitFpsMode();
@@ -1731,7 +1721,6 @@ void ViewportWindow::keyPressEvent(QKeyEvent* event) {
}
// Plain F (no modifiers): focus camera on the currently selected object.
// Shift+F is FPS-mode toggle and was handled above.
if (key == Qt::Key_F
&& event->modifiers() == Qt::NoModifier
&& !event->isAutoRepeat()) {
@@ -3678,14 +3667,16 @@ void ViewportWindow::handleMousePress(QMouseEvent* e) {
// gesture. Tool-mode LMB defers pick handling to surfacePickedInTool
// on release for tools that consume clicks (Area / Length); Volume is
// passive and routes input through normal selection.
const bool tool_consumes_clicks =
(tool_mode_ == ToolMode::Area || tool_mode_ == ToolMode::Length);
if (e->button() == Qt::LeftButton && !tool_consumes_clicks) {
press_pick_id_ = pickObjectAt(e->pos().x(), e->pos().y());
if (e->button() == Qt::LeftButton) {
box_select_start_pos_ = e->pos();
box_select_current_pos_ = e->pos();
box_select_press_mods_ = e->modifiers();
box_select_armed_ = true;
const bool tool_consumes_clicks =
(tool_mode_ == ToolMode::Area || tool_mode_ == ToolMode::Length);
if (!tool_consumes_clicks) {
press_pick_id_ = pickObjectAt(e->pos().x(), e->pos().y());
box_select_press_mods_ = e->modifiers();
box_select_armed_ = true;
}
}
}
void ViewportWindow::handleMouseRelease(QMouseEvent* e) {
+1 -1
View File
@@ -314,6 +314,7 @@ public:
void setModelCoordinateOperation(uint32_t model_id, const Eigen::Matrix4d& matrix_meters);
void setModelTransformation(uint32_t model_id, const Eigen::Matrix4d& matrix_meters);
void setBackgroundColor(const QColor& color);
void enterFpsMode();
// Selection. The viewport owns a SelectionState that tracks both
// the multi-set and the "active" (last single-clicked) id. External
@@ -601,7 +602,6 @@ private:
// way one long frame only produces a single catch-up step instead of
// also missing a QTimer tick.
enum class CameraMode { Orbit, Fps };
void enterFpsMode();
void exitFpsMode();
void fpsIntegrate(); // called from render()
void recenterFpsCursor();
+2
View File
@@ -80,6 +80,8 @@ set(INTERFACE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/modules/viewport/Panel.h
${CMAKE_CURRENT_SOURCE_DIR}/modules/viewport/Controller.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modules/viewport/Controller.h
${CMAKE_CURRENT_SOURCE_DIR}/../ifcviewer-full/Measurement.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../ifcviewer-full/Measurement.h
${CMAKE_CURRENT_SOURCE_DIR}/interface_resources.qrc
)
+36 -12
View File
@@ -49,6 +49,7 @@
#include <QIcon>
#include <QLabel>
#include <QMessageBox>
#include <QShortcut>
#include <QSignalBlocker>
#include <QStackedWidget>
#include <QStatusBar>
@@ -91,6 +92,33 @@ void MainWindow::setupChrome() {
setDockOptions(QMainWindow::AllowNestedDocks |
QMainWindow::AllowTabbedDocks |
QMainWindow::GroupedDragging);
auto bind_shortcut = [this](const QKeySequence& sequence, auto fn) {
auto* shortcut = new QShortcut(sequence, this);
shortcut->setContext(Qt::WindowShortcut);
connect(shortcut, &QShortcut::activated, this, fn);
};
bind_shortcut(QKeySequence("Ctrl+Shift+L"), [this]() {
viewport_controller_->toggleDistanceMode();
});
bind_shortcut(QKeySequence("Ctrl+Shift+A"), [this]() {
viewport_controller_->toggleAreaMode();
});
bind_shortcut(QKeySequence("Ctrl+Shift+V"), [this]() {
viewport_controller_->toggleVolumeMode();
});
bind_shortcut(QKeySequence(Qt::SHIFT | Qt::Key_F), [this]() {
viewport_controller_->setFlyMode();
});
bind_shortcut(QKeySequence(Qt::Key_H), [this]() {
viewport_controller_->hideSelectedElements();
});
bind_shortcut(QKeySequence(Qt::SHIFT | Qt::Key_H), [this]() {
viewport_controller_->isolateSelectedElements();
});
bind_shortcut(QKeySequence(Qt::ALT | Qt::Key_H), [this]() {
viewport_controller_->showAllElements();
});
updateWindowTitle();
}
@@ -211,18 +239,14 @@ QWidget* MainWindow::buildNavigateRibbonPage() {
viewport_widget_->viewport()->projectionOrtho() ? "Ortho" : "Perspective");
});
auto* orbit_mode = components::buttons::makeButton("Orbit", ":/icons/rotate-camera-right.svg", this);
connect(orbit_mode, &QToolButton::clicked, this, [this]() {
session_state_->setStatusMessage("Mode", "Orbit mode active");
});
auto* fly_mode = components::buttons::makeButton("Fly", ":/icons/drone.svg", this);
connect(fly_mode, &QToolButton::clicked, this, [this]() {
session_state_->setStatusMessage("Mode", "Fly mode coming soon");
viewport_controller_->setFlyMode();
});
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", {orbit_mode, fly_mode}, this));
row->addWidget(components::buttons::makeButtonGroup("MODE", {fly_mode}, this));
row->addStretch(1);
return page;
}
@@ -236,15 +260,15 @@ QWidget* MainWindow::buildInspectRibbonPage() {
auto* hide_selected = components::buttons::makeButton("Hide", ":/icons/eye-closed.svg", this);
connect(hide_selected, &QToolButton::clicked, this, [this]() {
session_state_->setStatusMessage("Selection", "Hide selected coming soon");
viewport_controller_->hideSelectedElements();
});
auto* isolate_selected = components::buttons::makeButton("Isolate", ":/icons/eye-solid.svg", this);
connect(isolate_selected, &QToolButton::clicked, this, [this]() {
session_state_->setStatusMessage("Selection", "Isolate selected coming soon");
viewport_controller_->isolateSelectedElements();
});
auto* show_all = components::buttons::makeButton("Show All", ":/icons/eye.svg", this);
connect(show_all, &QToolButton::clicked, this, [this]() {
session_state_->setStatusMessage("Selection", "Show all coming soon");
viewport_controller_->showAllElements();
});
auto* invert_selection = components::buttons::makeButton("Invert", ":/icons/intersect.svg", this);
connect(invert_selection, &QToolButton::clicked, this, [this]() {
@@ -253,15 +277,15 @@ QWidget* MainWindow::buildInspectRibbonPage() {
auto* distance = components::buttons::makeButton("Distance", ":/icons/select-edge3d.svg", this);
connect(distance, &QToolButton::clicked, this, [this]() {
session_state_->setStatusMessage("Measure", "Distance coming soon");
viewport_controller_->toggleDistanceMode();
});
auto* area = components::buttons::makeButton("Area", ":/icons/select-face3d.svg", this);
connect(area, &QToolButton::clicked, this, [this]() {
session_state_->setStatusMessage("Measure", "Area coming soon");
viewport_controller_->toggleAreaMode();
});
auto* volume = components::buttons::makeButton("Volume", ":/icons/select-point3d.svg", this);
connect(volume, &QToolButton::clicked, this, [this]() {
session_state_->setStatusMessage("Measure", "Volume coming soon");
viewport_controller_->toggleVolumeMode();
});
row->addWidget(components::buttons::makeButtonGroup("SELECTION", {hide_selected, isolate_selected, show_all, invert_selection}, this));
@@ -26,8 +26,13 @@
#include "../../../ifcviewer/Federation.h"
#include "../../../ifcviewer/SceneLoader.h"
#include "../../../ifcviewer/ViewportWindow.h"
#include "../../../ifcviewer/OverlayRenderer.h"
#include "../../../ifcviewer-full/Measurement.h"
#include <Eigen/Dense>
#include <QVector3D>
#include <vector>
namespace ifcinterface::modules::viewport {
@@ -37,6 +42,8 @@ ViewportController::ViewportController(ifcinterface::SessionState* session_state
: QObject(parent)
, session_state_(session_state)
, viewport_(viewport)
, area_measurement_(std::make_unique<AreaMeasurement>())
, length_measurement_(std::make_unique<LengthMeasurement>())
{
Federation* federation = session_state_->federation();
SceneLoader* loader = session_state_->loader();
@@ -91,8 +98,60 @@ ViewportController::ViewportController(ifcinterface::SessionState* session_state
applyModelVisibility(mid);
maybeGuessFederatedFalseOrigin(mid);
});
connect(viewport_, &ViewportWindow::surfacePickedInTool, this,
[this](int x, int y, int modifiers) {
const bool alt = (modifiers & Qt::AltModifier) != 0;
switch (viewport_->toolMode()) {
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 ViewportWindow::ToolMode::Length:
length_measurement_->onPick(*viewport_, x, y, alt);
break;
case ViewportWindow::ToolMode::None:
case ViewportWindow::ToolMode::Volume:
break;
}
});
connect(viewport_, &ViewportWindow::toolModeChanged, this,
[this](ViewportWindow::ToolMode mode) {
area_measurement_->clear(*viewport_);
length_measurement_->clear(*viewport_);
switch (mode) {
case ViewportWindow::ToolMode::None:
viewport_->setHudText(QString());
viewport_->setOverlayLabels({});
session_state_->setStatusMessage("Measure", "Measurement tool off");
break;
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 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 ViewportWindow::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) {
length_measurement_->removeLastPoint(*viewport_);
}
});
connect(viewport_, &ViewportWindow::objectPicked, this, [this](uint32_t) {
updateVolumeReadout();
});
}
ViewportController::~ViewportController() = default;
void ViewportController::applyCoordinateOperation(uint32_t mid) {
SceneLoader* loader = session_state_->loader();
Eigen::Matrix4d matrix = Eigen::Matrix4d::Identity();
@@ -170,6 +229,72 @@ void ViewportController::goHomeView() {
session_state_->setStatusMessage("Camera", "Home view restored");
}
void ViewportController::setFlyMode() {
viewport_->requestActivate();
viewport_->enterFpsMode();
session_state_->setStatusMessage("Mode", "Fly mode active");
}
void ViewportController::toggleDistanceMode() {
viewport_->toggleLengthTool();
}
void ViewportController::toggleAreaMode() {
viewport_->toggleAreaTool();
}
void ViewportController::toggleVolumeMode() {
viewport_->toggleVolumeTool();
}
void ViewportController::hideSelectedElements() {
viewport_->hideSelectedElements();
}
void ViewportController::isolateSelectedElements() {
viewport_->isolateSelectedElements();
}
void ViewportController::showAllElements() {
viewport_->showAllElements();
}
void ViewportController::updateVolumeReadout() {
if (viewport_->toolMode() != ViewportWindow::ToolMode::Volume) return;
const auto& sel = viewport_->selection().selectionIds();
if (sel.empty()) {
viewport_->setHudText(QString());
viewport_->setOverlayLabels({});
return;
}
std::vector<uint32_t> ids(sel.begin(), sel.end());
const auto per_obj = volumesPerObject(*viewport_, ids);
double total = 0.0;
std::vector<OverlayRenderer::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;
const QVector3D c = (mn + mx) * 0.5f;
lbl.world_pos[0] = c.x();
lbl.world_pos[1] = c.y();
lbl.world_pos[2] = c.z();
lbl.text = QString::number(v, 'f', 4) + "";
labels.push_back(std::move(lbl));
}
viewport_->setHudText(QString("Volume: %1 m³ (%2 object%3)")
.arg(total, 0, 'f', 4)
.arg(per_obj.size())
.arg(per_obj.size() == 1 ? "" : "s"));
viewport_->setOverlayLabels(labels);
}
void ViewportController::maybeGuessFederatedFalseOrigin(uint32_t mid) {
Federation* federation = session_state_->federation();
SceneLoader* loader = session_state_->loader();
@@ -22,9 +22,12 @@
#define IFCINTERFACE_PANELS_VIEWPORT_CONTROLLER_H
#include <QObject>
#include <memory>
namespace ifcinterface { class SessionState; }
class ViewportWindow;
class AreaMeasurement;
class LengthMeasurement;
namespace ifcinterface::modules::viewport {
@@ -35,19 +38,30 @@ public:
explicit ViewportController(ifcinterface::SessionState* session_state,
ViewportWindow* viewport,
QObject* parent = nullptr);
~ViewportController() override;
void applyFederatedFalseOrigin();
void setHomeView();
void goHomeView();
void setFlyMode();
void toggleDistanceMode();
void toggleAreaMode();
void toggleVolumeMode();
void hideSelectedElements();
void isolateSelectedElements();
void showAllElements();
private:
void applyCoordinateOperation(uint32_t mid);
void applyModelTransformation(uint32_t mid);
void applyModelVisibility(uint32_t mid);
void maybeGuessFederatedFalseOrigin(uint32_t mid);
void updateVolumeReadout();
ifcinterface::SessionState* session_state_ = nullptr;
ViewportWindow* viewport_ = nullptr;
std::unique_ptr<AreaMeasurement> area_measurement_;
std::unique_ptr<LengthMeasurement> length_measurement_;
};
} // namespace ifcinterface::modules::viewport