From ea803106424fad294de8ac062ec3321cfd2e19e1 Mon Sep 17 00:00:00 2001 From: tritao Date: Wed, 15 Jan 2025 21:05:07 +0000 Subject: [PATCH] Gui: Provide `ViewProviderFeaturePython` selection callbacks Adds support for Python-based selection callbacks to `ViewProviderFeaturePython` objects. It follows the same conventions as `SelectionObserverPython`, follows an example: ```python def setPreselection(self, document, object, element): print("setPreselection: %s.%s.%s"%(document, object, element)) def removePreselection(self, document, object, element): print("removePreselection: %s.%s.%s"%(document, object, element)) def addSelection(self, document, object, element, position): print("addSelection: %s.%s.%s at %s"%(document, object, element, str(position))) def removeSelection(self,document, object, element): print("removeSelection: %s.%s.%s"%(document, object, element)) def setSelection(self,doc): sel = FreeCADGui.Selection.getSelection(doc) print("setSelection: %s"%sel) def clearSelection(self,doc): print("clearSelection\n") ``` --- src/Gui/Selection/Selection.cpp | 41 ++++++++++++++++++- src/Gui/Selection/SelectionObserverPython.cpp | 19 +++++++-- src/Gui/Selection/SelectionObserverPython.h | 9 ++-- src/Gui/ViewProvider.h | 3 ++ src/Gui/ViewProviderFeaturePython.cpp | 10 +++++ src/Gui/ViewProviderFeaturePython.h | 10 ++++- 6 files changed, 84 insertions(+), 8 deletions(-) diff --git a/src/Gui/Selection/Selection.cpp b/src/Gui/Selection/Selection.cpp index b8fdab737e..b7664a06f5 100644 --- a/src/Gui/Selection/Selection.cpp +++ b/src/Gui/Selection/Selection.cpp @@ -25,6 +25,7 @@ #ifndef _PreComp_ # include +# include # include # include #endif @@ -52,6 +53,7 @@ #include "SelectionFilterPy.h" #include "SelectionObserverPython.h" #include "Tree.h" +#include "ViewProvider.h" #include "ViewProviderDocumentObject.h" @@ -391,6 +393,25 @@ void SelectionSingleton::enablePickedList(bool enable) } } +static void notifyDocumentObjectViewProvider(const SelectionChanges& changes) { + const auto* doc = App::GetApplication().getDocument(changes.pDocName); + if (!doc) { + return; + } + + const auto* obj = doc->getObject(changes.pObjectName); + if (!obj) { + return; + } + + auto* vp = Application::Instance->getViewProvider(obj); + if (!vp) { + return; + } + + vp->onSelectionChanged(changes); +} + void SelectionSingleton::notify(SelectionChanges &&Chng) { if(Notifying) { @@ -401,7 +422,7 @@ void SelectionSingleton::notify(SelectionChanges &&Chng) NotificationQueue.push_back(std::move(Chng)); while(!NotificationQueue.empty()) { const auto &msg = NotificationQueue.front(); - bool notify; + bool notify = false; switch(msg.Type) { case SelectionChanges::AddSelection: notify = isSelected(msg.pDocName, msg.pObjectName, msg.pSubName, ResolveMode::NoResolve); @@ -420,6 +441,9 @@ void SelectionSingleton::notify(SelectionChanges &&Chng) notify = true; } if(notify) { + // Notify the view provider of the object. + notifyDocumentObjectViewProvider(msg); + Notify(msg); try { signalSelectionChanged(msg); @@ -1454,6 +1478,21 @@ void SelectionSingleton::clearCompleteSelection(bool clearPreSelect) clearPreSelect?"Gui.Selection.clearSelection()" :"Gui.Selection.clearSelection(False)"); + // Send the clear selection notification to all view providers associated with the + // objects being deselected. + + std::set viewProviders; + for (_SelObj& sel : _SelList) { + if (auto vp = Application::Instance->getViewProvider(sel.pObject)) { + viewProviders.insert(vp); + } + } + + for (auto& vp : viewProviders) { + SelectionChanges Chng(SelectionChanges::ClrSelection); + vp->onSelectionChanged(Chng); + } + _SelList.clear(); SelectionChanges Chng(SelectionChanges::ClrSelection); diff --git a/src/Gui/Selection/SelectionObserverPython.cpp b/src/Gui/Selection/SelectionObserverPython.cpp index 936e074b1f..2d41f5c45a 100644 --- a/src/Gui/Selection/SelectionObserverPython.cpp +++ b/src/Gui/Selection/SelectionObserverPython.cpp @@ -32,15 +32,28 @@ using namespace Gui; std::vector SelectionObserverPython::_instances; -void SelectionObserverPythonHandler::init(const Py::Object& obj) +void SelectionObserverPythonHandler::init(PyObject* obj) { this->inst = obj; #undef FC_PY_ELEMENT -#define FC_PY_ELEMENT(_name) FC_PY_GetCallable(obj.ptr(),#_name,py_##_name); +#define FC_PY_ELEMENT(_name) FC_PY_GetCallable(obj,#_name,py_##_name); FC_PY_SEL_OBSERVER } +SelectionObserverPythonHandler::~SelectionObserverPythonHandler() +{ +#undef FC_PY_ELEMENT +#define FC_PY_ELEMENT(_name) py_##_name = Py::None(); + + try { + FC_PY_SEL_OBSERVER + } + catch (Py::Exception& e) { + e.clear(); + } +} + void SelectionObserverPythonHandler::handleSelectionChanged(const SelectionChanges& msg) { switch (msg.Type) @@ -198,7 +211,7 @@ void SelectionObserverPythonHandler::removePreselection(const SelectionChanges& SelectionObserverPython::SelectionObserverPython(const Py::Object& obj, ResolveMode resolve) : SelectionObserver(true, resolve) { - this->init(obj); + this->init(obj.ptr()); } SelectionObserverPython::~SelectionObserverPython() = default; diff --git a/src/Gui/Selection/SelectionObserverPython.h b/src/Gui/Selection/SelectionObserverPython.h index c7d258d20e..5eed3a217c 100644 --- a/src/Gui/Selection/SelectionObserverPython.h +++ b/src/Gui/Selection/SelectionObserverPython.h @@ -36,7 +36,9 @@ class GuiExport SelectionObserverPythonHandler public: /// Constructor explicit SelectionObserverPythonHandler() = default; - void init(const Py::Object& obj); + virtual ~SelectionObserverPythonHandler(); + + void init(PyObject* obj); void handleSelectionChanged(const SelectionChanges& msg); protected: @@ -48,10 +50,10 @@ protected: void removePreselection(const SelectionChanges&); void pickedListChanged(); - Py::Object inst; - private: + PyObject* inst{nullptr}; + #define FC_PY_SEL_OBSERVER \ FC_PY_ELEMENT(onSelectionChanged) \ FC_PY_ELEMENT(addSelection) \ @@ -89,6 +91,7 @@ public: private: void onSelectionChanged(const SelectionChanges& msg) override; + Py::Object inst; static std::vector _instances; }; diff --git a/src/Gui/ViewProvider.h b/src/Gui/ViewProvider.h index 4f394a0496..7b199119a2 100644 --- a/src/Gui/ViewProvider.h +++ b/src/Gui/ViewProvider.h @@ -74,6 +74,7 @@ class View3DInventorViewer; class ViewProviderPy; class ObjectItem; class MDIView; +class SelectionChanges; enum ViewStatus { UpdateData = 0, @@ -164,6 +165,8 @@ public: /// indicates if the ViewProvider use the new Selection model virtual bool useNewSelectionModel() const; virtual bool isSelectable() const {return true;} + /// called when the selection changes for the view provider + virtual void onSelectionChanged(const SelectionChanges&) {} /// return a hit element given the picked point which contains the full node path virtual bool getElementPicked(const SoPickedPoint *, std::string &subname) const; /// return a hit element to the selection path or 0 diff --git a/src/Gui/ViewProviderFeaturePython.cpp b/src/Gui/ViewProviderFeaturePython.cpp index 7b24735582..23c4c1383c 100644 --- a/src/Gui/ViewProviderFeaturePython.cpp +++ b/src/Gui/ViewProviderFeaturePython.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include "ViewProviderFeaturePython.h" #include "Application.h" @@ -44,6 +45,7 @@ #include "PythonWrapper.h" #include "View3DInventorViewer.h" #include "ViewProviderDocumentObjectPy.h" +#include "Selection.h" FC_LOG_LEVEL_INIT("ViewProviderFeaturePython", true, true) @@ -74,6 +76,8 @@ ViewProviderFeaturePythonImp::~ViewProviderFeaturePythonImp() catch (Py::Exception& e) { e.clear(); } + + this->selectionObserver.~SelectionObserverPythonHandler(); } void ViewProviderFeaturePythonImp::init(PyObject *pyobj) { @@ -84,6 +88,8 @@ void ViewProviderFeaturePythonImp::init(PyObject *pyobj) { #define FC_PY_ELEMENT(_name) FC_PY_ELEMENT_INIT(_name) FC_PY_VIEW_OBJECT + + this->selectionObserver.init(pyobj); } #define FC_PY_CALL_CHECK(_name) _FC_PY_CALL_CHECK(_name,return(NotImplemented)) @@ -202,6 +208,10 @@ ViewProviderFeaturePythonImp::useNewSelectionModel() const return Accepted; } +void ViewProviderFeaturePythonImp::onSelectionChanged(const SelectionChanges& changes) { + this->selectionObserver.handleSelectionChanged(changes); +} + bool ViewProviderFeaturePythonImp::getElement(const SoDetail *det, std::string &res) const { _FC_PY_CALL_CHECK(getElement,return(false)); diff --git a/src/Gui/ViewProviderFeaturePython.h b/src/Gui/ViewProviderFeaturePython.h index 023247019e..cc2737db8f 100644 --- a/src/Gui/ViewProviderFeaturePython.h +++ b/src/Gui/ViewProviderFeaturePython.h @@ -26,17 +26,19 @@ #include #include #include +#include #include "ViewProviderGeometryObject.h" #include "Document.h" - class SoSensor; class SoDragger; class SoNode; namespace Gui { +class SelectionChanges; + class GuiExport ViewProviderFeaturePythonImp { public: @@ -55,6 +57,7 @@ public: QIcon getIcon() const; bool claimChildren(std::vector&) const; ValueT useNewSelectionModel() const; + void onSelectionChanged(const SelectionChanges&); ValueT getElementPicked(const SoPickedPoint *pp, std::string &subname) const; bool getElement(const SoDetail *det, std::string &) const; bool getDetail(const char*, SoDetail *&det) const; @@ -130,6 +133,7 @@ public: private: ViewProviderDocumentObject* object; App::PropertyPythonObject &Proxy; + SelectionObserverPythonHandler selectionObserver; bool has__object__{false}; #define FC_PY_VIEW_OBJECT \ @@ -253,6 +257,10 @@ public: return ViewProviderT::useNewSelectionModel(); } } + /// called when the selection changes for the view provider + void onSelectionChanged(const SelectionChanges& changes) override { + return imp->onSelectionChanged(changes); + } bool getElementPicked(const SoPickedPoint *pp, std::string &subname) const override { auto ret = imp->getElementPicked(pp,subname); if(ret == ViewProviderFeaturePythonImp::NotImplemented)