From 9971a151a8a8eb0e4a6a5cad64530e01787717cc Mon Sep 17 00:00:00 2001 From: Paul Baksic Date: Thu, 9 Apr 2026 14:17:08 +0200 Subject: [PATCH 1/5] Modified the controller binding to make it the Component binding --- .../Sofa/Core/Binding_BaseComponent.cpp | 1 - ...g_Controller.cpp => Binding_Component.cpp} | 152 +++++++++++------- ...nding_Controller.h => Binding_Component.h} | 22 +-- ...ntroller_doc.h => Binding_Component_doc.h} | 6 +- .../src/SofaPython3/Sofa/Core/CMakeLists.txt | 6 +- .../SofaPython3/Sofa/Core/Submodule_Core.cpp | 3 +- 6 files changed, 111 insertions(+), 79 deletions(-) rename bindings/Sofa/src/SofaPython3/Sofa/Core/{Binding_Controller.cpp => Binding_Component.cpp} (63%) rename bindings/Sofa/src/SofaPython3/Sofa/Core/{Binding_Controller.h => Binding_Component.h} (83%) rename bindings/Sofa/src/SofaPython3/Sofa/Core/{Binding_Controller_doc.h => Binding_Component_doc.h} (94%) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseComponent.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseComponent.cpp index a211d32fc..3e0c6d82b 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseComponent.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseComponent.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.cpp similarity index 63% rename from bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.cpp rename to bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.cpp index f99484ddc..270bfc6b1 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.cpp @@ -22,8 +22,8 @@ #include #include #include -#include -#include +#include +#include #include #include @@ -38,20 +38,20 @@ namespace sofapython3 using sofa::core::objectmodel::Event; using sofa::core::objectmodel::BaseComponent; -Controller_Trampoline::Controller_Trampoline() = default; +Component_Trampoline::Component_Trampoline() = default; -Controller_Trampoline::~Controller_Trampoline() +Component_Trampoline::~Component_Trampoline() { // Clean up Python objects while holding the GIL if (m_cacheInitialized) { - PythonEnvironment::gil acquire {"~Controller_Trampoline"}; + PythonEnvironment::gil acquire {"~Component_Trampoline"}; m_methodCache.clear(); m_pySelf = py::object(); } } -void Controller_Trampoline::initializePythonCache() +void Component_Trampoline::initializePythonCache() { if (m_cacheInitialized) return; @@ -65,7 +65,7 @@ void Controller_Trampoline::initializePythonCache() m_cacheInitialized = true; } -py::object Controller_Trampoline::getCachedMethod(const std::string& methodName) +py::object Component_Trampoline::getCachedMethod(const std::string& methodName) { // Must be called with GIL held and cache initialized @@ -92,7 +92,7 @@ py::object Controller_Trampoline::getCachedMethod(const std::string& methodName) return method; } -bool Controller_Trampoline::callCachedMethod(const py::object& method, Event* event) +bool Component_Trampoline::callCachedMethod(const py::object& method, Event* event) { // Must be called with GIL held if (f_printLog.getValue()) @@ -108,7 +108,7 @@ bool Controller_Trampoline::callCachedMethod(const py::object& method, Event* ev return py::cast(result); } -void Controller_Trampoline::invalidateMethodCache(const std::string& methodName) +void Component_Trampoline::invalidateMethodCache(const std::string& methodName) { if (!m_cacheInitialized) return; @@ -117,7 +117,7 @@ void Controller_Trampoline::invalidateMethodCache(const std::string& methodName) m_methodCache.erase(methodName); } -std::string Controller_Trampoline::getClassName() const +std::string Component_Trampoline::getClassName() const { PythonEnvironment::gil acquire {"getClassName"}; @@ -130,32 +130,32 @@ std::string Controller_Trampoline::getClassName() const return py::str(py::type::of(py::cast(this)).attr("__name__")); } -void Controller_Trampoline::draw(const sofa::core::visual::VisualParams* params) +void Component_Trampoline::draw(const sofa::core::visual::VisualParams* params) { PythonEnvironment::executePython(this, [this, params](){ - PYBIND11_OVERLOAD(void, Controller, draw, params); + PYBIND11_OVERLOAD(void, Component, draw, params); }); } -void Controller_Trampoline::init() +void Component_Trampoline::init() { PythonEnvironment::executePython(this, [this](){ // Initialize the Python object cache on first init initializePythonCache(); - PYBIND11_OVERLOAD(void, Controller, init, ); + PYBIND11_OVERLOAD(void, Component, init, ); }); } -void Controller_Trampoline::reinit() +void Component_Trampoline::reinit() { PythonEnvironment::executePython(this, [this](){ - PYBIND11_OVERLOAD(void, Controller, reinit, ); + PYBIND11_OVERLOAD(void, Component, reinit, ); }); } /// If a method named "methodName" exists in the python controller, /// methodName is called, with the Event's dict as argument -bool Controller_Trampoline::callScriptMethod( +bool Component_Trampoline::callScriptMethod( const py::object& self, Event* event, const std::string & methodName) { if(f_printLog.getValue()) @@ -177,7 +177,7 @@ bool Controller_Trampoline::callScriptMethod( return false; } -void Controller_Trampoline::handleEvent(Event* event) +void Component_Trampoline::handleEvent(Event* event) { PythonEnvironment::executePython(this, [this, event](){ // Ensure cache is initialized (in case init() wasn't called or @@ -204,59 +204,87 @@ void Controller_Trampoline::handleEvent(Event* event) }); } -void moduleAddController(py::module &m) { - py::class_ Component_Trampoline::_init_(pybind11::args& /*args*/, pybind11::kwargs& kwargs) +{ + auto c = sofa::core::sptr (new Component_Trampoline()); + c->f_listening.setValue(true); + + for(auto kv : kwargs) + { + std::string key = py::cast(kv.first); + py::object value = py::reinterpret_borrow(kv.second); + + if( key == "name") + c->setName(py::cast(kv.second)); + try { + BindingBase::SetAttr(*c, key, value); + } catch (py::attribute_error& /*e*/) { + /// kwargs are used to set datafields to their initial values, + /// but they can also be used as simple python attributes, unrelated to SOFA. + /// thus we catch & ignore the py::attribute_error thrown by SetAttr + } + } + return c; +} + +void Component_Trampoline::_setattr_(pybind11::object self, const std::string& s, pybind11::object value) +{ + // If the attribute starts with "on" and the new value is callable, invalidate the cached method + if (s.rfind("on", 0) == 0 && PyCallable_Check(value.ptr())) + { + auto* trampoline = dynamic_cast(py::cast(self)); + if (trampoline) + { + trampoline->invalidateMethodCache(s); + } + } + + // Delegate to the base class __setattr__ + BindingBase::__setattr__(self, s, value); +} + + + +void moduleAddComponent(py::module &m) { + py::class_> f(m, "Controller", + py_shared_ptr> f(m, "Component", py::dynamic_attr(), sofapython3::doc::controller::controllerClass); - f.def(py::init([](py::args& /*args*/, py::kwargs& kwargs) - { - auto c = sofa::core::sptr (new Controller_Trampoline()); - c->f_listening.setValue(true); - - for(auto kv : kwargs) - { - std::string key = py::cast(kv.first); - py::object value = py::reinterpret_borrow(kv.second); - - if( key == "name") - c->setName(py::cast(kv.second)); - try { - BindingBase::SetAttr(*c, key, value); - } catch (py::attribute_error& /*e*/) { - /// kwargs are used to set datafields to their initial values, - /// but they can also be used as simple python attributes, unrelated to SOFA. - /// thus we catch & ignore the py::attribute_error thrown by SetAttr - } - } - return c; - })); - - f.def("init", &Controller::init); - f.def("reinit", &Controller::reinit); - f.def("draw", [](Controller& self, sofa::core::visual::VisualParams* params){ + f.def(py::init(&Component_Trampoline::_init_)); + f.def("__setattr__",&Component_Trampoline::_setattr_); + + f.def("init", &Component::init); + f.def("reinit", &Component::reinit); + f.def("draw", [](Component& self, sofa::core::visual::VisualParams* params){ self.draw(params); }, pybind11::return_value_policy::reference); - // Override __setattr__ to invalidate the method cache when an "on*" attribute is reassigned - f.def("__setattr__", [](py::object self, const std::string& s, py::object value) { - // If the attribute starts with "on" and the new value is callable, invalidate the cached method - if (s.rfind("on", 0) == 0 && PyCallable_Check(value.ptr())) - { - auto* trampoline = dynamic_cast(py::cast(self)); - if (trampoline) - { - trampoline->invalidateMethodCache(s); - } - } +} + +void moduleAddController(py::module &m) { + py::class_> f(m, "Controller", + py::dynamic_attr(), + sofapython3::doc::controller::controllerClass); + + f.def(py::init(&Component_Trampoline::_init_)); + f.def("__setattr__",&Component_Trampoline::_setattr_); + + f.def("init", &Component::init); + f.def("reinit", &Component::reinit); + f.def("draw", [](Component& self, sofa::core::visual::VisualParams* params){ + self.draw(params); + }, pybind11::return_value_policy::reference); - // Delegate to the base class __setattr__ - BindingBase::__setattr__(self, s, value); - }); } + } diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.h similarity index 83% rename from bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.h rename to bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.h index 3deb345df..1d5166755 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.h @@ -21,30 +21,30 @@ #pragma once #include -#include -#include +#include #include +#include namespace sofapython3 { /** - * Empty controller shell that allows pybind11 to bind the init and reinit methods (since BaseController doesn't have + * Empty controller shell that allows pybind11 to bind the init and reinit methods (since BaseComponent doesn't have * them) */ -class Controller : public sofa::core::behavior::BaseController { +class Component : public sofa::core::objectmodel::BaseComponent { public: - SOFA_CLASS(Controller, sofa::core::behavior::BaseController); + SOFA_CLASS(Component, sofa::core::objectmodel::BaseComponent); void init() override {}; void reinit() override {}; }; -class Controller_Trampoline : public Controller +class Component_Trampoline : public Component { public: - SOFA_CLASS(Controller_Trampoline, Controller); + SOFA_CLASS(Component_Trampoline, Component); - Controller_Trampoline(); - ~Controller_Trampoline() override; + Component_Trampoline(); + ~Component_Trampoline() override; void init() override; void reinit() override; @@ -57,6 +57,9 @@ class Controller_Trampoline : public Controller /// Invalidates a specific entry in the method cache (called when a user reassigns an on* attribute) void invalidateMethodCache(const std::string& methodName); + static sofa::core::sptr _init_(pybind11::args& /*args*/, pybind11::kwargs& kwargs); + static void _setattr_(pybind11::object self, const std::string& s, pybind11::object value); + private: /// Initializes the Python object cache (m_pySelf and method cache) void initializePythonCache(); @@ -82,6 +85,7 @@ class Controller_Trampoline : public Controller bool m_cacheInitialized = false; }; +void moduleAddComponent(pybind11::module &m); void moduleAddController(pybind11::module &m); } /// namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller_doc.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component_doc.h similarity index 94% rename from bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller_doc.h rename to bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component_doc.h index 34e9bdbc2..cba10f0c5 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller_doc.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component_doc.h @@ -43,10 +43,10 @@ static auto controllerClass = import Sofa.Core - class MyController(Sofa.Core.Controller): + class MyComponent(Sofa.Core.Component): def __init__(self, *args, **kwargs): ## These are needed (and the normal way to override from a python class) - Sofa.Core.Controller.__init__(self, *args, **kwargs) + Sofa.Core.Component.__init__(self, *args, **kwargs) print(" Python::__init__::"+str(self.name)) def onEvent(self, event): @@ -59,7 +59,7 @@ static auto controllerClass = print("onAnimateBeginEvent") def createScene(rootNode): - controller = MyController(name="MyC") + controller = MyComponent(name="MyC") rootNode.addObject(controller) return rootNode )"; diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt index 5b096833b..d65bd55fe 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt @@ -15,8 +15,8 @@ set(HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Binding_ContactListener.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_ContactListener_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Context.h - ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Controller.h - ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Controller_doc.h + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Component.h + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Component_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_DataEngine.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_DataEngine_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_DrawTool.h @@ -68,7 +68,7 @@ set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Binding_BaseContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_ContactListener.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Context.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Controller.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Component.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_DataEngine.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Data/Binding_DataContainer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Data/Binding_DataString.cpp diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp index 23d0c89ab..561516c20 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp @@ -34,7 +34,7 @@ using sofa::helper::logging::Message; #include #include #include -#include +#include #include #include #include @@ -142,6 +142,7 @@ PYBIND11_MODULE(Core, core) moduleAddBaseCamera(core); moduleAddContactListener(core); moduleAddContext(core); + moduleAddComponent(core); moduleAddController(core); moduleAddDataEngine(core); moduleAddForceField(core); From 4fe39690ff15a86e54b931e1d0b8013e9980adcf Mon Sep 17 00:00:00 2001 From: Paul Baksic Date: Thu, 9 Apr 2026 14:25:42 +0200 Subject: [PATCH 2/5] Factorize implementation --- .../Sofa/Core/Binding_Component.cpp | 24 +++++-------------- .../SofaPython3/Sofa/Core/Binding_Component.h | 1 + 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.cpp index 270bfc6b1..8bd54e511 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.cpp @@ -247,11 +247,11 @@ void Component_Trampoline::_setattr_(pybind11::object self, const std::string& s -void moduleAddComponent(py::module &m) { +void moduleAddBase(py::module &m, const std::string & name) { py::class_> f(m, "Component", + py_shared_ptr> f(m, name.c_str(), py::dynamic_attr(), sofapython3::doc::controller::controllerClass); @@ -267,22 +267,10 @@ void moduleAddComponent(py::module &m) { } void moduleAddController(py::module &m) { - py::class_> f(m, "Controller", - py::dynamic_attr(), - sofapython3::doc::controller::controllerClass); - - f.def(py::init(&Component_Trampoline::_init_)); - f.def("__setattr__",&Component_Trampoline::_setattr_); - - f.def("init", &Component::init); - f.def("reinit", &Component::reinit); - f.def("draw", [](Component& self, sofa::core::visual::VisualParams* params){ - self.draw(params); - }, pybind11::return_value_policy::reference); - + moduleAddBase(m, "Controller"); +} +void moduleAddComponent(py::module &m) { + moduleAddBase(m, "Component"); } diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.h index 1d5166755..edd7418ad 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.h @@ -85,6 +85,7 @@ class Component_Trampoline : public Component bool m_cacheInitialized = false; }; +void moduleAddBase(pybind11::module &m, const std::string & name); void moduleAddComponent(pybind11::module &m); void moduleAddController(pybind11::module &m); From 4e15ec858597a4f8e336ebd3065e9d3695dceed8 Mon Sep 17 00:00:00 2001 From: Paul Baksic Date: Thu, 9 Apr 2026 15:48:25 +0200 Subject: [PATCH 3/5] try to factorize at best using CRTP --- .../Sofa/Core/Binding_Component.cpp | 216 +--------------- .../SofaPython3/Sofa/Core/Binding_Component.h | 61 +++-- .../Sofa/Core/Binding_Component.inl | 233 ++++++++++++++++++ .../Sofa/Core/Binding_Component_doc.h | 4 +- .../Sofa/Core/Binding_Controller.cpp | 96 ++++++++ .../Sofa/Core/Binding_Controller.h | 62 +++++ .../Sofa/Core/Binding_Controller_doc.h | 66 +++++ .../src/SofaPython3/Sofa/Core/CMakeLists.txt | 4 + .../SofaPython3/Sofa/Core/Submodule_Core.cpp | 1 + 9 files changed, 515 insertions(+), 228 deletions(-) create mode 100644 bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.inl create mode 100644 bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.cpp create mode 100644 bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.h create mode 100644 bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller_doc.h diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.cpp index 8bd54e511..252dcf478 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.cpp @@ -18,15 +18,7 @@ * Contact information: contact@sofa-framework.org * ******************************************************************************/ -#include -#include -#include -#include -#include -#include - -#include -#include +#include SOFAPYTHON3_BIND_ATTRIBUTE_ERROR() @@ -38,97 +30,6 @@ namespace sofapython3 using sofa::core::objectmodel::Event; using sofa::core::objectmodel::BaseComponent; -Component_Trampoline::Component_Trampoline() = default; - -Component_Trampoline::~Component_Trampoline() -{ - // Clean up Python objects while holding the GIL - if (m_cacheInitialized) - { - PythonEnvironment::gil acquire {"~Component_Trampoline"}; - m_methodCache.clear(); - m_pySelf = py::object(); - } -} - -void Component_Trampoline::initializePythonCache() -{ - if (m_cacheInitialized) - return; - - // Must be called with GIL held - m_pySelf = py::cast(this); - - // Pre-cache the fallback "onEvent" method via the standard cache path - getCachedMethod("onEvent"); - - m_cacheInitialized = true; -} - -py::object Component_Trampoline::getCachedMethod(const std::string& methodName) -{ - // Must be called with GIL held and cache initialized - - // Check if we've already looked up this method - auto it = m_methodCache.find(methodName); - if (it != m_methodCache.end()) - { - return it->second; - } - - // First time looking up this method - check if it exists - py::object method; - if (py::hasattr(m_pySelf, methodName.c_str())) - { - py::object fct = m_pySelf.attr(methodName.c_str()); - if (PyCallable_Check(fct.ptr())) - { - method = fct; - } - } - - // Cache the result (even if empty, to avoid repeated hasattr checks) - m_methodCache[methodName] = method; - return method; -} - -bool Component_Trampoline::callCachedMethod(const py::object& method, Event* event) -{ - // Must be called with GIL held - if (f_printLog.getValue()) - { - std::string eventStr = py::str(PythonFactory::toPython(event)); - msg_info() << "on" << event->getClassName() << " " << eventStr; - } - - py::object result = method(PythonFactory::toPython(event)); - if (result.is_none()) - return false; - - return py::cast(result); -} - -void Component_Trampoline::invalidateMethodCache(const std::string& methodName) -{ - if (!m_cacheInitialized) - return; - - // Remove the entry so getCachedMethod will re-resolve it on next call - m_methodCache.erase(methodName); -} - -std::string Component_Trampoline::getClassName() const -{ - PythonEnvironment::gil acquire {"getClassName"}; - - if (m_pySelf) - { - return py::str(py::type::of(m_pySelf).attr("__name__")); - } - - // Fallback for when cache isn't initialized yet - return py::str(py::type::of(py::cast(this)).attr("__name__")); -} void Component_Trampoline::draw(const sofa::core::visual::VisualParams* params) { @@ -153,124 +54,33 @@ void Component_Trampoline::reinit() }); } -/// If a method named "methodName" exists in the python controller, -/// methodName is called, with the Event's dict as argument -bool Component_Trampoline::callScriptMethod( - const py::object& self, Event* event, const std::string & methodName) -{ - if(f_printLog.getValue()) - { - std::string name = std::string("on")+event->getClassName(); - std::string eventStr = py::str(PythonFactory::toPython(event)); - msg_info() << name << " " << eventStr; - } - - if( py::hasattr(self, methodName.c_str()) ) - { - py::object fct = self.attr(methodName.c_str()); - py::object result = fct(PythonFactory::toPython(event)); - if(result.is_none()) - return false; - - return py::cast(result); - } - return false; -} - -void Component_Trampoline::handleEvent(Event* event) -{ - PythonEnvironment::executePython(this, [this, event](){ - // Ensure cache is initialized (in case init() wasn't called or - // handleEvent is called before init) - if (!m_cacheInitialized) - { - initializePythonCache(); - } - - // Build the event-specific method name (e.g., "onAnimateBeginEvent") - std::string methodName = std::string("on") + event->getClassName(); - - // Try the event-specific method first, then fall back to generic "onEvent" - py::object method = getCachedMethod(methodName); - if (!method) - method = getCachedMethod("onEvent"); - - if (method) - { - bool isHandled = callCachedMethod(method, event); - if (isHandled) - event->setHandled(); - } - }); -} - - -sofa::core::sptr Component_Trampoline::_init_(pybind11::args& /*args*/, pybind11::kwargs& kwargs) +void Component_Trampoline::handleEvent(sofa::core::objectmodel::Event* event) { - auto c = sofa::core::sptr (new Component_Trampoline()); - c->f_listening.setValue(true); - - for(auto kv : kwargs) - { - std::string key = py::cast(kv.first); - py::object value = py::reinterpret_borrow(kv.second); - - if( key == "name") - c->setName(py::cast(kv.second)); - try { - BindingBase::SetAttr(*c, key, value); - } catch (py::attribute_error& /*e*/) { - /// kwargs are used to set datafields to their initial values, - /// but they can also be used as simple python attributes, unrelated to SOFA. - /// thus we catch & ignore the py::attribute_error thrown by SetAttr - } - } - return c; + trampoline_handleEvent(event); } -void Component_Trampoline::_setattr_(pybind11::object self, const std::string& s, pybind11::object value) +std::string Component_Trampoline::getClassName() const { - // If the attribute starts with "on" and the new value is callable, invalidate the cached method - if (s.rfind("on", 0) == 0 && PyCallable_Check(value.ptr())) - { - auto* trampoline = dynamic_cast(py::cast(self)); - if (trampoline) - { - trampoline->invalidateMethodCache(s); - } - } - - // Delegate to the base class __setattr__ - BindingBase::__setattr__(self, s, value); + return trampoline_getClassName(); } - - -void moduleAddBase(py::module &m, const std::string & name) { +void moduleAddComponent(py::module &m) { py::class_> f(m, name.c_str(), - py::dynamic_attr(), - sofapython3::doc::controller::controllerClass); + Component_Trampoline, + BaseComponent, + py_shared_ptr> f(m, "Component", + py::dynamic_attr(), + sofapython3::doc::component::componentClass); - f.def(py::init(&Component_Trampoline::_init_)); - f.def("__setattr__",&Component_Trampoline::_setattr_); + f.def(py::init(&Trampoline_T::_init_)); + f.def("__setattr__",&Trampoline_T::_setattr_); f.def("init", &Component::init); f.def("reinit", &Component::reinit); f.def("draw", [](Component& self, sofa::core::visual::VisualParams* params){ self.draw(params); }, pybind11::return_value_policy::reference); - -} - -void moduleAddController(py::module &m) { - moduleAddBase(m, "Controller"); -} -void moduleAddComponent(py::module &m) { - moduleAddBase(m, "Component"); } diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.h index edd7418ad..5cc6ecbd7 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.h @@ -27,40 +27,27 @@ namespace sofapython3 { -/** - * Empty controller shell that allows pybind11 to bind the init and reinit methods (since BaseComponent doesn't have - * them) - */ -class Component : public sofa::core::objectmodel::BaseComponent { -public: - SOFA_CLASS(Component, sofa::core::objectmodel::BaseComponent); - void init() override {}; - void reinit() override {}; -}; -class Component_Trampoline : public Component +template +class Trampoline_T { public: - SOFA_CLASS(Component_Trampoline, Component); - Component_Trampoline(); - ~Component_Trampoline() override; + virtual ~Trampoline_T(); - void init() override; - void reinit() override; - void draw(const sofa::core::visual::VisualParams* params) override; - void handleEvent(sofa::core::objectmodel::Event* event) override; + void trampoline_handleEvent(sofa::core::objectmodel::Event* event); - std::string getClassName() const override; + std::string trampoline_getClassName() const; /// Invalidates a specific entry in the method cache (called when a user reassigns an on* attribute) void invalidateMethodCache(const std::string& methodName); - static sofa::core::sptr _init_(pybind11::args& /*args*/, pybind11::kwargs& kwargs); + static sofa::core::sptr _init_(pybind11::args& /*args*/, pybind11::kwargs& kwargs); + static void _setattr_(pybind11::object self, const std::string& s, pybind11::object value); -private: +protected: /// Initializes the Python object cache (m_pySelf and method cache) void initializePythonCache(); @@ -85,9 +72,37 @@ class Component_Trampoline : public Component bool m_cacheInitialized = false; }; -void moduleAddBase(pybind11::module &m, const std::string & name); + +/** + * Empty controller shell that allows pybind11 to bind the init and reinit methods (since BaseComponent doesn't have + * them) + */ +class Component : public sofa::core::objectmodel::BaseComponent { +public: + SOFA_CLASS(Component, sofa::core::objectmodel::BaseComponent); + void init() override {}; + void reinit() override {}; +}; + +class Component_Trampoline : public Component, public Trampoline_T +{ +public: + SOFA_CLASS(Component_Trampoline, Component); + + Component_Trampoline() = default; + virtual ~Component_Trampoline() = default; + + void init() override; + void reinit() override; + void draw(const sofa::core::visual::VisualParams* params) override; + + void handleEvent(sofa::core::objectmodel::Event* event) override; + + std::string getClassName() const override; + +}; + void moduleAddComponent(pybind11::module &m); -void moduleAddController(pybind11::module &m); } /// namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.inl b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.inl new file mode 100644 index 000000000..eb4a58394 --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.inl @@ -0,0 +1,233 @@ +/****************************************************************************** +* SofaPython3 plugin * +* (c) 2021 CNRS, University of Lille, INRIA * +* * +* This program 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. * +* * +* This program 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 this program. If not, see . * +******************************************************************************* +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#pragma once + + +#include +#include +#include +#include +#include +#include + +#include +#include + +/// Makes an alias for the pybind11 namespace to increase readability. +namespace py { using namespace pybind11; } + +namespace sofapython3 +{ +using sofa::core::objectmodel::Event; +using sofa::core::objectmodel::BaseComponent; + +template +Trampoline_T::~Trampoline_T() +{ + // Clean up Python objects while holding the GIL + if (m_cacheInitialized) + { + PythonEnvironment::gil acquire {"~Component_Trampoline"}; + m_methodCache.clear(); + m_pySelf = py::object(); + } +} + +template +void Trampoline_T::initializePythonCache() +{ + if (m_cacheInitialized) + return; + + // Must be called with GIL held + m_pySelf = py::cast(this); + + // Pre-cache the fallback "onEvent" method via the standard cache path + getCachedMethod("onEvent"); + + m_cacheInitialized = true; +} + +template +py::object Trampoline_T::getCachedMethod(const std::string& methodName) +{ + // Must be called with GIL held and cache initialized + + // Check if we've already looked up this method + auto it = m_methodCache.find(methodName); + if (it != m_methodCache.end()) + { + return it->second; + } + + // First time looking up this method - check if it exists + py::object method; + if (py::hasattr(m_pySelf, methodName.c_str())) + { + py::object fct = m_pySelf.attr(methodName.c_str()); + if (PyCallable_Check(fct.ptr())) + { + method = fct; + } + } + + // Cache the result (even if empty, to avoid repeated hasattr checks) + m_methodCache[methodName] = method; + return method; +} + +template +bool Trampoline_T::callCachedMethod(const py::object& method, Event* event) +{ + // Must be called with GIL held + // if (f_printLog.getValue()) + // { + // std::string eventStr = py::str(PythonFactory::toPython(event)); + // msg_info() << "on" << event->getClassName() << " " << eventStr; + // } + + py::object result = method(PythonFactory::toPython(event)); + if (result.is_none()) + return false; + + return py::cast(result); +} + +template +void Trampoline_T::invalidateMethodCache(const std::string& methodName) +{ + if (!m_cacheInitialized) + return; + + // Remove the entry so getCachedMethod will re-resolve it on next call + m_methodCache.erase(methodName); +} + +template +std::string Trampoline_T::trampoline_getClassName() const +{ + PythonEnvironment::gil acquire {"getClassName"}; + + if (m_pySelf) + { + return py::str(py::type::of(m_pySelf).attr("__name__")); + } + + // Fallback for when cache isn't initialized yet + return py::str(py::type::of(py::cast(this)).attr("__name__")); +} + +/// If a method named "methodName" exists in the python controller, +/// methodName is called, with the Event's dict as argument +template +bool Trampoline_T::callScriptMethod( + const py::object& self, Event* event, const std::string & methodName) +{ + // if(f_printLog.getValue()) + // { + // std::string name = std::string("on")+event->getClassName(); + // std::string eventStr = py::str(PythonFactory::toPython(event)); + // msg_info() << name << " " << eventStr; + // } + + if( py::hasattr(self, methodName.c_str()) ) + { + py::object fct = self.attr(methodName.c_str()); + py::object result = fct(PythonFactory::toPython(event)); + if(result.is_none()) + return false; + + return py::cast(result); + } + return false; +} + +template +void Trampoline_T::trampoline_handleEvent(Event* event) +{ + PythonEnvironment::executePython(dynamic_cast(this), [this, event](){ + // Ensure cache is initialized (in case init() wasn't called or + // handleEvent is called before init) + if (!m_cacheInitialized) + { + initializePythonCache(); + } + + // Build the event-specific method name (e.g., "onAnimateBeginEvent") + std::string methodName = std::string("on") + event->getClassName(); + + // Try the event-specific method first, then fall back to generic "onEvent" + py::object method = getCachedMethod(methodName); + if (!method) + method = getCachedMethod("onEvent"); + + if (method) + { + bool isHandled = callCachedMethod(method, event); + if (isHandled) + event->setHandled(); + } + }); +} + + + +template +sofa::core::sptr Trampoline_T::_init_(pybind11::args& /*args*/, pybind11::kwargs& kwargs) +{ + auto c = sofa::core::sptr (new T()); + c->f_listening.setValue(true); + + for(auto kv : kwargs) + { + std::string key = py::cast(kv.first); + py::object value = py::reinterpret_borrow(kv.second); + + if( key == "name") + c->setName(py::cast(kv.second)); + try { + BindingBase::SetAttr(*c, key, value); + } catch (py::attribute_error& /*e*/) { + /// kwargs are used to set datafields to their initial values, + /// but they can also be used as simple python attributes, unrelated to SOFA. + /// thus we catch & ignore the py::attribute_error thrown by SetAttr + } + } + return c; +} + +template +void Trampoline_T::_setattr_(pybind11::object self, const std::string& s, pybind11::object value) +{ + // If the attribute starts with "on" and the new value is callable, invalidate the cached method + if (s.rfind("on", 0) == 0 && PyCallable_Check(value.ptr())) + { + auto* trampoline = dynamic_cast(py::cast(self)); + if (trampoline) + { + trampoline->invalidateMethodCache(s); + } + } + + // Delegate to the base class __setattr__ + BindingBase::__setattr__(self, s, value); +} + +} diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component_doc.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component_doc.h index cba10f0c5..66f81cb52 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component_doc.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component_doc.h @@ -20,9 +20,9 @@ #pragma once -namespace sofapython3::doc::controller +namespace sofapython3::doc::component { -static auto controllerClass = +static auto componentClass = R"( Overridable class for user interaction on SOFA Components diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.cpp new file mode 100644 index 000000000..a35610adb --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.cpp @@ -0,0 +1,96 @@ +/****************************************************************************** +* SofaPython3 plugin * +* (c) 2021 CNRS, University of Lille, INRIA * +* * +* This program 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. * +* * +* This program 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 this program. If not, see . * +******************************************************************************* +* Contact information: contact@sofa-framework.org * +******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +SOFAPYTHON3_BIND_ATTRIBUTE_ERROR() + +/// Makes an alias for the pybind11 namespace to increase readability. +namespace py { using namespace pybind11; } + +namespace sofapython3 +{ +using sofa::core::objectmodel::Event; +using sofa::core::behavior::BaseController; + +void Controller_Trampoline::draw(const sofa::core::visual::VisualParams* params) +{ + PythonEnvironment::executePython(this, [this, params](){ + PYBIND11_OVERLOAD(void, Controller, draw, params); + }); +} + +void Controller_Trampoline::init() +{ + PythonEnvironment::executePython(this, [this](){ + // Initialize the Python object cache on first init + initializePythonCache(); + PYBIND11_OVERLOAD(void, Controller, init, ); + }); +} + +void Controller_Trampoline::reinit() +{ + PythonEnvironment::executePython(this, [this](){ + PYBIND11_OVERLOAD(void, Controller, reinit, ); + }); +} + + +void Controller_Trampoline::handleEvent(sofa::core::objectmodel::Event* event) +{ + trampoline_handleEvent(event); +} + +std::string Controller_Trampoline::getClassName() const +{ + return trampoline_getClassName(); +} + + +void moduleAddController(py::module &m) { + py::class_> f(m, "Controller", + py::dynamic_attr(), + sofapython3::doc::controller::controllerClass); + + f.def(py::init(&Trampoline_T::_init_)); + f.def("__setattr__",&Trampoline_T::_setattr_); + + f.def("init", &Controller::init); + f.def("reinit", &Controller::reinit); + f.def("draw", [](Controller& self, sofa::core::visual::VisualParams* params){ + self.draw(params); + }, pybind11::return_value_policy::reference);} + + + +} diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.h new file mode 100644 index 000000000..1a3d93a21 --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.h @@ -0,0 +1,62 @@ +/****************************************************************************** +* SofaPython3 plugin * +* (c) 2021 CNRS, University of Lille, INRIA * +* * +* This program 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. * +* * +* This program 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 this program. If not, see . * +******************************************************************************* +* Contact information: contact@sofa-framework.org * +******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +namespace sofapython3 { + +/** + * Empty controller shell that allows pybind11 to bind the init and reinit methods (since BaseController doesn't have + * them) + */ +class Controller : public sofa::core::behavior::BaseController { +public: + SOFA_CLASS(Controller, sofa::core::behavior::BaseController); + void init() override {}; + void reinit() override {}; +}; + +class Controller_Trampoline : public Controller, public Trampoline_T +{ +public: + SOFA_CLASS(Controller_Trampoline, Controller); + + Controller_Trampoline() = default; + + void init() override; + void reinit() override; + void draw(const sofa::core::visual::VisualParams* params) override; + + void handleEvent(sofa::core::objectmodel::Event* event) override; + + std::string getClassName() const override; + +}; + +void moduleAddController(pybind11::module &m); + +} /// namespace sofapython3 + diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller_doc.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller_doc.h new file mode 100644 index 000000000..6006ccaae --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller_doc.h @@ -0,0 +1,66 @@ +/****************************************************************************** +* SofaPython3 plugin * +* (c) 2021 CNRS, University of Lille, INRIA * +* * +* This program 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. * +* * +* This program 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 this program. If not, see . * +******************************************************************************* +* Contact information: contact@sofa-framework.org * +******************************************************************************/ + +#pragma once + +namespace sofapython3::doc::controller +{ +static auto controllerClass = + R"( + Overridable class for user interaction on SOFA Controllers + + It can catch events to trigger actions, such as onAnimateBeginEvent, onAnimateEndEvent or onPythonScriptEvent. + A new custom controller class needs to be defined to use a controller in a script, + and that class needs to reimplement the __init__ method. + + :example of use: + + In the following example, we redefine the controller class, and reimplement the __init__ method. + We also implement the onAnimateBeginEvent, that will be activted everytime an animation step ends + and that will simply print a message in the command line. In the createScene function, we initialize + the controller and add it to the rootNode. + If you run this with runSofa, it will simply endlessly print `onAnimateBeginEvent` + when you click the Animate button. + + .. code-block:: python + + import Sofa.Core + + class MyController(Sofa.Core.Controller): + def __init__(self, *args, **kwargs): + ## These are needed (and the normal way to override from a python class) + Sofa.Core.Controller.__init__(self, *args, **kwargs) + print(" Python::__init__::"+str(self.name)) + + def onEvent(self, event): + """This function is the fallback one that is called if the XXXX event is + received but there is not overriden onXXXX() method. + """ + print("generic event handler catched ", event) + + def onAnimateBeginEvent(self, event): + print("onAnimateBeginEvent") + + def createScene(rootNode): + controller = MyController(name="MyC") + rootNode.addObject(controller) + return rootNode + )"; +} diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt index d65bd55fe..c688a9ea1 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt @@ -16,7 +16,10 @@ set(HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Binding_ContactListener_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Context.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Component.h + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Component.inl ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Component_doc.h + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Controller.h + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Controller_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_DataEngine.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_DataEngine_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_DrawTool.h @@ -69,6 +72,7 @@ set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Binding_ContactListener.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Context.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Component.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Controller.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_DataEngine.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Data/Binding_DataContainer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Data/Binding_DataString.cpp diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp index 561516c20..390425c4b 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp @@ -35,6 +35,7 @@ using sofa::helper::logging::Message; #include #include #include +#include #include #include #include From 8c3525f64daef91289cc3f1b606d77317e8a729b Mon Sep 17 00:00:00 2001 From: Paul Baksic Date: Thu, 9 Apr 2026 16:02:49 +0200 Subject: [PATCH 4/5] Add log --- .../Sofa/Core/Binding_Component.inl | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.inl b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.inl index eb4a58394..a61695db8 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.inl +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.inl @@ -57,7 +57,7 @@ void Trampoline_T::initializePythonCache() return; // Must be called with GIL held - m_pySelf = py::cast(this); + m_pySelf = py::cast(dynamic_cast(this)); // Pre-cache the fallback "onEvent" method via the standard cache path getCachedMethod("onEvent"); @@ -96,12 +96,14 @@ py::object Trampoline_T::getCachedMethod(const std::string& methodName) template bool Trampoline_T::callCachedMethod(const py::object& method, Event* event) { + auto thisT = dynamic_cast(this); + // Must be called with GIL held - // if (f_printLog.getValue()) - // { - // std::string eventStr = py::str(PythonFactory::toPython(event)); - // msg_info() << "on" << event->getClassName() << " " << eventStr; - // } + if (thisT->f_printLog.getValue()) + { + std::string eventStr = py::str(PythonFactory::toPython(event)); + msg_info(thisT) << "on" << event->getClassName() << " " << eventStr; + } py::object result = method(PythonFactory::toPython(event)); if (result.is_none()) @@ -140,12 +142,14 @@ template bool Trampoline_T::callScriptMethod( const py::object& self, Event* event, const std::string & methodName) { - // if(f_printLog.getValue()) - // { - // std::string name = std::string("on")+event->getClassName(); - // std::string eventStr = py::str(PythonFactory::toPython(event)); - // msg_info() << name << " " << eventStr; - // } + auto thisT = dynamic_cast(this); + + if(thisT->f_printLog.getValue()) + { + std::string name = std::string("on")+event->getClassName(); + std::string eventStr = py::str(PythonFactory::toPython(event)); + msg_info(thisT) << name << " " << eventStr; + } if( py::hasattr(self, methodName.c_str()) ) { From b631c8a6147b5f952de5ee278190ee207500cddf Mon Sep 17 00:00:00 2001 From: Paul Baksic Date: Thu, 9 Apr 2026 16:07:44 +0200 Subject: [PATCH 5/5] Use static cast instead --- .../Sofa/src/SofaPython3/Sofa/Core/Binding_Component.inl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.inl b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.inl index a61695db8..579bd802d 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.inl +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.inl @@ -57,7 +57,7 @@ void Trampoline_T::initializePythonCache() return; // Must be called with GIL held - m_pySelf = py::cast(dynamic_cast(this)); + m_pySelf = py::cast(static_cast(this)); // Pre-cache the fallback "onEvent" method via the standard cache path getCachedMethod("onEvent"); @@ -96,7 +96,7 @@ py::object Trampoline_T::getCachedMethod(const std::string& methodName) template bool Trampoline_T::callCachedMethod(const py::object& method, Event* event) { - auto thisT = dynamic_cast(this); + auto thisT = static_cast(this); // Must be called with GIL held if (thisT->f_printLog.getValue()) @@ -142,7 +142,7 @@ template bool Trampoline_T::callScriptMethod( const py::object& self, Event* event, const std::string & methodName) { - auto thisT = dynamic_cast(this); + auto thisT = static_cast(this); if(thisT->f_printLog.getValue()) { @@ -166,7 +166,7 @@ bool Trampoline_T::callScriptMethod( template void Trampoline_T::trampoline_handleEvent(Event* event) { - PythonEnvironment::executePython(dynamic_cast(this), [this, event](){ + PythonEnvironment::executePython(static_cast(this), [this, event](){ // Ensure cache is initialized (in case init() wasn't called or // handleEvent is called before init) if (!m_cacheInitialized)