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 @@ + + + + + + + + + image/svg+xml + + + + + + + + + NX + 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 @@ + + + + + + + + + image/svg+xml + + + + + + + + + NX +