mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 02:23:34 +00:00
Configurable navigation bindings
This commit is contained in:
@@ -42,6 +42,7 @@ constexpr const char* kHizResolutionKey = "viewport/hiz_resolution";
|
||||
constexpr int kHizResolutionDefault = 256;
|
||||
constexpr int kHizResolutionFloor = 64;
|
||||
constexpr const char* kHizEnabledKey = "viewport/hiz_enabled";
|
||||
constexpr const char* kNavPresetKey = "viewport/nav_preset";
|
||||
}
|
||||
|
||||
AppSettings& AppSettings::instance() {
|
||||
@@ -181,6 +182,17 @@ void AppSettings::setHizEnabled(bool value) {
|
||||
emit hizEnabledChanged(value);
|
||||
}
|
||||
|
||||
AppSettings::NavPreset AppSettings::navPreset() const {
|
||||
return nav_preset_;
|
||||
}
|
||||
|
||||
void AppSettings::setNavPreset(NavPreset value) {
|
||||
if (nav_preset_ == value) return;
|
||||
nav_preset_ = value;
|
||||
persist();
|
||||
emit navPresetChanged(value);
|
||||
}
|
||||
|
||||
void AppSettings::load() {
|
||||
QSettings settings;
|
||||
geometry_library_ = settings.value(kGeometryLibraryKey, kGeometryLibraryDefault).toString();
|
||||
@@ -203,6 +215,17 @@ void AppSettings::load() {
|
||||
hiz_resolution_ = settings.value(kHizResolutionKey, kHizResolutionDefault).toInt();
|
||||
if (hiz_resolution_ < kHizResolutionFloor) hiz_resolution_ = kHizResolutionFloor;
|
||||
hiz_enabled_ = settings.value(kHizEnabledKey, true).toBool();
|
||||
{
|
||||
const int raw = settings.value(kNavPresetKey,
|
||||
static_cast<int>(NavPreset::Blender)).toInt();
|
||||
// Clamp to known values so a stale config doesn't drop us into
|
||||
// an undefined preset slot.
|
||||
if (raw < 0 || raw > static_cast<int>(NavPreset::Revit)) {
|
||||
nav_preset_ = NavPreset::Blender;
|
||||
} else {
|
||||
nav_preset_ = static_cast<NavPreset>(raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppSettings::persist() {
|
||||
@@ -218,4 +241,5 @@ void AppSettings::persist() {
|
||||
settings.setValue(kLod1PixelThresholdKey, lod1_pixel_threshold_);
|
||||
settings.setValue(kHizResolutionKey, hiz_resolution_);
|
||||
settings.setValue(kHizEnabledKey, hiz_enabled_);
|
||||
settings.setValue(kNavPresetKey, static_cast<int>(nav_preset_));
|
||||
}
|
||||
|
||||
@@ -29,6 +29,21 @@
|
||||
class AppSettings : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
// Navigation preset. Selects which mouse button (+ optional Shift)
|
||||
// drives orbit and pan. Selection is unaffected and stays on LMB
|
||||
// for every preset — these three intentionally don't take LMB so
|
||||
// click + box-select remain available without modifier gymnastics.
|
||||
//
|
||||
// Blender — Orbit MMB, Pan Shift+MMB (current default)
|
||||
// Rhino — Orbit RMB, Pan Shift+RMB
|
||||
// Revit — Orbit Shift+MMB, Pan MMB
|
||||
enum class NavPreset {
|
||||
Blender = 0,
|
||||
Rhino = 1,
|
||||
Revit = 2,
|
||||
};
|
||||
Q_ENUM(NavPreset)
|
||||
|
||||
static AppSettings& instance();
|
||||
|
||||
QString geometryLibrary() const;
|
||||
@@ -89,6 +104,10 @@ public:
|
||||
bool hizEnabled() const;
|
||||
void setHizEnabled(bool value);
|
||||
|
||||
// Navigation preset (see NavPreset enum above).
|
||||
NavPreset navPreset() const;
|
||||
void setNavPreset(NavPreset value);
|
||||
|
||||
signals:
|
||||
void geometryLibraryChanged(const QString& value);
|
||||
void showStatsChanged(bool value);
|
||||
@@ -101,6 +120,7 @@ signals:
|
||||
void lod1PixelThresholdChanged(double value);
|
||||
void hizResolutionChanged(int value);
|
||||
void hizEnabledChanged(bool value);
|
||||
void navPresetChanged(NavPreset value);
|
||||
|
||||
private:
|
||||
AppSettings();
|
||||
@@ -118,6 +138,7 @@ private:
|
||||
double lod1_pixel_threshold_ = 30.0;
|
||||
int hiz_resolution_ = 256;
|
||||
bool hiz_enabled_ = true;
|
||||
NavPreset nav_preset_ = NavPreset::Blender;
|
||||
};
|
||||
|
||||
#endif // APPSETTINGS_H
|
||||
|
||||
@@ -3572,9 +3572,17 @@ void ViewportWindow::handleMousePress(QMouseEvent* e) {
|
||||
box_select_armed_ = false;
|
||||
box_select_active_ = false;
|
||||
press_pick_id_ = 0;
|
||||
if (e->button() == Qt::MiddleButton) {
|
||||
setPivotIndicatorVisible(true);
|
||||
requestUpdate();
|
||||
// Show the pivot indicator while the user holds the navigation
|
||||
// button(s) for the active preset — Blender uses MMB for both
|
||||
// orbit + pan, Revit too (with Shift swapping the role), Rhino
|
||||
// uses RMB. Either way, the press of that button is what the
|
||||
// user means as "I'm about to navigate".
|
||||
{
|
||||
const NavBindings nb = currentNavBindings();
|
||||
if (e->button() == nb.orbit_button || e->button() == nb.pan_button) {
|
||||
setPivotIndicatorVisible(true);
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
if (section_tool_active_ && e->button() == Qt::LeftButton) {
|
||||
// First try to grab an existing plane's arrow gizmo.
|
||||
@@ -3684,7 +3692,9 @@ void ViewportWindow::handleMouseRelease(QMouseEvent* e) {
|
||||
press_pick_id_ = 0;
|
||||
}
|
||||
|
||||
const bool was_navigating = (active_button_ == Qt::MiddleButton);
|
||||
const NavBindings nb = currentNavBindings();
|
||||
const bool was_navigating =
|
||||
(active_button_ == nb.orbit_button || active_button_ == nb.pan_button);
|
||||
active_button_ = Qt::NoButton;
|
||||
if (was_navigating && pivot_indicator_visible_) {
|
||||
setPivotIndicatorVisible(false);
|
||||
@@ -3749,26 +3759,40 @@ void ViewportWindow::handleMouseMove(QMouseEvent* e) {
|
||||
|
||||
QPoint delta = e->pos() - last_mouse_pos_;
|
||||
last_mouse_pos_ = e->pos();
|
||||
if (active_button_ == Qt::MiddleButton) {
|
||||
if (e->modifiers() & Qt::ShiftModifier) {
|
||||
const float pan_speed = camera_distance_ * 0.002f;
|
||||
// Derive screen-right and screen-up from the actual camera basis
|
||||
// rather than yaw/pitch alone — the latter assumed up = world +Z,
|
||||
// which breaks at top/bottom views where updateCamera switches
|
||||
// the lookAt up vector to world +Y.
|
||||
const QVector3D forward = (camera_target_ - camera_eye_).normalized();
|
||||
const QVector3D up_ref = (std::abs(camera_pitch_) >= 89.0f)
|
||||
? QVector3D(0, 1, 0)
|
||||
: QVector3D(0, 0, 1);
|
||||
const QVector3D right = QVector3D::crossProduct(forward, up_ref).normalized();
|
||||
const QVector3D up = QVector3D::crossProduct(right, forward).normalized();
|
||||
camera_target_ -= right * delta.x() * pan_speed;
|
||||
camera_target_ += up * delta.y() * pan_speed;
|
||||
} else {
|
||||
camera_yaw_ -= delta.x() * 0.3f;
|
||||
camera_pitch_ += delta.y() * 0.3f;
|
||||
camera_pitch_ = qBound(-89.0f, camera_pitch_, 89.0f);
|
||||
}
|
||||
|
||||
// Dispatch nav drag against the active preset. Modifier matching
|
||||
// is exact on the Shift bit only — the only modifier any of our
|
||||
// presets uses — so toggling Shift mid-drag flips between orbit
|
||||
// and pan when both share a button (Blender / Revit).
|
||||
const NavBindings nb = currentNavBindings();
|
||||
const bool shift_now = (e->modifiers() & Qt::ShiftModifier) != 0;
|
||||
const bool shift_for_pan = (nb.pan_mods & Qt::ShiftModifier) != 0;
|
||||
const bool shift_for_orb = (nb.orbit_mods & Qt::ShiftModifier) != 0;
|
||||
const bool is_pan = (active_button_ == nb.pan_button)
|
||||
&& (shift_now == shift_for_pan);
|
||||
const bool is_orbit = !is_pan
|
||||
&& (active_button_ == nb.orbit_button)
|
||||
&& (shift_now == shift_for_orb);
|
||||
|
||||
if (is_pan) {
|
||||
const float pan_speed = camera_distance_ * 0.002f;
|
||||
// Derive screen-right and screen-up from the actual camera basis
|
||||
// rather than yaw/pitch alone — the latter assumed up = world +Z,
|
||||
// which breaks at top/bottom views where updateCamera switches
|
||||
// the lookAt up vector to world +Y.
|
||||
const QVector3D forward = (camera_target_ - camera_eye_).normalized();
|
||||
const QVector3D up_ref = (std::abs(camera_pitch_) >= 89.0f)
|
||||
? QVector3D(0, 1, 0)
|
||||
: QVector3D(0, 0, 1);
|
||||
const QVector3D right = QVector3D::crossProduct(forward, up_ref).normalized();
|
||||
const QVector3D up = QVector3D::crossProduct(right, forward).normalized();
|
||||
camera_target_ -= right * delta.x() * pan_speed;
|
||||
camera_target_ += up * delta.y() * pan_speed;
|
||||
requestUpdate();
|
||||
} else if (is_orbit) {
|
||||
camera_yaw_ -= delta.x() * 0.3f;
|
||||
camera_pitch_ += delta.y() * 0.3f;
|
||||
camera_pitch_ = qBound(-89.0f, camera_pitch_, 89.0f);
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
@@ -4249,6 +4273,25 @@ void ViewportWindow::toggleVolumeTool() {
|
||||
setToolMode(tool_mode_ == ToolMode::Volume ? ToolMode::None : ToolMode::Volume);
|
||||
}
|
||||
|
||||
ViewportWindow::NavBindings ViewportWindow::currentNavBindings() const {
|
||||
using NP = AppSettings::NavPreset;
|
||||
switch (AppSettings::instance().navPreset()) {
|
||||
case NP::Rhino:
|
||||
// Orbit RMB, Pan Shift+RMB.
|
||||
return { Qt::RightButton, Qt::NoModifier,
|
||||
Qt::RightButton, Qt::ShiftModifier };
|
||||
case NP::Revit:
|
||||
// Orbit Shift+MMB, Pan MMB.
|
||||
return { Qt::MiddleButton, Qt::ShiftModifier,
|
||||
Qt::MiddleButton, Qt::NoModifier };
|
||||
case NP::Blender:
|
||||
default:
|
||||
// Orbit MMB, Pan Shift+MMB.
|
||||
return { Qt::MiddleButton, Qt::NoModifier,
|
||||
Qt::MiddleButton, Qt::ShiftModifier };
|
||||
}
|
||||
}
|
||||
|
||||
void ViewportWindow::setHighlightTriangles(const std::vector<float>& world_xyz,
|
||||
float r, float g, float b, float a) {
|
||||
if (!gl_initialized_) return;
|
||||
|
||||
@@ -431,6 +431,17 @@ protected:
|
||||
bool event(QEvent* event) override;
|
||||
|
||||
private:
|
||||
// Resolved mouse-binding for the active AppSettings::NavPreset.
|
||||
// Selection always stays on LMB; only orbit and pan move around
|
||||
// (presets are picked so neither lands on plain LMB).
|
||||
struct NavBindings {
|
||||
Qt::MouseButton orbit_button;
|
||||
Qt::KeyboardModifiers orbit_mods;
|
||||
Qt::MouseButton pan_button;
|
||||
Qt::KeyboardModifiers pan_mods;
|
||||
};
|
||||
NavBindings currentNavBindings() const;
|
||||
|
||||
enum class PendingOpType {
|
||||
UploadMeshChunk,
|
||||
UploadInstanceChunk,
|
||||
|
||||
Reference in New Issue
Block a user