diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt
index b3fff2e403..ab93d16cac 100644
--- a/src/Gui/CMakeLists.txt
+++ b/src/Gui/CMakeLists.txt
@@ -932,12 +932,14 @@ SET(View3D_SRCS
SOURCE_GROUP("View3D" FILES ${View3D_SRCS})
SET(Navigation_CPP_SRCS
+ Navigation/NavigationStateChart.cpp
Navigation/NavigationStyle.cpp
Navigation/NavigationStylePyImp.cpp
Navigation/InventorNavigationStyle.cpp
Navigation/CADNavigationStyle.cpp
Navigation/RevitNavigationStyle.cpp
Navigation/BlenderNavigationStyle.cpp
+ Navigation/SiemensNXNavigationStyle.cpp
Navigation/SolidWorksNavigationStyle.cpp
Navigation/MayaGestureNavigationStyle.cpp
Navigation/OpenCascadeNavigationStyle.cpp
@@ -950,11 +952,13 @@ SET(Navigation_CPP_SRCS
)
SET(Navigation_SRCS
${Navigation_CPP_SRCS}
+ Navigation/NavigationStateChart.h
Navigation/NavigationStyle.h
Navigation/NavigationStyle.pyi
Navigation/GestureNavigationStyle.h
Navigation/NavigationAnimator.h
Navigation/NavigationAnimation.h
+ Navigation/SiemensNXNavigationStyle.h
)
SOURCE_GROUP("Navigation" FILES ${Navigation_SRCS})
diff --git a/src/Gui/Navigation/NavigationStateChart.cpp b/src/Gui/Navigation/NavigationStateChart.cpp
new file mode 100644
index 0000000000..70a12f8f67
--- /dev/null
+++ b/src/Gui/Navigation/NavigationStateChart.cpp
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+/***************************************************************************
+ * Copyright (c) 2025 Werner Mayer *
+ * *
+ * This file is part of FreeCAD. *
+ * *
+ * FreeCAD is free software: you can redistribute it and/or modify it *
+ * under the terms of the GNU Lesser General Public License as *
+ * published by the Free Software Foundation, either version 2.1 of the *
+ * License, or (at your option) any later version. *
+ * *
+ * FreeCAD is distributed in the hope that it will be useful, but *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+ * Lesser General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU Lesser General Public *
+ * License along with FreeCAD. If not, see *
+ * . *
+ * *
+ **************************************************************************/
+
+
+#include "PreCompiled.h"
+#include
+#include
+#include
+
+#include "Camera.h"
+#include "NavigationStateChart.h"
+#include "View3DInventorViewer.h"
+
+
+using namespace Gui;
+namespace sc = boost::statechart;
+using NS = NavigationStateChart;
+
+NS::Event::Event() : flags(new Flags)
+{}
+
+bool NS::Event::isMouseButtonEvent() const
+{
+ return this->inventor_event->isOfType(SoMouseButtonEvent::getClassTypeId());
+}
+
+const SoMouseButtonEvent* NS::Event::asMouseButtonEvent() const
+{
+ return static_cast(this->inventor_event);
+}
+
+bool NS::Event::isPress(Button button) const
+{
+ return SoMouseButtonEvent::isButtonPressEvent(this->inventor_event, button);
+}
+
+bool NS::Event::isRelease(Button button) const
+{
+ return SoMouseButtonEvent::isButtonReleaseEvent(this->inventor_event, button);
+}
+
+bool NS::Event::isKeyPress(Key key) const
+{
+ return SoKeyboardEvent::isKeyPressEvent(this->inventor_event, key);
+}
+
+bool NS::Event::isKeyRelease(Key key) const
+{
+ return SoKeyboardEvent::isKeyReleaseEvent(this->inventor_event, key);
+}
+
+bool NS::Event::isKeyboardEvent() const
+{
+ return this->inventor_event->isOfType(SoKeyboardEvent::getClassTypeId());
+}
+
+const SoKeyboardEvent* NS::Event::asKeyboardEvent() const
+{
+ return static_cast(this->inventor_event);
+}
+
+bool NS::Event::isLocation2Event() const
+{
+ return this->inventor_event->isOfType(SoLocation2Event::getClassTypeId());
+}
+
+const SoLocation2Event* NS::Event::asLocation2Event() const
+{
+ return static_cast(this->inventor_event);
+}
+
+bool NS::Event::isMotion3Event() const
+{
+ return this->inventor_event->isOfType(SoMotion3Event::getClassTypeId());
+}
+
+const SoMotion3Event* NS::Event::asMotion3Event() const
+{
+ return static_cast(this->inventor_event);
+}
+
+bool NS::Event::isDownButton(unsigned int state) const
+{
+ return mbstate() == state;
+}
+
+bool NS::Event::isDownNoButton() const
+{
+ return mbstate() == 0;
+}
+
+bool NS::Event::isDownButton1() const
+{
+ return (mbstate() & BUTTON1DOWN) == BUTTON1DOWN;
+}
+
+bool NS::Event::isDownButton2() const
+{
+ return (mbstate() & BUTTON2DOWN) == BUTTON2DOWN;
+}
+
+bool NS::Event::isDownButton3() const
+{
+ return (mbstate() & BUTTON3DOWN) == BUTTON3DOWN;
+}
+
+bool NS::Event::isDownControl() const
+{
+ return (kbstate() & CTRLDOWN) == CTRLDOWN;
+}
+
+bool NS::Event::isDownShift() const
+{
+ return (kbstate() & SHIFTDOWN) == SHIFTDOWN;
+}
+
+bool NS::Event::isDownAlt() const
+{
+ return (kbstate() & ALTDOWN) == ALTDOWN;
+}
+
+
+/* TRANSLATOR Gui::NavigationStateChart */
+
+TYPESYSTEM_SOURCE_ABSTRACT(Gui::NavigationStateChart, Gui::UserNavigationStyle)
+
+NavigationStateChart::NavigationStateChart()
+{}
+
+NavigationStateChart::~NavigationStateChart()
+{
+ naviMachine.reset();
+}
+
+SbBool NavigationStateChart::processSoEvent(const SoEvent * const ev)
+{
+ // Events when in "ready-to-seek" mode are ignored, except those
+ // which influence the seek mode itself -- these are handled further
+ // up the inheritance hierarchy.
+ if (this->isSeekMode()) {
+ return inherited::processSoEvent(ev);
+ }
+
+ // Switch off viewing mode (Bug #0000911)
+ if (!this->isSeekMode() && !this->isAnimating() && this->isViewing()) {
+ this->setViewing(false); // by default disable viewing mode to render the scene
+ }
+
+ // Mismatches in state of the modifier keys happens if the user
+ // presses or releases them outside the viewer window.
+ syncModifierKeys(ev);
+
+ // give the nodes in the foreground root the chance to handle events (e.g color bar)
+ if (!viewer->isEditing()) {
+ if (handleEventInForeground(ev)) {
+ return true;
+ }
+ }
+
+ NS::Event smev;
+ smev.inventor_event = ev;
+
+ // Spaceball/joystick handling
+ if (ev->isOfType(SoMotion3Event::getClassTypeId())){
+ smev.flags->processed = true;
+ this->processMotionEvent(static_cast(ev));
+ return true;
+ }
+
+ // Keyboard handling
+ if (ev->isOfType(SoKeyboardEvent::getClassTypeId())) {
+ const auto event = static_cast(ev);
+ smev.flags->processed = processKeyboardEvent(event);
+ }
+
+ if (smev.isMouseButtonEvent()) {
+ const auto button = smev.asMouseButtonEvent()->getButton();
+ const SbBool press = smev.asMouseButtonEvent()->getState() == SoButtonEvent::DOWN;
+ switch (button) {
+ case SoMouseButtonEvent::BUTTON1:
+ this->button1down = press;
+ break;
+ case SoMouseButtonEvent::BUTTON2:
+ this->button2down = press;
+ break;
+ case SoMouseButtonEvent::BUTTON3:
+ this->button3down = press;
+ break;
+ default:
+ break;
+ }
+ }
+
+ smev.modifiers =
+ (this->button1down ? NS::Event::BUTTON1DOWN : 0) |
+ (this->button2down ? NS::Event::BUTTON2DOWN : 0) |
+ (this->button3down ? NS::Event::BUTTON3DOWN : 0) |
+ (this->ctrldown ? NS::Event::CTRLDOWN : 0) |
+ (this->shiftdown ? NS::Event::SHIFTDOWN : 0) |
+ (this->altdown ? NS::Event::ALTDOWN : 0);
+
+ if (!smev.flags->processed) {
+ this->naviMachine->process_event(smev);
+ }
+
+ if (!smev.flags->propagated && !smev.flags->processed) {
+ return inherited::processSoEvent(ev);
+ }
+
+ return smev.flags->processed;
+}
diff --git a/src/Gui/Navigation/NavigationStateChart.h b/src/Gui/Navigation/NavigationStateChart.h
new file mode 100644
index 0000000000..9e60708d1a
--- /dev/null
+++ b/src/Gui/Navigation/NavigationStateChart.h
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+/***************************************************************************
+ * Copyright (c) 2025 Werner Mayer *
+ * *
+ * This file is part of FreeCAD. *
+ * *
+ * FreeCAD is free software: you can redistribute it and/or modify it *
+ * under the terms of the GNU Lesser General Public License as *
+ * published by the Free Software Foundation, either version 2.1 of the *
+ * License, or (at your option) any later version. *
+ * *
+ * FreeCAD is distributed in the hope that it will be useful, but *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+ * Lesser General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU Lesser General Public *
+ * License along with FreeCAD. If not, see *
+ * . *
+ * *
+ **************************************************************************/
+
+
+#ifndef GUI_NAVIGATIONSTATECHART_H
+#define GUI_NAVIGATIONSTATECHART_H
+
+#include
+#include
+
+// NOLINTBEGIN(cppcoreguidelines-avoid*, readability-avoid-const-params-in-decls)
+namespace Gui
+{
+
+class NaviStateMachine;
+
+class GuiExport NavigationStateChart : public UserNavigationStyle {
+ using inherited = UserNavigationStyle;
+
+ TYPESYSTEM_HEADER_WITH_OVERRIDE();
+
+public:
+ struct Event: public boost::statechart::event
+ {
+ using Button = SoMouseButtonEvent::Button;
+ using Key = SoKeyboardEvent::Key;
+
+ Event();
+ bool isMouseButtonEvent() const;
+ const SoMouseButtonEvent* asMouseButtonEvent() const;
+ bool isPress(Button button) const;
+ bool isRelease(Button button) const;
+ bool isKeyPress(Key key) const;
+ bool isKeyRelease(Key key) const;
+ bool isKeyboardEvent() const;
+ const SoKeyboardEvent* asKeyboardEvent() const;
+ bool isLocation2Event() const;
+ const SoLocation2Event* asLocation2Event() const;
+ bool isMotion3Event() const;
+ const SoMotion3Event* asMotion3Event() const;
+ bool isDownButton(unsigned int state) const;
+ bool isDownNoButton() const;
+ bool isDownButton1() const;
+ bool isDownButton2() const;
+ bool isDownButton3() const;
+ bool isDownControl() const;
+ bool isDownShift() const;
+ bool isDownAlt() const;
+
+ enum {
+ // bits: 0-shift-ctrl-alt-0-lmb-mmb-rmb
+ BUTTON1DOWN = 0x00000100,
+ BUTTON2DOWN = 0x00000001,
+ BUTTON3DOWN = 0x00000010,
+ CTRLDOWN = 0x00100000,
+ SHIFTDOWN = 0x01000000,
+ ALTDOWN = 0x00010000,
+ MASKBUTTONS = BUTTON1DOWN | BUTTON2DOWN | BUTTON3DOWN,
+ MASKMODIFIERS = CTRLDOWN | SHIFTDOWN | ALTDOWN
+ };
+
+ const SoEvent* inventor_event{nullptr};
+ unsigned int modifiers{0};
+ unsigned int mbstate() const {return modifiers & MASKBUTTONS;}
+ unsigned int kbstate() const {return modifiers & MASKMODIFIERS;}
+
+ struct Flags{
+ bool processed = false;
+ bool propagated = false;
+ };
+ std::shared_ptr flags;
+ };
+
+ NavigationStateChart();
+ ~NavigationStateChart() override;
+
+protected:
+ SbBool processSoEvent(const SoEvent * const ev) override;
+ std::unique_ptr naviMachine; // NOLINT
+};
+
+class GuiExport NaviStateMachine
+{
+public:
+ NaviStateMachine(const NaviStateMachine&) = delete;
+ NaviStateMachine(NaviStateMachine&&) = delete;
+ NaviStateMachine& operator= (const NaviStateMachine&) = delete;
+ NaviStateMachine& operator= (NaviStateMachine&&) = delete;
+
+ NaviStateMachine() = default;
+ virtual ~NaviStateMachine() = default;
+
+ virtual void process_event(const NavigationStateChart::Event&) = 0;
+};
+
+template< typename T >
+class NaviStateMachineT : public NaviStateMachine
+{
+public:
+ explicit NaviStateMachineT(T* t) : object(t)
+ {
+ object->initiate();
+ }
+
+ ~NaviStateMachineT() override
+ {
+ object.reset();
+ }
+
+ void process_event(const NavigationStateChart::Event& ev) override
+ {
+ object->process_event(ev);
+ }
+
+private:
+ std::unique_ptr object;
+};
+
+} // namespace Gui
+// NOLINTEND(cppcoreguidelines-avoid*, readability-avoid-const-params-in-decls)
+
+#endif // GUI_NAVIGATIONSTATECHART_H
diff --git a/src/Gui/Navigation/NavigationStyle.h b/src/Gui/Navigation/NavigationStyle.h
index be54296471..152b6f030a 100644
--- a/src/Gui/Navigation/NavigationStyle.h
+++ b/src/Gui/Navigation/NavigationStyle.h
@@ -51,6 +51,7 @@ class SoCamera;
class SoSensor;
class SbSphereSheetProjector;
+// NOLINTBEGIN(cppcoreguidelines-avoid*, readability-avoid-const-params-in-decls)
namespace Gui {
class View3DInventorViewer;
@@ -490,6 +491,7 @@ protected:
};
} // namespace Gui
+// NOLINTEND(cppcoreguidelines-avoid*, readability-avoid-const-params-in-decls)
Q_DECLARE_OPERATORS_FOR_FLAGS(Gui::NavigationStyle::RotationCenterModes)
diff --git a/src/Gui/Navigation/SiemensNXNavigationStyle.cpp b/src/Gui/Navigation/SiemensNXNavigationStyle.cpp
new file mode 100644
index 0000000000..45090aa2bf
--- /dev/null
+++ b/src/Gui/Navigation/SiemensNXNavigationStyle.cpp
@@ -0,0 +1,471 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+/***************************************************************************
+ * Copyright (c) 2025 Werner Mayer *
+ * *
+ * This file is part of FreeCAD. *
+ * *
+ * FreeCAD is free software: you can redistribute it and/or modify it *
+ * under the terms of the GNU Lesser General Public License as *
+ * published by the Free Software Foundation, either version 2.1 of the *
+ * License, or (at your option) any later version. *
+ * *
+ * FreeCAD is distributed in the hope that it will be useful, but *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+ * Lesser General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU Lesser General Public *
+ * License along with FreeCAD. If not, see *
+ * . *
+ * *
+ **************************************************************************/
+
+
+#include "PreCompiled.h"
+#ifndef _PreComp_
+#include
+#endif
+
+#include
+#include
+#include
+
+#include "Camera.h"
+#include "SiemensNXNavigationStyle.h"
+#include "View3DInventorViewer.h"
+
+// NOLINTBEGIN(cppcoreguidelines-pro-type-static-cast-downcast,
+// cppcoreguidelines-avoid*,
+// readability-avoid-const-params-in-decls)
+using namespace Gui;
+namespace sc = boost::statechart;
+using SC = NavigationStateChart;
+using NS = SiemensNXNavigationStyle;
+
+struct NS::NaviMachine: public sc::state_machine
+{
+ using superclass = sc::state_machine;
+ explicit NaviMachine(NS& ns) : ns(ns) {}
+ NS& ns;
+};
+
+struct NS::IdleState: public sc::state
+{
+ using reactions = sc::custom_reaction;
+ explicit IdleState(my_context ctx) : my_base(ctx)
+ {
+ auto &ns = this->outermost_context().ns;
+ ns.setViewingMode(NavigationStyle::IDLE);
+ }
+ sc::result react(const SC::Event& ev)
+ {
+ auto &ns = this->outermost_context().ns;
+ switch (ns.getViewingMode()) {
+ case NavigationStyle::SEEK_WAIT_MODE:
+ {
+ if (ev.isPress(SoMouseButtonEvent::BUTTON1)) {
+ ns.seekToPoint(ev.inventor_event->getPosition());
+ ns.setViewingMode(NavigationStyle::SEEK_MODE);
+ ev.flags->processed = true;
+ return transit();
+ }
+ break;
+ }
+ case NavigationStyle::SPINNING:
+ case NavigationStyle::SEEK_MODE:
+ {
+ if (!ev.flags->processed) {
+ if (ev.isMouseButtonEvent()) {
+ ev.flags->processed = true;
+ return transit();
+ }
+ else if (ev.isKeyboardEvent() || ev.isMotion3Event()) {
+ ns.setViewingMode(NavigationStyle::IDLE);
+ }
+ }
+
+ break;
+ }
+ case NavigationStyle::BOXZOOM:
+ return forward_event();
+ }
+
+ // right-click
+ if (ev.isRelease(SoMouseButtonEvent::BUTTON2)
+ && ev.mbstate() == 0
+ && !ns.viewer->isEditing()
+ && ns.isPopupMenuEnabled()){
+ ns.openPopupMenu(ev.inventor_event->getPosition());
+ }
+
+ if (ev.isPress(SoMouseButtonEvent::BUTTON3)) {
+ if (ev.isDownShift()) {
+ ev.flags->processed = true;
+ return transit();
+ }
+
+ if (ev.isDownButton(SC::Event::BUTTON3DOWN)) {
+ ev.flags->processed = true;
+ return transit();
+ }
+ }
+
+ // Use processClickEvent()
+
+ // Implement selection callback
+ //if (ev.isLocation2Event() && ev.isDownButton1()) {
+ // ev.flags->processed = true;
+ // return transit();
+ //}
+
+ return forward_event();
+ }
+};
+
+struct NS::AwaitingReleaseState : public sc::state
+{
+ using reactions = sc::custom_reaction;
+ explicit AwaitingReleaseState(my_context ctx) : my_base(ctx)
+ {}
+
+ sc::result react(const NS::Event& /*ev*/)
+ {
+ return forward_event();
+ }
+};
+
+struct NS::InteractState: public sc::state
+{
+ using reactions = sc::custom_reaction;
+ explicit InteractState(my_context ctx):my_base(ctx)
+ {
+ auto &ns = this->outermost_context().ns;
+ ns.setViewingMode(NavigationStyle::INTERACT);
+ }
+
+ sc::result react(const NS::Event& /*ev*/) {
+ return forward_event();
+ }
+};
+
+struct NS::AwaitingMoveState: public sc::state
+{
+ using reactions = sc::custom_reaction;
+
+private:
+ SbVec2s base_pos;
+ SbTime since;
+
+public:
+ explicit AwaitingMoveState(my_context ctx):my_base(ctx)
+ {
+ auto &ns = this->outermost_context().ns;
+ ns.setViewingMode(NavigationStyle::DRAGGING);
+ this->base_pos = static_cast(this->triggering_event())->inventor_event->getPosition();
+ this->since = static_cast(this->triggering_event())->inventor_event->getTime();
+ }
+ sc::result react(const NS::Event& ev){
+ //this state consumes all mouse events.
+ ev.flags->processed = ev.isMouseButtonEvent() || ev.isLocation2Event();
+
+ if (ev.isLocation2Event()) {
+ return transit();
+ }
+
+ // right-click
+ if (ev.isPress(SoMouseButtonEvent::BUTTON2) && ev.isDownButton3()) {
+ return transit();
+ }
+
+ if (ev.isKeyPress(SoKeyboardEvent::LEFT_SHIFT)) {
+ ev.flags->processed = true;
+ return transit();
+ }
+
+ // left-click
+ if (ev.isPress(SoMouseButtonEvent::BUTTON1) && ev.isDownButton3()) {
+ return transit();
+ }
+
+ if (ev.isKeyPress(SoKeyboardEvent::LEFT_CONTROL)) {
+ ev.flags->processed = true;
+ return transit();
+ }
+
+ // middle-click
+ if (ev.isRelease(SoMouseButtonEvent::BUTTON3) && ev.isDownNoButton()) {
+ auto &ns = this->outermost_context().ns;
+ SbTime tmp = (ev.inventor_event->getTime() - this->since);
+ double dci = QApplication::doubleClickInterval() / 1000.0;
+
+ // is this a simple middle click?
+ if (tmp.getValue() < dci) {
+ ev.flags->processed = true;
+ SbVec2s pos = ev.inventor_event->getPosition();
+ ns.lookAtPoint(pos);
+ }
+ return transit();
+ }
+
+ return forward_event();
+ }
+};
+
+struct NS::RotateState: public sc::state
+{
+ using reactions = sc::custom_reaction;
+ explicit RotateState(my_context ctx) : my_base(ctx)
+ {
+ auto &ns = this->outermost_context().ns;
+ const auto inventorEvent = static_cast(this->triggering_event())->inventor_event;
+ ns.saveCursorPosition(inventorEvent);
+ ns.setViewingMode(NavigationStyle::DRAGGING);
+ this->base_pos = inventorEvent->getPosition();
+ }
+
+ sc::result react(const NS::Event& ev)
+ {
+ if (ev.isLocation2Event()) {
+ auto &ns = this->outermost_context().ns;
+ ns.addToLog(ev.inventor_event->getPosition(), ev.inventor_event->getTime());
+ const SbVec2s pos = ev.inventor_event->getPosition();
+ const SbVec2f posn = ns.normalizePixelPos(pos);
+ ns.spin(posn);
+ ns.moveCursorPosition();
+ ev.flags->processed = true;
+ }
+
+ // right-click
+ if (ev.isPress(SoMouseButtonEvent::BUTTON2) && ev.isDownButton3()) {
+ ev.flags->processed = true;
+ return transit();
+ }
+
+ if (ev.isKeyPress(SoKeyboardEvent::LEFT_SHIFT)) {
+ ev.flags->processed = true;
+ return transit();
+ }
+
+ // left-click
+ if (ev.isPress(SoMouseButtonEvent::BUTTON1) && ev.isDownButton3()) {
+ ev.flags->processed = true;
+ return transit();
+ }
+
+ if (ev.isKeyPress(SoKeyboardEvent::LEFT_CONTROL)) {
+ ev.flags->processed = true;
+ return transit();
+ }
+
+ if (ev.isRelease(SoMouseButtonEvent::BUTTON3) && ev.isDownNoButton()) {
+ ev.flags->processed = true;
+ return transit();
+ }
+ return forward_event();
+ }
+
+private:
+ SbVec2s base_pos;
+};
+
+struct NS::PanState: public sc::state
+{
+ using reactions = sc::custom_reaction;
+ explicit PanState(my_context ctx):my_base(ctx)
+ {
+ auto &ns = this->outermost_context().ns;
+ const NS::Event* ev = static_cast(this->triggering_event());
+ ns.setViewingMode(NavigationStyle::PANNING);
+ this->base_pos = ev->inventor_event->getPosition();
+ this->ratio = ns.viewer->getSoRenderManager()->getViewportRegion().getViewportAspectRatio();
+ ns.centerTime = ev->inventor_event->getTime();
+ ns.setupPanningPlane(ns.getCamera());
+ }
+ sc::result react(const NS::Event& ev)
+ {
+ if (ev.isLocation2Event()){
+ ev.flags->processed = true;
+ SbVec2s pos = ev.inventor_event->getPosition();
+ auto &ns = this->outermost_context().ns;
+ ns.panCamera(ns.viewer->getSoRenderManager()->getCamera(),
+ this->ratio,
+ ns.panningplane,
+ ns.normalizePixelPos(pos),
+ ns.normalizePixelPos(this->base_pos));
+ this->base_pos = pos;
+ }
+
+ if (ev.isRelease(SoMouseButtonEvent::BUTTON2) && ev.isDownButton3()) {
+ ev.flags->processed = true;
+ return transit();
+ }
+
+ if (ev.isKeyRelease(SoKeyboardEvent::LEFT_SHIFT) && ev.isDownButton3()) {
+ ev.flags->processed = true;
+ return transit();
+ }
+
+ if (ev.isRelease(SoMouseButtonEvent::BUTTON3)) {
+ ev.flags->processed = true;
+ return transit();
+ }
+
+ return forward_event();
+ }
+
+private:
+ SbVec2s base_pos;
+ float ratio {1.0F};
+};
+
+struct NS::ZoomState: public sc::state
+{
+ using reactions = sc::custom_reaction;
+ explicit ZoomState(my_context ctx) : my_base(ctx)
+ {
+ auto &ns = this->outermost_context().ns;
+ const NS::Event* ev = static_cast(this->triggering_event());
+ ns.setViewingMode(NavigationStyle::ZOOMING);
+ this->base_pos = ev->inventor_event->getPosition();
+ }
+
+ sc::result react(const NS::Event& ev)
+ {
+ if (ev.isLocation2Event()){
+ ev.flags->processed = true;
+ SbVec2s pos = ev.inventor_event->getPosition();
+ auto &ns = this->outermost_context().ns;
+ ns.zoomByCursor(ns.normalizePixelPos(pos),
+ ns.normalizePixelPos(this->base_pos));
+ this->base_pos = pos;
+ }
+
+ if (ev.isRelease(SoMouseButtonEvent::BUTTON1) && ev.isDownButton3()) {
+ ev.flags->processed = true;
+ return transit();
+ }
+
+ if (ev.isKeyRelease(SoKeyboardEvent::LEFT_CONTROL) && ev.isDownButton3()) {
+ ev.flags->processed = true;
+ return transit();
+ }
+
+ if (ev.isRelease(SoMouseButtonEvent::BUTTON3)) {
+ ev.flags->processed = true;
+ return transit();
+ }
+
+ return forward_event();
+ }
+
+private:
+ SbVec2s base_pos;
+};
+
+struct NS::SelectionState: public sc::state
+{
+ using reactions = sc::custom_reaction;
+ explicit SelectionState(my_context ctx) : my_base(ctx)
+ {
+ auto &ns = this->outermost_context().ns;
+ const NS::Event* ev = static_cast(this->triggering_event());
+
+ ns.setViewingMode(NavigationStyle::BOXZOOM);
+ ns.startSelection(NavigationStyle::Rubberband);
+ fakeLeftButtonDown(ev->inventor_event->getPosition());
+ }
+
+ void fakeLeftButtonDown(const SbVec2s& pos)
+ {
+ SoMouseButtonEvent mbe;
+ mbe.setButton(SoMouseButtonEvent::BUTTON1);
+ mbe.setState(SoMouseButtonEvent::DOWN);
+ mbe.setPosition(pos);
+
+ auto &ns = this->outermost_context().ns;
+ ns.processEvent(&mbe);
+ }
+
+ sc::result react(const NS::Event& /*ev*/)
+ {
+ // This isn't called while selection mode is active
+ return transit();
+ }
+};
+
+// ----------------------------------------------------------------------------------
+
+/* TRANSLATOR Gui::SiemensNXNavigationStyle */
+
+TYPESYSTEM_SOURCE(Gui::SiemensNXNavigationStyle, Gui::UserNavigationStyle)
+
+SiemensNXNavigationStyle::SiemensNXNavigationStyle()
+{
+ naviMachine.reset(new NaviStateMachineT(new NaviMachine(*this)));
+}
+
+SiemensNXNavigationStyle::~SiemensNXNavigationStyle()
+{
+}
+
+const char* SiemensNXNavigationStyle::mouseButtons(ViewerMode mode)
+{
+ switch (mode) {
+ case NavigationStyle::SELECTION:
+ return QT_TR_NOOP("Press left mouse button");
+ case NavigationStyle::PANNING:
+ return QT_TR_NOOP("Press middle+right click");
+ case NavigationStyle::DRAGGING:
+ return QT_TR_NOOP("Press middle mouse button");
+ case NavigationStyle::ZOOMING:
+ return QT_TR_NOOP("Scroll mouse wheel");
+ default:
+ return "No description";
+ }
+}
+
+std::string SiemensNXNavigationStyle::userFriendlyName() const
+{
+ return {"Siemens NX"};
+}
+
+SbBool SiemensNXNavigationStyle::processKeyboardEvent(const SoKeyboardEvent* const event)
+{
+ // See https://forum.freecad.org/viewtopic.php?t=96459
+ // Isometric view: Home key button
+ // Trimetric view: End key button
+ // Fit all: CTRL+F
+ // Normal view: F8
+ switch (event->getKey()) {
+ case SoKeyboardEvent::F:
+ if (event->wasCtrlDown()) {
+ viewer->viewAll();
+ return true;
+ }
+ break;
+ case SoKeyboardEvent::HOME:
+ {
+ viewer->setCameraOrientation(Camera::rotation(Camera::Isometric));
+ return true;
+ }
+ case SoKeyboardEvent::END:
+ {
+ viewer->setCameraOrientation(Camera::rotation(Camera::Trimetric));
+ return true;
+ }
+ case SoKeyboardEvent::F8:
+ {
+ viewer->setCameraOrientation(Camera::rotation(Camera::Top));
+ return true;
+ }
+ default:
+ break;
+ }
+
+ return inherited::processKeyboardEvent(event);
+}
+
+// NOLINTEND(cppcoreguidelines-pro-type-static-cast-downcast,
+// cppcoreguidelines-avoid*,
+// readability-avoid-const-params-in-decls)
diff --git a/src/Gui/Navigation/SiemensNXNavigationStyle.h b/src/Gui/Navigation/SiemensNXNavigationStyle.h
new file mode 100644
index 0000000000..a888551df0
--- /dev/null
+++ b/src/Gui/Navigation/SiemensNXNavigationStyle.h
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+/***************************************************************************
+ * Copyright (c) 2025 Werner Mayer *
+ * *
+ * This file is part of FreeCAD. *
+ * *
+ * FreeCAD is free software: you can redistribute it and/or modify it *
+ * under the terms of the GNU Lesser General Public License as *
+ * published by the Free Software Foundation, either version 2.1 of the *
+ * License, or (at your option) any later version. *
+ * *
+ * FreeCAD is distributed in the hope that it will be useful, but *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+ * Lesser General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU Lesser General Public *
+ * License along with FreeCAD. If not, see *
+ * . *
+ * *
+ **************************************************************************/
+
+
+#ifndef GUI_SIEMENSNXNAVIGATIONSTYLE_H
+#define GUI_SIEMENSNXNAVIGATIONSTYLE_H
+
+#include
+
+// NOLINTBEGIN(cppcoreguidelines-avoid*, readability-avoid-const-params-in-decls)
+namespace Gui
+{
+
+class GuiExport SiemensNXNavigationStyle : public NavigationStateChart {
+ using inherited = NavigationStateChart;
+
+ TYPESYSTEM_HEADER_WITH_OVERRIDE();
+
+public:
+ SiemensNXNavigationStyle();
+ ~SiemensNXNavigationStyle() override;
+ const char* mouseButtons(ViewerMode mode) override;
+ std::string userFriendlyName() const override;
+
+protected:
+ SbBool processKeyboardEvent(const SoKeyboardEvent * const event) override;
+
+private:
+ struct NaviMachine;
+ struct IdleState;
+ struct AwaitingReleaseState;
+ struct AwaitingMoveState;
+ struct InteractState;
+ struct RotateState;
+ struct PanState;
+ struct ZoomState;
+ struct SelectionState;
+};
+
+} // namespace Gui
+// NOLINTEND(cppcoreguidelines-avoid*, readability-avoid-const-params-in-decls)
+
+#endif // GUI_SIEMENSNXNAVIGATIONSTYLE_H
diff --git a/src/Gui/SoFCDB.cpp b/src/Gui/SoFCDB.cpp
index 29e80fbe31..19b32fda94 100644
--- a/src/Gui/SoFCDB.cpp
+++ b/src/Gui/SoFCDB.cpp
@@ -53,6 +53,7 @@
#include "Inventor/Draggers/SoTransformDragger.h"
#include "Navigation/GestureNavigationStyle.h"
#include "Navigation/NavigationStyle.h"
+#include "Navigation/SiemensNXNavigationStyle.h"
#include "SelectionObject.h"
#include "SoDevicePixelRatioElement.h"
#include "SoFCColorBar.h"
@@ -188,17 +189,19 @@ void Gui::SoFCDB::init()
NavigationStyle ::init();
UserNavigationStyle ::init();
- InventorNavigationStyle ::init();
- CADNavigationStyle ::init();
- RevitNavigationStyle ::init();
+ NavigationStateChart ::init();
BlenderNavigationStyle ::init();
- SolidWorksNavigationStyle ::init();
- MayaGestureNavigationStyle ::init();
- TouchpadNavigationStyle ::init();
+ CADNavigationStyle ::init();
GestureNavigationStyle ::init();
+ MayaGestureNavigationStyle ::init();
OpenCascadeNavigationStyle ::init();
+ InventorNavigationStyle ::init();
OpenSCADNavigationStyle ::init();
+ RevitNavigationStyle ::init();
+ SiemensNXNavigationStyle ::init();
+ SolidWorksNavigationStyle ::init();
TinkerCADNavigationStyle ::init();
+ TouchpadNavigationStyle ::init();
GLGraphicsItem ::init();
GLFlagWindow ::init();
diff --git a/src/Mod/Tux/NavigationIndicatorGui.py b/src/Mod/Tux/NavigationIndicatorGui.py
index 84b7f3db7b..194ead53ab 100644
--- a/src/Mod/Tux/NavigationIndicatorGui.py
+++ b/src/Mod/Tux/NavigationIndicatorGui.py
@@ -74,9 +74,10 @@ def RePopulateIcons():
a6.setIcon(QtGui.QIcon(":/icons/NavigationOpenInventor_" + StyleSheetType + ".svg"))
a7.setIcon(QtGui.QIcon(":/icons/NavigationOpenSCAD_" + StyleSheetType + ".svg"))
a8.setIcon(QtGui.QIcon(":/icons/NavigationRevit_" + StyleSheetType + ".svg"))
- a9.setIcon(QtGui.QIcon(":/icons/NavigationSolidWorks_" + StyleSheetType + ".svg"))
- a10.setIcon(QtGui.QIcon(":/icons/NavigationTinkerCAD_" + StyleSheetType + ".svg"))
- a11.setIcon(QtGui.QIcon(":/icons/NavigationTouchpad_" + StyleSheetType + ".svg"))
+ a9.setIcon(QtGui.QIcon(":/icons/NavigationSiemensNX_" + StyleSheetType + ".svg"))
+ a10.setIcon(QtGui.QIcon(":/icons/NavigationSolidWorks_" + StyleSheetType + ".svg"))
+ a11.setIcon(QtGui.QIcon(":/icons/NavigationTinkerCAD_" + StyleSheetType + ".svg"))
+ a12.setIcon(QtGui.QIcon(":/icons/NavigationTouchpad_" + StyleSheetType + ".svg"))
def retranslateUi():
@@ -474,6 +475,48 @@ def retranslateUi():
global t9
t9 = (
+ "Siemens NX "
+ + text06
+ + """
+
+
+ | """
+ + text01
+ + """ |
+ """
+ + text02
+ + """ |
+ """
+ + text02
+ + """ |
+ """
+ + text03
+ + """ |
+ """
+ + text04
+ + """ |
+ """
+ + text04
+ + """ |
+
+
+  |
+  |
+  |
+  |
+  |
+  |
+
+
+ """
+ + text08
+ + ": "
+ + text10
+ + "
"
+ )
+
+ global t10
+ t10 = (
"SolidWorks "
+ text06
+ """
@@ -510,8 +553,8 @@ def retranslateUi():
+ ""
)
- global t10
- t10 = (
+ global t11
+ t11 = (
"TinkerCAD "
+ text06
+ """
@@ -539,8 +582,8 @@ def retranslateUi():
"""
)
- global t11
- t11 = (
+ global t12
+ t12 = (
"Touchpad "
+ text06
+ """
@@ -716,19 +759,24 @@ a8.setData("Gui::RevitNavigationStyle")
a8.setObjectName("Indicator_NavigationRevit")
a9 = QtGui.QAction(gStyle)
-a9.setText("SolidWorks ")
-a9.setData("Gui::SolidWorksNavigationStyle")
-a9.setObjectName("Indicator_NavigationSolidWorks")
+a9.setText("Siemens NX ")
+a9.setData("Gui::SiemensNXNavigationStyle")
+a9.setObjectName("Indicator_NavigationSiemensNX")
a10 = QtGui.QAction(gStyle)
-a10.setText("TinkerCAD ")
-a10.setData("Gui::TinkerCADNavigationStyle")
-a10.setObjectName("Indicator_NavigationTinkerCAD")
+a10.setText("SolidWorks ")
+a10.setData("Gui::SolidWorksNavigationStyle")
+a10.setObjectName("Indicator_NavigationSolidWorks")
a11 = QtGui.QAction(gStyle)
-a11.setText("Touchpad ")
-a11.setData("Gui::TouchpadNavigationStyle")
-a11.setObjectName("Indicator_NavigationTouchpad")
+a11.setText("TinkerCAD ")
+a11.setData("Gui::TinkerCADNavigationStyle")
+a11.setObjectName("Indicator_NavigationTinkerCAD")
+
+a12 = QtGui.QAction(gStyle)
+a12.setText("Touchpad ")
+a12.setData("Gui::TouchpadNavigationStyle")
+a12.setObjectName("Indicator_NavigationTouchpad")
RePopulateIcons()
@@ -746,6 +794,7 @@ menu.addAction(a8)
menu.addAction(a9)
menu.addAction(a10)
menu.addAction(a11)
+menu.addAction(a12)
pView.Attach(indicator)
@@ -787,6 +836,7 @@ def onTooltip():
a9.setToolTip(t9)
a10.setToolTip(t10)
a11.setToolTip(t11)
+ a12.setToolTip(t12)
p.SetBool("Tooltip", 1)
else:
for i in gStyle.actions():
diff --git a/src/Mod/Tux/Resources/Tux.qrc b/src/Mod/Tux/Resources/Tux.qrc
index 6ec43834ca..413d549458 100644
--- a/src/Mod/Tux/Resources/Tux.qrc
+++ b/src/Mod/Tux/Resources/Tux.qrc
@@ -47,6 +47,8 @@
icons/NavigationOpenSCAD_dark.svg
icons/NavigationRevit_light.svg
icons/NavigationRevit_dark.svg
+ icons/NavigationSiemensNX_light.svg
+ icons/NavigationSiemensNX_dark.svg
icons/NavigationSolidWorks_light.svg
icons/NavigationSolidWorks_dark.svg
icons/NavigationTinkerCAD_light.svg
diff --git a/src/Mod/Tux/Resources/icons/NavigationSiemensNX_dark.svg b/src/Mod/Tux/Resources/icons/NavigationSiemensNX_dark.svg
new file mode 100644
index 0000000000..38ab2c06cc
--- /dev/null
+++ b/src/Mod/Tux/Resources/icons/NavigationSiemensNX_dark.svg
@@ -0,0 +1,50 @@
+
+
+
+
diff --git a/src/Mod/Tux/Resources/icons/NavigationSiemensNX_light.svg b/src/Mod/Tux/Resources/icons/NavigationSiemensNX_light.svg
new file mode 100644
index 0000000000..0064477a86
--- /dev/null
+++ b/src/Mod/Tux/Resources/icons/NavigationSiemensNX_light.svg
@@ -0,0 +1,50 @@
+
+
+
+