mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-18 22:33:33 +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 kHizResolutionDefault = 256;
|
||||||
constexpr int kHizResolutionFloor = 64;
|
constexpr int kHizResolutionFloor = 64;
|
||||||
constexpr const char* kHizEnabledKey = "viewport/hiz_enabled";
|
constexpr const char* kHizEnabledKey = "viewport/hiz_enabled";
|
||||||
|
constexpr const char* kNavPresetKey = "viewport/nav_preset";
|
||||||
}
|
}
|
||||||
|
|
||||||
AppSettings& AppSettings::instance() {
|
AppSettings& AppSettings::instance() {
|
||||||
@@ -181,6 +182,17 @@ void AppSettings::setHizEnabled(bool value) {
|
|||||||
emit hizEnabledChanged(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() {
|
void AppSettings::load() {
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
geometry_library_ = settings.value(kGeometryLibraryKey, kGeometryLibraryDefault).toString();
|
geometry_library_ = settings.value(kGeometryLibraryKey, kGeometryLibraryDefault).toString();
|
||||||
@@ -203,6 +215,17 @@ void AppSettings::load() {
|
|||||||
hiz_resolution_ = settings.value(kHizResolutionKey, kHizResolutionDefault).toInt();
|
hiz_resolution_ = settings.value(kHizResolutionKey, kHizResolutionDefault).toInt();
|
||||||
if (hiz_resolution_ < kHizResolutionFloor) hiz_resolution_ = kHizResolutionFloor;
|
if (hiz_resolution_ < kHizResolutionFloor) hiz_resolution_ = kHizResolutionFloor;
|
||||||
hiz_enabled_ = settings.value(kHizEnabledKey, true).toBool();
|
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() {
|
void AppSettings::persist() {
|
||||||
@@ -218,4 +241,5 @@ void AppSettings::persist() {
|
|||||||
settings.setValue(kLod1PixelThresholdKey, lod1_pixel_threshold_);
|
settings.setValue(kLod1PixelThresholdKey, lod1_pixel_threshold_);
|
||||||
settings.setValue(kHizResolutionKey, hiz_resolution_);
|
settings.setValue(kHizResolutionKey, hiz_resolution_);
|
||||||
settings.setValue(kHizEnabledKey, hiz_enabled_);
|
settings.setValue(kHizEnabledKey, hiz_enabled_);
|
||||||
|
settings.setValue(kNavPresetKey, static_cast<int>(nav_preset_));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,21 @@
|
|||||||
class AppSettings : public QObject {
|
class AppSettings : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
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();
|
static AppSettings& instance();
|
||||||
|
|
||||||
QString geometryLibrary() const;
|
QString geometryLibrary() const;
|
||||||
@@ -89,6 +104,10 @@ public:
|
|||||||
bool hizEnabled() const;
|
bool hizEnabled() const;
|
||||||
void setHizEnabled(bool value);
|
void setHizEnabled(bool value);
|
||||||
|
|
||||||
|
// Navigation preset (see NavPreset enum above).
|
||||||
|
NavPreset navPreset() const;
|
||||||
|
void setNavPreset(NavPreset value);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void geometryLibraryChanged(const QString& value);
|
void geometryLibraryChanged(const QString& value);
|
||||||
void showStatsChanged(bool value);
|
void showStatsChanged(bool value);
|
||||||
@@ -101,6 +120,7 @@ signals:
|
|||||||
void lod1PixelThresholdChanged(double value);
|
void lod1PixelThresholdChanged(double value);
|
||||||
void hizResolutionChanged(int value);
|
void hizResolutionChanged(int value);
|
||||||
void hizEnabledChanged(bool value);
|
void hizEnabledChanged(bool value);
|
||||||
|
void navPresetChanged(NavPreset value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AppSettings();
|
AppSettings();
|
||||||
@@ -118,6 +138,7 @@ private:
|
|||||||
double lod1_pixel_threshold_ = 30.0;
|
double lod1_pixel_threshold_ = 30.0;
|
||||||
int hiz_resolution_ = 256;
|
int hiz_resolution_ = 256;
|
||||||
bool hiz_enabled_ = true;
|
bool hiz_enabled_ = true;
|
||||||
|
NavPreset nav_preset_ = NavPreset::Blender;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // APPSETTINGS_H
|
#endif // APPSETTINGS_H
|
||||||
|
|||||||
@@ -3572,9 +3572,17 @@ void ViewportWindow::handleMousePress(QMouseEvent* e) {
|
|||||||
box_select_armed_ = false;
|
box_select_armed_ = false;
|
||||||
box_select_active_ = false;
|
box_select_active_ = false;
|
||||||
press_pick_id_ = 0;
|
press_pick_id_ = 0;
|
||||||
if (e->button() == Qt::MiddleButton) {
|
// Show the pivot indicator while the user holds the navigation
|
||||||
setPivotIndicatorVisible(true);
|
// button(s) for the active preset — Blender uses MMB for both
|
||||||
requestUpdate();
|
// 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) {
|
if (section_tool_active_ && e->button() == Qt::LeftButton) {
|
||||||
// First try to grab an existing plane's arrow gizmo.
|
// First try to grab an existing plane's arrow gizmo.
|
||||||
@@ -3684,7 +3692,9 @@ void ViewportWindow::handleMouseRelease(QMouseEvent* e) {
|
|||||||
press_pick_id_ = 0;
|
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;
|
active_button_ = Qt::NoButton;
|
||||||
if (was_navigating && pivot_indicator_visible_) {
|
if (was_navigating && pivot_indicator_visible_) {
|
||||||
setPivotIndicatorVisible(false);
|
setPivotIndicatorVisible(false);
|
||||||
@@ -3749,26 +3759,40 @@ void ViewportWindow::handleMouseMove(QMouseEvent* e) {
|
|||||||
|
|
||||||
QPoint delta = e->pos() - last_mouse_pos_;
|
QPoint delta = e->pos() - last_mouse_pos_;
|
||||||
last_mouse_pos_ = e->pos();
|
last_mouse_pos_ = e->pos();
|
||||||
if (active_button_ == Qt::MiddleButton) {
|
|
||||||
if (e->modifiers() & Qt::ShiftModifier) {
|
// Dispatch nav drag against the active preset. Modifier matching
|
||||||
const float pan_speed = camera_distance_ * 0.002f;
|
// is exact on the Shift bit only — the only modifier any of our
|
||||||
// Derive screen-right and screen-up from the actual camera basis
|
// presets uses — so toggling Shift mid-drag flips between orbit
|
||||||
// rather than yaw/pitch alone — the latter assumed up = world +Z,
|
// and pan when both share a button (Blender / Revit).
|
||||||
// which breaks at top/bottom views where updateCamera switches
|
const NavBindings nb = currentNavBindings();
|
||||||
// the lookAt up vector to world +Y.
|
const bool shift_now = (e->modifiers() & Qt::ShiftModifier) != 0;
|
||||||
const QVector3D forward = (camera_target_ - camera_eye_).normalized();
|
const bool shift_for_pan = (nb.pan_mods & Qt::ShiftModifier) != 0;
|
||||||
const QVector3D up_ref = (std::abs(camera_pitch_) >= 89.0f)
|
const bool shift_for_orb = (nb.orbit_mods & Qt::ShiftModifier) != 0;
|
||||||
? QVector3D(0, 1, 0)
|
const bool is_pan = (active_button_ == nb.pan_button)
|
||||||
: QVector3D(0, 0, 1);
|
&& (shift_now == shift_for_pan);
|
||||||
const QVector3D right = QVector3D::crossProduct(forward, up_ref).normalized();
|
const bool is_orbit = !is_pan
|
||||||
const QVector3D up = QVector3D::crossProduct(right, forward).normalized();
|
&& (active_button_ == nb.orbit_button)
|
||||||
camera_target_ -= right * delta.x() * pan_speed;
|
&& (shift_now == shift_for_orb);
|
||||||
camera_target_ += up * delta.y() * pan_speed;
|
|
||||||
} else {
|
if (is_pan) {
|
||||||
camera_yaw_ -= delta.x() * 0.3f;
|
const float pan_speed = camera_distance_ * 0.002f;
|
||||||
camera_pitch_ += delta.y() * 0.3f;
|
// Derive screen-right and screen-up from the actual camera basis
|
||||||
camera_pitch_ = qBound(-89.0f, camera_pitch_, 89.0f);
|
// 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();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4249,6 +4273,25 @@ void ViewportWindow::toggleVolumeTool() {
|
|||||||
setToolMode(tool_mode_ == ToolMode::Volume ? ToolMode::None : ToolMode::Volume);
|
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,
|
void ViewportWindow::setHighlightTriangles(const std::vector<float>& world_xyz,
|
||||||
float r, float g, float b, float a) {
|
float r, float g, float b, float a) {
|
||||||
if (!gl_initialized_) return;
|
if (!gl_initialized_) return;
|
||||||
|
|||||||
@@ -431,6 +431,17 @@ protected:
|
|||||||
bool event(QEvent* event) override;
|
bool event(QEvent* event) override;
|
||||||
|
|
||||||
private:
|
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 {
|
enum class PendingOpType {
|
||||||
UploadMeshChunk,
|
UploadMeshChunk,
|
||||||
UploadInstanceChunk,
|
UploadInstanceChunk,
|
||||||
|
|||||||
Reference in New Issue
Block a user