diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseComponent.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseComponent.cpp index a211d32f..3e0c6d82 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_Component.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.cpp new file mode 100644 index 00000000..252dcf47 --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.cpp @@ -0,0 +1,88 @@ +/****************************************************************************** +* 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 + +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::objectmodel::BaseComponent; + + +void Component_Trampoline::draw(const sofa::core::visual::VisualParams* params) +{ + PythonEnvironment::executePython(this, [this, params](){ + PYBIND11_OVERLOAD(void, Component, draw, params); + }); +} + +void Component_Trampoline::init() +{ + PythonEnvironment::executePython(this, [this](){ + // Initialize the Python object cache on first init + initializePythonCache(); + PYBIND11_OVERLOAD(void, Component, init, ); + }); +} + +void Component_Trampoline::reinit() +{ + PythonEnvironment::executePython(this, [this](){ + PYBIND11_OVERLOAD(void, Component, reinit, ); + }); +} + + +void Component_Trampoline::handleEvent(sofa::core::objectmodel::Event* event) +{ + trampoline_handleEvent(event); +} + +std::string Component_Trampoline::getClassName() const +{ + return trampoline_getClassName(); +} + +void moduleAddComponent(py::module &m) { + py::class_> f(m, "Component", + py::dynamic_attr(), + sofapython3::doc::component::componentClass); + + 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); +} + + + +} diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.h new file mode 100644 index 00000000..5cc6ecbd --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.h @@ -0,0 +1,108 @@ +/****************************************************************************** +* 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 + +namespace sofapython3 { + + +template +class Trampoline_T +{ +public: + + virtual ~Trampoline_T(); + + + void trampoline_handleEvent(sofa::core::objectmodel::Event* event); + + 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 void _setattr_(pybind11::object self, const std::string& s, pybind11::object value); + +protected: + /// Initializes the Python object cache (m_pySelf and method cache) + void initializePythonCache(); + + /// Returns a cached method if it exists, or an empty object if not + pybind11::object getCachedMethod(const std::string& methodName); + + /// Calls a cached Python method with the given event + bool callCachedMethod(const pybind11::object& method, sofa::core::objectmodel::Event* event); + + /// Legacy method for uncached calls (fallback) + bool callScriptMethod(const pybind11::object& self, sofa::core::objectmodel::Event* event, + const std::string& methodName); + + /// Cached Python self reference (avoids repeated py::cast(this)) + pybind11::object m_pySelf; + + /// Cache of Python method objects, keyed by method name (including "onEvent" fallback) + /// Stores the method object if it exists, or an empty object if checked and not found + std::unordered_map m_methodCache; + + /// Flag indicating whether the cache has been initialized + bool m_cacheInitialized = false; +}; + + +/** + * 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); + +} /// 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 00000000..579bd802 --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component.inl @@ -0,0 +1,237 @@ +/****************************************************************************** +* 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(static_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) +{ + auto thisT = static_cast(this); + + // Must be called with GIL held + 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()) + 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) +{ + auto thisT = static_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()) ) + { + 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(static_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 new file mode 100644 index 00000000..66f81cb5 --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Component_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::component +{ +static auto componentClass = + R"( + Overridable class for user interaction on SOFA Components + + 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 MyComponent(Sofa.Core.Component): + def __init__(self, *args, **kwargs): + ## These are needed (and the normal way to override from a python class) + Sofa.Core.Component.__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 = MyComponent(name="MyC") + rootNode.addObject(controller) + return rootNode + )"; +} diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.cpp index f99484dd..a35610ad 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -36,99 +37,7 @@ namespace py { using namespace pybind11; } namespace sofapython3 { using sofa::core::objectmodel::Event; -using sofa::core::objectmodel::BaseComponent; - -Controller_Trampoline::Controller_Trampoline() = default; - -Controller_Trampoline::~Controller_Trampoline() -{ - // Clean up Python objects while holding the GIL - if (m_cacheInitialized) - { - PythonEnvironment::gil acquire {"~Controller_Trampoline"}; - m_methodCache.clear(); - m_pySelf = py::object(); - } -} - -void Controller_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 Controller_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 Controller_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 Controller_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 Controller_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__")); -} +using sofa::core::behavior::BaseController; void Controller_Trampoline::draw(const sofa::core::visual::VisualParams* params) { @@ -153,110 +62,35 @@ void Controller_Trampoline::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( - 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 Controller_Trampoline::handleEvent(sofa::core::objectmodel::Event* event) +{ + trampoline_handleEvent(event); } -void Controller_Trampoline::handleEvent(Event* event) +std::string Controller_Trampoline::getClassName() const { - 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(); - } - }); + return trampoline_getClassName(); } + void moduleAddController(py::module &m) { py::class_> f(m, "Controller", - py::dynamic_attr(), - sofapython3::doc::controller::controllerClass); + Controller_Trampoline, + BaseComponent, + py_shared_ptr> f(m, "Controller", + 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(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); - - // 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); - } - } + }, 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_Controller.h index 3deb345d..1a3d93a2 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.h @@ -22,8 +22,9 @@ #include #include -#include +#include #include +#include namespace sofapython3 { @@ -38,13 +39,12 @@ class Controller : public sofa::core::behavior::BaseController { void reinit() override {}; }; -class Controller_Trampoline : public Controller +class Controller_Trampoline : public Controller, public Trampoline_T { public: SOFA_CLASS(Controller_Trampoline, Controller); - Controller_Trampoline(); - ~Controller_Trampoline() override; + Controller_Trampoline() = default; void init() override; void reinit() override; @@ -54,32 +54,6 @@ class Controller_Trampoline : public Controller std::string getClassName() const override; - /// Invalidates a specific entry in the method cache (called when a user reassigns an on* attribute) - void invalidateMethodCache(const std::string& methodName); - -private: - /// Initializes the Python object cache (m_pySelf and method cache) - void initializePythonCache(); - - /// Returns a cached method if it exists, or an empty object if not - pybind11::object getCachedMethod(const std::string& methodName); - - /// Calls a cached Python method with the given event - bool callCachedMethod(const pybind11::object& method, sofa::core::objectmodel::Event* event); - - /// Legacy method for uncached calls (fallback) - bool callScriptMethod(const pybind11::object& self, sofa::core::objectmodel::Event* event, - const std::string& methodName); - - /// Cached Python self reference (avoids repeated py::cast(this)) - pybind11::object m_pySelf; - - /// Cache of Python method objects, keyed by method name (including "onEvent" fallback) - /// Stores the method object if it exists, or an empty object if checked and not found - std::unordered_map m_methodCache; - - /// Flag indicating whether the cache has been initialized - bool m_cacheInitialized = false; }; void moduleAddController(pybind11::module &m); diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller_doc.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller_doc.h index 34e9bdbc..6006ccaa 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller_doc.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller_doc.h @@ -24,7 +24,7 @@ namespace sofapython3::doc::controller { static auto controllerClass = R"( - Overridable class for user interaction on SOFA Components + 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, diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt index 5b096833..c688a9ea 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt @@ -15,6 +15,9 @@ 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_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 @@ -68,6 +71,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_Component.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Controller.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_DataEngine.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Data/Binding_DataContainer.cpp diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp index 23d0c89a..390425c4 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp @@ -34,6 +34,7 @@ using sofa::helper::logging::Message; #include #include #include +#include #include #include #include @@ -142,6 +143,7 @@ PYBIND11_MODULE(Core, core) moduleAddBaseCamera(core); moduleAddContactListener(core); moduleAddContext(core); + moduleAddComponent(core); moduleAddController(core); moduleAddDataEngine(core); moduleAddForceField(core);