From 2a7b06dc8387d2fcaacec5ff34e20014818867c3 Mon Sep 17 00:00:00 2001 From: Damien Marchal Date: Tue, 14 Sep 2021 22:48:41 +0200 Subject: [PATCH] [Binding/Sofa.Core] Implement a custom getClassName() for trampoline classes. Trampoline classes are used to implement sofa components in python. The default getClassName() returns the name of the c++ trampoline class name instead of the name of the python overrides. This is why you can see stuff like that: ``` Controller_Trampoline(myObject) ForceField_Trampoline(myFF) ``` This PR retrieve the real class name from python to returns it in a custom getClassName. Eg: ``` class RoboticsController(Sofa.Core.Controller): pass ``` will appears with its real name in the GUI: ``` RoboticController(myObject) ``` --- .../Sofa/Core/Binding_Controller.cpp | 150 +++++++++--------- .../Sofa/Core/Binding_Controller.h | 2 + .../Sofa/Core/Binding_DataEngine.cpp | 112 +++++++------ .../Sofa/Core/Binding_DataEngine.h | 1 + .../Sofa/Core/Binding_ForceField.cpp | 9 ++ .../Sofa/Core/Binding_ForceField.h | 1 + 6 files changed, 152 insertions(+), 123 deletions(-) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.cpp index d9b69ace..002286df 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.cpp @@ -35,88 +35,96 @@ namespace py { using namespace pybind11; } namespace sofapython3 { - using sofa::core::objectmodel::Event; - using sofa::core::objectmodel::BaseObject; +using sofa::core::objectmodel::Event; +using sofa::core::objectmodel::BaseObject; - void Controller_Trampoline::init() - { - PythonEnvironment::gil acquire {"Controller_init"}; - PYBIND11_OVERLOAD(void, Controller, init, ); - } +std::string Controller_Trampoline::getClassName() const +{ + PythonEnvironment::gil acquire {"getClassName"}; - void Controller_Trampoline::reinit() - { - PythonEnvironment::gil acquire {"Controller_reinit"}; - PYBIND11_OVERLOAD(void, Controller, reinit, ); - } + // Get the actual class name from python. + return py::str(py::cast(this).get_type().attr("__name__")); +} + +void Controller_Trampoline::init() +{ + PythonEnvironment::gil acquire {"Controller_init"}; + PYBIND11_OVERLOAD(void, Controller, init, ); +} + +void Controller_Trampoline::reinit() +{ + PythonEnvironment::gil acquire {"Controller_reinit"}; + PYBIND11_OVERLOAD(void, Controller, reinit, ); +} - /// If a method named "methodName" exists in the python controller, - /// methodName is called, with the Event's dict as argument - void Controller_Trampoline::callScriptMethod( - const py::object& self, Event* event, const std::string & methodName) +/// If a method named "methodName" exists in the python controller, +/// methodName is called, with the Event's dict as argument +void Controller_Trampoline::callScriptMethod( + const py::object& self, Event* event, const std::string & methodName) +{ + if( py::hasattr(self, methodName.c_str()) ) { - if( py::hasattr(self, methodName.c_str()) ) - { - py::object fct = self.attr(methodName.c_str()); - fct(PythonFactory::toPython(event)); - } + py::object fct = self.attr(methodName.c_str()); + fct(PythonFactory::toPython(event)); } +} + +void Controller_Trampoline::handleEvent(Event* event) +{ + PythonEnvironment::gil acquire {"Controller_handleEvent"}; - void Controller_Trampoline::handleEvent(Event* event) + py::object self = py::cast(this); + std::string name = std::string("on")+event->getClassName(); + /// Is there a method with this name in the class ? + if( py::hasattr(self, name.c_str()) ) { - PythonEnvironment::gil acquire {"Controller_handleEvent"}; - - py::object self = py::cast(this); - std::string name = std::string("on")+event->getClassName(); - /// Is there a method with this name in the class ? - if( py::hasattr(self, name.c_str()) ) - { - py::object fct = self.attr(name.c_str()); - if (PyCallable_Check(fct.ptr())) { - callScriptMethod(self, event, name); - return; - } + py::object fct = self.attr(name.c_str()); + if (PyCallable_Check(fct.ptr())) { + callScriptMethod(self, event, name); + return; } - - /// Is the fallback method available. - callScriptMethod(self, event, "onEvent"); } - void moduleAddController(py::module &m) { - py::class_> f(m, "Controller", - py::dynamic_attr(), - sofapython3::doc::controller::Controller); - - 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); - } + /// Is the fallback method available. + callScriptMethod(self, event, "onEvent"); +} + +void moduleAddController(py::module &m) { + py::class_> f(m, "Controller", + py::dynamic_attr(), + sofapython3::doc::controller::Controller); + + 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); +} } diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.h index 7e04b6c3..0524e6f3 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.h @@ -45,6 +45,8 @@ class Controller_Trampoline : public Controller void reinit() override; void handleEvent(sofa::core::objectmodel::Event* event) override; + std::string getClassName() const override; + private: void callScriptMethod(const pybind11::object& self, sofa::core::objectmodel::Event* event, const std::string& methodName); diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_DataEngine.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_DataEngine.cpp index 8ecf8bee..d44fc8c0 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_DataEngine.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_DataEngine.cpp @@ -34,67 +34,75 @@ namespace py { using namespace pybind11; } namespace sofapython3 { - using sofa::core::objectmodel::Event; - using sofa::core::DataEngine; - using sofa::core::objectmodel::BaseData; - using sofa::core::objectmodel::BaseObject; - using sofa::core::objectmodel::DDGNode; +using sofa::core::objectmodel::Event; +using sofa::core::DataEngine; +using sofa::core::objectmodel::BaseData; +using sofa::core::objectmodel::BaseObject; +using sofa::core::objectmodel::DDGNode; - void DataEngine_Trampoline::init() - { - PythonEnvironment::gil acquire; - PYBIND11_OVERLOAD(void, DataEngine, init, ); - } +std::string DataEngine_Trampoline::getClassName() const +{ + PythonEnvironment::gil acquire {"getClassName"}; + + // Get the actual class name from python. + return py::str(py::cast(this).get_type().attr("__name__")); +} + +void DataEngine_Trampoline::init() +{ + PythonEnvironment::gil acquire; + PYBIND11_OVERLOAD(void, DataEngine, init, ); +} - void DataEngine_Trampoline::doUpdate() - { - PythonEnvironment::gil acquire; - py::object self = py::cast(this); - if (py::hasattr(self, "update")) { - py::object fct = self.attr("update"); - try { - fct(); - return; - } catch (std::exception& /*e*/) { - throw py::type_error(this->getName() + ": The DataEngine requires an update method with no parameter and no return type"); - } +void DataEngine_Trampoline::doUpdate() +{ + PythonEnvironment::gil acquire; + py::object self = py::cast(this); + if (py::hasattr(self, "update")) { + py::object fct = self.attr("update"); + try { + fct(); + return; + } catch (std::exception& /*e*/) { + throw py::type_error(this->getName() + ": The DataEngine requires an update method with no parameter and no return type"); } - throw py::type_error(this->getName() + " has no update() method."); } + throw py::type_error(this->getName() + " has no update() method."); +} - void moduleAddDataEngine(pybind11::module &m) - { - py::class_> f(m, "DataEngine", - py::dynamic_attr(), - sofapython3::doc::dataengine::DataEngine); +void moduleAddDataEngine(pybind11::module &m) +{ + py::class_> f(m, "DataEngine", + py::dynamic_attr(), + sofapython3::doc::dataengine::DataEngine); - f.def(py::init([](py::args& /*args*/, py::kwargs& kwargs) - { - auto c = new DataEngine_Trampoline(); + f.def(py::init([](py::args& /*args*/, py::kwargs& kwargs) + { + auto c = new DataEngine_Trampoline(); - for(auto kv : kwargs) - { - std::string key = py::cast(kv.first); - py::object value = py::reinterpret_borrow(kv.second); + 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 - } + 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; - })); + } + return c; + })); - f.def("addInput", &DataEngine::addInput, sofapython3::doc::dataengine::addInput); - f.def("addOutput", &DataEngine::addOutput, sofapython3::doc::dataengine::addOutput); - } + f.def("addInput", &DataEngine::addInput, sofapython3::doc::dataengine::addInput); + f.def("addOutput", &DataEngine::addOutput, sofapython3::doc::dataengine::addOutput); +} } /// namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_DataEngine.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_DataEngine.h index b1360fc7..f2afb0ad 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_DataEngine.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_DataEngine.h @@ -32,6 +32,7 @@ class DataEngine_Trampoline : public sofa::core::DataEngine void init() override; void doUpdate() override; + std::string getClassName() const override; }; void moduleAddDataEngine(pybind11::module &m); diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_ForceField.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_ForceField.cpp index b2dd89b6..33015d73 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_ForceField.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_ForceField.cpp @@ -58,6 +58,15 @@ namespace sofapython3 template ForceField_Trampoline::~ForceField_Trampoline() = default; + template + std::string ForceField_Trampoline::getClassName() const + { + PythonEnvironment::gil acquire {"getClassName"}; + + // Get the actual class name from python. + return py::str(py::cast(this).get_type().attr("__name__")); + } + template void ForceField_Trampoline::init() { diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_ForceField.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_ForceField.h index 3c3cfa74..6f6861bf 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_ForceField.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_ForceField.h @@ -42,6 +42,7 @@ class ForceField_Trampoline : public sofa::core::behavior::ForceField ~ForceField_Trampoline() override; void init() override; + std::string getClassName() const override; void addForce(const sofa::core::MechanicalParams* mparams, DataVecDeriv& f, const DataVecCoord& x, const DataVecDeriv& v) override; void addDForce(const sofa::core::MechanicalParams* mparams, DataVecDeriv& df, const DataVecDeriv& dx ) override;