diff --git a/Plugin/CMakeLists.txt b/Plugin/CMakeLists.txt index f53e987a..b2b1aabc 100644 --- a/Plugin/CMakeLists.txt +++ b/Plugin/CMakeLists.txt @@ -4,6 +4,7 @@ set(HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/initModule.h ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/PythonEnvironment.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/PyBindHelper.h ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/SceneLoaderPY3.h ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/SpellingSuggestionHelper.h diff --git a/Plugin/src/SofaPython3/PyBindHelper.h b/Plugin/src/SofaPython3/PyBindHelper.h new file mode 100644 index 00000000..e8a5787b --- /dev/null +++ b/Plugin/src/SofaPython3/PyBindHelper.h @@ -0,0 +1,190 @@ +/****************************************************************************** +* 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 <http://www.gnu.org/licenses/>. * +******************************************************************************* +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#pragma once + +#include <SofaPython3/config.h> +#include <sofa/helper/logging/Messaging.h> +#include <string> +#include <vector> +#include <regex> +#include <pybind11/pybind11.h> + +namespace sofapython3 +{ + +class PythonMethodDescription +{ +public: + void add_entry(const std::string& signature="") + { + signatures.push_back(signature); + docstrings.push_back(""); + } + + /// Parse the docstring generated by pybind11 to extract the different method signatures + /// and for each the associated docstring. + void parse_docstring(const std::string& name, std::string docstring) + { + std::smatch m; + std::string last; + std::regex e; + + if(signatures.size()==0) + return; + + // easy path. Take the signature, then consider the remaining as a docstring + if(signatures.size()==1) + { + e = std::regex("("+name+".*)"); + if (std::regex_search (docstring, m,e)) + { + for (auto x:m) + last = x; + if(signatures[0].size()==0) + signatures[0] = last; + docstrings[0] = m.suffix().str(); + } + return; + } + + int idx = 0; + e = std::regex("(1\\. )("+name+".*)"); + while (std::regex_search (docstring, m,e)) + { + for (auto x:m) + last = x; + + if(signatures[idx].size()==0) + signatures[idx] = last; + + if(idx>0) + docstrings[idx-1] = m.prefix().str(); + idx++; + docstring = m.suffix().str(); + + auto pex = "("+std::to_string(idx+1)+"\\. )("+name+".*)"; + e = std::regex(pex); + } + if(!docstring.empty()) + docstrings[idx-1] = docstring; + } + + /// Generates the docstring from the extracted ones from pybind11 and applying the + /// customs signatures, if any. + const std::string build_docstring(const std::string& name) const + { + std::stringstream tmp; + if(signatures.size() == 1) + { + tmp << signatures[0]; + tmp << docstrings[0]; + return tmp.str(); + } + else + { + tmp << name <<"(*args, **kwargs)\n"; + tmp << "Overloaded function.\n\n"; + } + for(unsigned int i=0;i<docstrings.size();i++) + { + tmp << (i+1) << ". " << signatures[i] ; + tmp << docstrings[i] ; + } + return tmp.str(); + } + +private: + std::vector<std::string> signatures; //< signatures for the method overrides + std::vector<std::string> docstrings; //< docstrings for the method overrides + +}; + +template<typename Class> +class def_method +{ +public: + def_method(Class classI, const std::string& name_) : + classInstance(classI), name(name_) + { + } + + virtual ~def_method() + { + description.parse_docstring(name, get_current_docstring()); + set_docstring(description.build_docstring(name)); + } + + template<typename ... Args> + def_method& add_override(Args ... args) + { + classInstance.def(name.c_str(), args...); + description.add_entry(); + return *this; + } + + template<typename ... Args> + def_method& add_override(const char* signature, Args ... args) + { + classInstance.def(name.c_str(), args...); + description.add_entry(signature); + return *this; + } + + std::string get_current_docstring() const { + std::string tmp; + // get the raw python object out of the classInstance + auto method = classInstance.attr(name.c_str()).ptr(); + // if it is an instance method get the raw CFunction + if(Py_TYPE(method) == &PyInstanceMethod_Type){ + method = ((PyInstanceMethodObject*)(method))->func; + } + // if it is a CFunction, get its docstring + if(Py_TYPE(method) == &PyCFunction_Type){ + auto cmethod = (PyCFunctionObject*)method; + tmp = std::string(cmethod->m_ml->ml_doc); + } + return tmp; + } + + void set_docstring(const std::string& docstring) const + { + // get the raw python object out of the classInstance + auto method = classInstance.attr(name.c_str()).ptr(); + // if it is an instance method get the raw CFunction + if(Py_TYPE(method) == &PyInstanceMethod_Type){ + method = ((PyInstanceMethodObject*)(method))->func; + } + // if it is a CFunction, get its docstring + if(Py_TYPE(method) == &PyCFunction_Type){ + auto cmethod = (PyCFunctionObject*)method; + cmethod->m_ml->ml_doc = strdup(docstring.c_str()); + }else + { + msg_error("SofaPython3") << "Unable to build docstring for method " << name; + } + } + +private: + Class classInstance; //< stores the pybind::class_<XXX, > + std::string name; //< stores the name of the method we declares + PythonMethodDescription description; +}; + +} diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp index 6aa8d763..81365ecf 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp @@ -449,12 +449,21 @@ py::object BindingBase::setDataValues(Base& self, py::kwargs kwargs) return py::none(); } +auto getBaseBinding(py::module& m) +{ + static py::class_<Base, py_shared_ptr<Base>> base(m, "Base", py::dynamic_attr(), doc::base::BaseClass); + return base; +} + +void moduleForwardAddBase(py::module& m) +{ + getBaseBinding(m); +} + void moduleAddBase(py::module &m) { - moduleForwardAddBaseData(m); - moduleForwardAddBaseLink(m); + auto base = getBaseBinding(m); - py::class_<Base, py_shared_ptr<Base>> base(m, "Base", py::dynamic_attr(), doc::base::BaseClass); /// set & get the name as string. The alternative is to access the data field using /// obj.name.value = "aName" base.def("getName", [](Base& b){ return b.getName(); }, sofapython3::doc::base::getName); diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.h index 670ce54b..06a98e59 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.h @@ -68,7 +68,9 @@ class BindingBase static std::string getLinkPath(sofa::core::objectmodel::Base& self); }; - +/// Forward declaration in pybind11. +/// more details in: https://github.com/sofa-framework/SofaPython3/pull/457 +void moduleForwardAddBase(pybind11::module& m); void moduleAddBase(pybind11::module& m); } /// namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseClass.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseClass.cpp new file mode 100644 index 00000000..7fbbc0dd --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseClass.cpp @@ -0,0 +1,48 @@ +/****************************************************************************** +* SOFA, Simulation Open-Framework Architecture * +* (c) 2021 INRIA, USTL, UJF, CNRS, MGH * +* * +* 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 <http://www.gnu.org/licenses/>. * +******************************************************************************* +* Contact information: contact@sofa-framework.org * +******************************************************************************/ + +#include <SofaPython3/Sofa/Core/Binding_BaseClass.h> +#include <SofaPython3/Sofa/Core/Binding_BaseClass_doc.h> +#include <sofa/core/objectmodel/BaseClass.h> + +namespace sofapython3 +{ +using namespace sofa::core::objectmodel; + +/// Makes an alias for the pybind11 namespace to increase readability. +namespace py { using namespace pybind11; } + +auto getBaseClassBinding(py::module& m) +{ + static py::class_<BaseClass, std::unique_ptr<BaseClass, py::nodelete>> base(m, "BaseClass", doc::baseclass::classdocstring); + return base; +} + +void moduleForwardAddBaseClass(py::module& m) +{ + getBaseClassBinding(m); +} + +void moduleAddBaseClass(py::module &m) +{ + // adds here the BaseClass's binded function (if any). +} + +} /// namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseClass.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseClass.h new file mode 100644 index 00000000..6ae2c6b7 --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseClass.h @@ -0,0 +1,32 @@ +/****************************************************************************** +* SOFA, Simulation Open-Framework Architecture * +* (c) 2021 INRIA, USTL, UJF, CNRS, MGH * +* * +* 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 <http://www.gnu.org/licenses/>. * +******************************************************************************* +* Contact information: contact@sofa-framework.org * +******************************************************************************/ + +#pragma once + +#include <pybind11/pybind11.h> + +namespace sofapython3 { + +/// Forward declaration in pybind11. +/// more details in: https://github.com/sofa-framework/SofaPython3/pull/457 +void moduleForwardAddBaseClass(pybind11::module& m); +void moduleAddBaseClass(pybind11::module& m); + +} /// namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseClass_doc.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseClass_doc.h new file mode 100644 index 00000000..2ed3d7d3 --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseClass_doc.h @@ -0,0 +1,31 @@ +/****************************************************************************** +* SOFA, Simulation Open-Framework Architecture * +* (c) 2021 INRIA, USTL, UJF, CNRS, MGH * +* * +* 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 <http://www.gnu.org/licenses/>. * +******************************************************************************* +* Contact information: contact@sofa-framework.org * +******************************************************************************/ + +#pragma once + +namespace sofapython3::doc::baseclass { + +static auto classdocstring = + R"(Class hierarchy reflection base class +This class provides information on the class and parent classes of components. +It is created by using the SOFA_CLASS macro on each new class declaration. +All classes deriving from Base should use the SOFA_CLASS macro within their declaration.)"; + +} diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp index ecc83750..d00341e6 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp @@ -207,8 +207,6 @@ void moduleForwardAddBaseData(py::module& m) void moduleAddBaseData(py::module& m) { /// Register the BaseData binding into the pybind11 system. - //py::class_<BaseData, std::unique_ptr<sofa::core::objectmodel::BaseData, pybind11::nodelete>> data(m, "Data", sofapython3::doc::baseData::BaseDataClass); - auto data =getPythonClassForBaseData(m); data.def("getName", [](BaseData& b){ return b.getName(); }, sofapython3::doc::baseData::getName); data.def("setName", [](BaseData& b, const std::string& s){ b.setName(s); }, sofapython3::doc::baseData::setName); diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.h index badd6522..54be7010 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.h @@ -24,6 +24,8 @@ namespace sofapython3 { + /// Forward declaration in pybind11. + /// more details in: https://github.com/sofa-framework/SofaPython3/pull/457 void moduleForwardAddBaseData(pybind11::module& m); void moduleAddBaseData(pybind11::module& m); diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseLink.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseLink.h index 1cf5df4a..7beca2fa 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseLink.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseLink.h @@ -24,6 +24,8 @@ namespace sofapython3 { + /// Forward declaration in pybind11. + /// more details in: https://github.com/sofa-framework/SofaPython3/pull/457 void moduleForwardAddBaseLink(pybind11::module& m); void moduleAddBaseLink(pybind11::module& m); diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseMeshTopology.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseMeshTopology.cpp index 2759c530..886179b0 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseMeshTopology.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseMeshTopology.cpp @@ -21,6 +21,7 @@ #include <SofaPython3/Sofa/Core/Binding_Base.h> #include <SofaPython3/Sofa/Core/Binding_BaseContext.h> +#include <SofaPython3/Sofa/Core/Binding_BaseMeshTopology.h> #include <SofaPython3/PythonFactory.h> #include <sofa/core/BaseState.h> #include <sofa/core/objectmodel/BaseObject.h> @@ -36,8 +37,21 @@ using namespace sofa::core::topology; namespace sofapython3 { + +auto getPythonClassForBaseMeshTopology(py::module& m) +{ + /// Register the BaseData binding into the pybind11 system. + static py::class_<BaseMeshTopology, Topology, py_shared_ptr<BaseMeshTopology>> basemesh(m, "BaseMeshTopology"); + return basemesh; +} + +void moduleForwardAddBaseMeshTopology(py::module& m) +{ + getPythonClassForBaseMeshTopology(m); +} + void moduleAddBaseMeshTopology(py::module& m) { - py::class_<BaseMeshTopology, Topology, py_shared_ptr<BaseMeshTopology>> c (m, "BaseMeshTopology"); + auto c = getPythonClassForBaseMeshTopology(m); /// register the BaseMeshTopology binding in the downcasting subsystem PythonFactory::registerType<BaseMeshTopology>([](sofa::core::objectmodel::Base* object) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseMeshTopology.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseMeshTopology.h index db28451e..7f8dd115 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseMeshTopology.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseMeshTopology.h @@ -24,6 +24,9 @@ namespace sofapython3 { +/// Forward declaration in pybind11. +/// more details in: https://github.com/sofa-framework/SofaPython3/pull/457 +void moduleForwardAddBaseMeshTopology(pybind11::module &m); void moduleAddBaseMeshTopology(pybind11::module &m); } // namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseObject.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseObject.cpp index 7f9a476c..06fe77b6 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseObject.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseObject.cpp @@ -188,10 +188,21 @@ py::object __getitem__(BaseObject &self, std::string s) return getItem(self, s); } -void moduleAddBaseObject(py::module& m) +auto getBaseObjectBinding(py::module& m) { /// Register the BaseObject binding into the pybind11 typing system - py::class_<BaseObject, Base, py_shared_ptr<BaseObject>>p(m, "Object", sofapython3::doc::baseObject::Class); + static py::class_<BaseObject, Base, py_shared_ptr<BaseObject>>p(m, "Object", sofapython3::doc::baseObject::Class); + return p; +} + +void moduleForwardAddBaseObject(py::module& m) +{ + getBaseObjectBinding(m); +} + +void moduleAddBaseObject(py::module& m) +{ + auto p = getBaseObjectBinding(m); /// Register the BaseObject binding into the downcasting subsystem PythonFactory::registerType<sofa::core::objectmodel::BaseObject>( diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseObject.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseObject.h index 3e40176d..b85eed1f 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseObject.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseObject.h @@ -29,6 +29,8 @@ class BaseObject; namespace sofapython3 { pybind11::object getItem(const sofa::core::objectmodel::BaseObject & self, const std::string& path); + +void moduleForwardAddBaseObject(pybind11::module &m); void moduleAddBaseObject(pybind11::module &m); } /// namespace sofapython diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mass.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mass.cpp index 75af4211..268b7a0d 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mass.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mass.cpp @@ -94,4 +94,10 @@ void moduleAddMass(py::module &m) { declare_mass<sofa::defaulttype::Rigid2dTypes>(m); } +void moduleForwardAddBaseMass(py::module& m) +{ + static py::class_<sofa::core::behavior::BaseMass, + sofa::core::Base, py_shared_ptr<sofa::core::behavior::BaseMass>> basemass(m, "BaseMass"); +} + } // namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mass.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mass.h index 826b78da..7316b6ab 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mass.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mass.h @@ -24,6 +24,9 @@ namespace sofapython3 { +/// Forward declaration in pybind11. +/// more details in: https://github.com/sofa-framework/SofaPython3/pull/457 +void moduleForwardAddBaseMass(pybind11::module &m); void moduleAddMass(pybind11::module &m); } /// namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp index dda992d1..f20275db 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp @@ -61,6 +61,7 @@ using sofapython3::PythonEnvironment; #include <SofaPython3/Sofa/Core/Binding_PythonScriptEvent.h> #include <SofaPython3/SpellingSuggestionHelper.h> +#include <SofaPython3/PyBindHelper.h> using sofa::core::objectmodel::BaseObjectDescription; @@ -294,6 +295,12 @@ py::object addObjectKwargs(Node* self, const std::string& type, const py::kwargs return PythonFactory::toPython(object.get()); } +py::object addObjectGenericType(Node* self, const py::type type, const py::kwargs& kwargs) +{ + auto name = py::cast<std::string>(type.attr("__name__")); + return addObjectKwargs(self, name, kwargs); +} + /// Implement the addObject function. py::object addKwargs(Node* self, const py::object& callable, const py::kwargs& kwargs) { @@ -605,61 +612,69 @@ void sendEvent(Node* self, py::object pyUserData, char* eventName) sofapython3::PythonScriptEvent event(self, eventName, pyUserData); self->propagateEvent(sofa::core::execparams::defaultInstance(), &event); } - } void moduleAddNode(py::module &m) { - /// Register the complete parent-child relationship between Base and Node to the pybind11 - /// typing system. - py::class_<sofa::core::objectmodel::BaseNode, - sofa::core::objectmodel::Base, - py_shared_ptr<sofa::core::objectmodel::BaseNode>>(m, "BaseNode"); - - py::class_<Node, sofa::core::objectmodel::BaseNode, - sofa::core::objectmodel::Context, py_shared_ptr<Node>> - p(m, "Node", sofapython3::doc::sofa::core::Node::Class); - - PythonFactory::registerType<sofa::simulation::graph::DAGNode>( - [](sofa::core::objectmodel::Base* object) { - return py::cast(dynamic_cast<Node*>(object->toBaseNode())); - }); - - p.def(py::init(&__init__noname), sofapython3::doc::sofa::core::Node::init); - p.def(py::init(&__init__), sofapython3::doc::sofa::core::Node::init1Arg, py::arg("name")); - p.def("init", &init, sofapython3::doc::sofa::core::Node::initSofa ); - p.def("add", &addKwargs, sofapython3::doc::sofa::core::Node::addKwargs); - p.def("addObject", &addObjectKwargs, sofapython3::doc::sofa::core::Node::addObjectKwargs); - p.def("addObject", &addObject, sofapython3::doc::sofa::core::Node::addObject, py::keep_alive<0, 2>()); - p.def("createObject", &createObject, sofapython3::doc::sofa::core::Node::createObject, py::keep_alive<0, 2>()); - p.def("hasObject", &hasObject, sofapython3::doc::sofa::core::Node::hasObject); - p.def("getObject", &getObject, sofapython3::doc::sofa::core::Node::getObject); - p.def("addChild", &addChildKwargs, sofapython3::doc::sofa::core::Node::addChildKwargs); - p.def("addChild", &addChild, sofapython3::doc::sofa::core::Node::addChild, py::keep_alive<0, 2>()); - p.def("createChild", &createChild, sofapython3::doc::sofa::core::Node::createChild, py::keep_alive<0, 2>()); - p.def("getChild", &getChild, sofapython3::doc::sofa::core::Node::getChild); - p.def("removeChild", &removeChild, sofapython3::doc::sofa::core::Node::removeChild); - p.def("removeChild", &removeChildByName, sofapython3::doc::sofa::core::Node::removeChildWithName); - p.def("getRoot", &getRoot, sofapython3::doc::sofa::core::Node::getRoot); - p.def("getPathName", &Node::getPathName, sofapython3::doc::sofa::core::Node::getPathName); - p.def("getLinkPath", &getLinkPath, sofapython3::doc::sofa::core::Node::getLinkPath); - p.def_property_readonly("children", &property_children, sofapython3::doc::sofa::core::Node::children); - p.def_property_readonly("parents", &property_parents, sofapython3::doc::sofa::core::Node::parents); - p.def_property_readonly("objects", &property_objects, sofapython3::doc::sofa::core::Node::objects); - p.def("__getattr__", &__getattr__); - p.def("__getitem__", &__getitem__); - p.def("removeObject", &removeObject, sofapython3::doc::sofa::core::Node::removeObject); - p.def("getRootPath", &Node::getRootPath, sofapython3::doc::sofa::core::Node::getRootPath); - p.def("moveChild", &moveChild, sofapython3::doc::sofa::core::Node::moveChild); - p.def("isInitialized", &Node::isInitialized, sofapython3::doc::sofa::core::Node::isInitialized); - p.def("getAsACreateObjectParameter", &getLinkPath, sofapython3::doc::sofa::core::Node::getAsACreateObjectParameter); - p.def("detachFromGraph", &Node::detachFromGraph, sofapython3::doc::sofa::core::Node::detachFromGraph); - p.def("getMass", &getMass, sofapython3::doc::sofa::core::Node::getMass); - p.def("hasODESolver", &hasODESolver, sofapython3::doc::sofa::core::Node::hasODESolver); - p.def("getForceField", &getForceField, sofapython3::doc::sofa::core::Node::getForceField); - p.def("getMechanicalState", &getMechanicalState, sofapython3::doc::sofa::core::Node::getMechanicalState); - p.def("getMechanicalMapping", &getMechanicalMapping, sofapython3::doc::sofa::core::Node::getMechanicalMapping); - p.def("sendEvent", &sendEvent, sofapython3::doc::sofa::core::Node::sendEvent); + /// Register the complete parent-child relationship between Base and Node to the pybind11 + /// typing system. + py::class_<sofa::core::objectmodel::BaseNode, + sofa::core::objectmodel::Base, + py_shared_ptr<sofa::core::objectmodel::BaseNode>>(m, "BaseNode"); + + py::class_<Node, sofa::core::objectmodel::BaseNode, + sofa::core::objectmodel::Context, py_shared_ptr<Node>> + p(m, "Node", sofapython3::doc::sofa::core::Node::Class); + + PythonFactory::registerType<sofa::simulation::graph::DAGNode>( + [](sofa::core::objectmodel::Base* object) + { + return py::cast(dynamic_cast<Node*>(object->toBaseNode())); + }); + + p.def(py::init(&__init__noname), sofapython3::doc::sofa::core::Node::init); + p.def(py::init(&__init__), sofapython3::doc::sofa::core::Node::init1Arg, py::arg("name")); + p.def("init", &init, sofapython3::doc::sofa::core::Node::initSofa ); + p.def("add", &addKwargs, sofapython3::doc::sofa::core::Node::addKwargs); + + def_method(p, "addObject") + .add_override("addObject(self, typename: str, **kwargs) -> Sofa.Core.Object", + addObjectKwargs, sofapython3::doc::sofa::core::Node::addObjectKwargs) + .add_override(addObject, + sofapython3::doc::sofa::core::Node::addObject, py::keep_alive<0, 2>()) + .add_override("addObject[T](self, python_type: type, **kwargs) -> T", + addObjectGenericType, sofapython3::doc::sofa::core::Node::addObjectGenerictype); + + p.def("createObject", &createObject, sofapython3::doc::sofa::core::Node::createObject, py::keep_alive<0, 2>()); + p.def("hasObject", &hasObject, sofapython3::doc::sofa::core::Node::hasObject); + p.def("getObject", &getObject, sofapython3::doc::sofa::core::Node::getObject); + p.def("addChild", &addChildKwargs, sofapython3::doc::sofa::core::Node::addChildKwargs); + p.def("addChild", &addChild, sofapython3::doc::sofa::core::Node::addChild, py::keep_alive<0, 2>()); + p.def("createChild", &createChild, sofapython3::doc::sofa::core::Node::createChild, py::keep_alive<0, 2>()); + p.def("getChild", &getChild, sofapython3::doc::sofa::core::Node::getChild); + p.def("removeChild", &removeChild, sofapython3::doc::sofa::core::Node::removeChild); + p.def("removeChild", &removeChildByName, sofapython3::doc::sofa::core::Node::removeChildWithName); + p.def("getRoot", &getRoot, sofapython3::doc::sofa::core::Node::getRoot); + p.def("getPathName", &Node::getPathName, sofapython3::doc::sofa::core::Node::getPathName); + p.def("getLinkPath", &getLinkPath, sofapython3::doc::sofa::core::Node::getLinkPath); + p.def_property_readonly("children", &property_children, sofapython3::doc::sofa::core::Node::children); + p.def_property_readonly("parents", &property_parents, sofapython3::doc::sofa::core::Node::parents); + p.def_property_readonly("objects", &property_objects, sofapython3::doc::sofa::core::Node::objects); + p.def("__getattr__", &__getattr__); + p.def("__getitem__", &__getitem__); + p.def("removeObject", &removeObject, sofapython3::doc::sofa::core::Node::removeObject); + p.def("getRootPath", &Node::getRootPath, sofapython3::doc::sofa::core::Node::getRootPath); + p.def("moveChild", &moveChild, sofapython3::doc::sofa::core::Node::moveChild); + p.def("isInitialized", &Node::isInitialized, sofapython3::doc::sofa::core::Node::isInitialized); + p.def("getAsACreateObjectParameter", &getLinkPath, sofapython3::doc::sofa::core::Node::getAsACreateObjectParameter); + p.def("detachFromGraph", &Node::detachFromGraph, sofapython3::doc::sofa::core::Node::detachFromGraph); + p.def("getMass", &getMass, sofapython3::doc::sofa::core::Node::getMass); + p.def("hasODESolver", &hasODESolver, sofapython3::doc::sofa::core::Node::hasODESolver); + p.def("getForceField", &getForceField, sofapython3::doc::sofa::core::Node::getForceField); + p.def("getMechanicalState", &getMechanicalState, sofapython3::doc::sofa::core::Node::getMechanicalState); + p.def("getMechanicalMapping", &getMechanicalMapping, sofapython3::doc::sofa::core::Node::getMechanicalMapping); + p.def("sendEvent", &sendEvent, sofapython3::doc::sofa::core::Node::sendEvent); + } } } /// namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.h index 9224ab1d..608eadbe 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.h @@ -23,6 +23,7 @@ namespace sofapython3 { +void moduleForwardAddNode(pybind11::module& m); void moduleAddNode(pybind11::module &m); } /// namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node_doc.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node_doc.h index 3f971926..1aa62f97 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node_doc.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node_doc.h @@ -84,8 +84,8 @@ static auto Class = # fast access. n["child1.child2.dofs.position"] - )"; + static auto init = R"( Initialize the components of this node and all the nodes which depend on it. @@ -123,27 +123,30 @@ static auto addKwargs = node.add(Cube, name="MyCube"") )"; - static auto addObjectKwargs = R"( - Add an object. + Creates and add a new sofa object to the node. Detect the implemented interfaces and add the object to the corresponding lists. :param self: the node itself - :param type: type of the object - :param kwargs - :type self: Sofa.Simulation.Node* - :type type: string& - :type kwargs: kwargs& + :param typename: name of the type of the object to create + :param kwargs: extra parameteres to initialize the created object. + )"; + +static auto addObjectGenerictype = + R"( + Creates and add a new sofa object to the node. + :param self: the node itself + :param type: the component's type to add, type's name is used as sofa factory's type name + :return: the created component )"; static auto addObject = R"( - Add an object. + Adds an existing object to the node. Detect the implemented interfaces and add the object to the corresponding lists. :param self: the node itself - :param object: the object to be added - :type self: Sofa.Simulation.Node& - :type object: Sofa.Simulation.BaseObject* + :param object: the component to add + :return: the added component )"; static auto createObject = diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Topology.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Topology.cpp index ed57ba11..63c182c5 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Topology.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Topology.cpp @@ -35,8 +35,17 @@ using namespace sofa::core::topology; namespace sofapython3 { +auto getTopologyClass(py::module& m){ + static py::class_<Topology, BaseObject, py_shared_ptr<Topology>> c (m, "Topology"); + return c; +} + +void moduleForwardAddTopology(py::module& m) { + getTopologyClass(m); +} + void moduleAddTopology(py::module& m) { - py::class_<Topology, BaseObject, py_shared_ptr<Topology>> c (m, "Topology"); + getTopologyClass(m); } } // namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Topology.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Topology.h index 5f89f0e1..4bd7d4d1 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Topology.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Topology.h @@ -24,6 +24,9 @@ namespace sofapython3 { +/// Forward declaration in pybind11. +/// more details in: https://github.com/sofa-framework/SofaPython3/pull/457 +void moduleForwardAddTopology(pybind11::module &m); void moduleAddTopology(pybind11::module &m); } // namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt index 6dad5fea..d7ff4fd7 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt @@ -3,6 +3,8 @@ project(Bindings.Sofa.Core) set(HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Base.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Base_doc.h + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_BaseClass.h + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_BaseClass_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_DataDict.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_DataDict_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_BaseData.h @@ -51,6 +53,7 @@ set(HEADER_FILES set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_BaseClass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_BaseData.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_DataDict.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_BaseObject.cpp @@ -72,12 +75,12 @@ set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Binding_NodeIterator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_PointSetTopologyModifier.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Prefab.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/Submodule_Core.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_PythonScriptEvent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_BaseLink.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Topology.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_BaseMeshTopology.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_TaskScheduler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Submodule_Core.cpp ) if (NOT TARGET SofaPython3::Plugin) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp index 546bb435..462d220c 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp @@ -23,6 +23,7 @@ using sofa::helper::logging::Message; #include <SofaPython3/Sofa/Core/Binding_Base.h> +#include <SofaPython3/Sofa/Core/Binding_BaseClass.h> #include <SofaPython3/Sofa/Core/Binding_BaseContext.h> #include <SofaPython3/Sofa/Core/Binding_BaseObject.h> #include <SofaPython3/Sofa/Core/Binding_DataDict.h> @@ -44,6 +45,7 @@ using sofa::helper::logging::Message; #include <SofaPython3/Sofa/Core/Binding_PythonScriptEvent.h> #include <SofaPython3/Sofa/Core/Binding_Topology.h> #include <SofaPython3/Sofa/Core/Binding_BaseMeshTopology.h> +#include <SofaPython3/Sofa/Core/Binding_Topology.h> #include <SofaPython3/Sofa/Core/Binding_TaskScheduler.h> #include <SofaPython3/Sofa/Core/Data/Binding_DataString.h> @@ -103,6 +105,27 @@ PYBIND11_MODULE(Core, core) #Sofa.Core.WriteAccessor )doc"; + + + /// Forward declaration of a class in pybind11. + /// The general idea is that to avoid typeing errors in pybind11 because of -yet- to + /// define classes it is needed to register binded class before any use (including use + /// in function signature inferance) + /// more details in: https://github.com/sofa-framework/SofaPython3/pull/457 + moduleForwardAddBaseClass(core); + moduleForwardAddBase(core); + moduleForwardAddBaseObject(core); + moduleForwardAddBaseData(core); + moduleForwardAddBaseLink(core); + moduleForwardAddTopology(core); + moduleForwardAddBaseMeshTopology(core); + moduleForwardAddBaseMass(core); + + py::class_<sofa::core::behavior::BaseMechanicalState, + Base, py_shared_ptr<sofa::core::behavior::BaseMechanicalState>> basems(core, "BaseMechanicalState"); + + /// When all forward declarations in pybind11 are done we can actually fully + /// define the full binding. moduleAddPythonScriptEvent(); moduleAddDataDict(core); moduleAddDataDictIterator(core); @@ -143,7 +166,6 @@ PYBIND11_MODULE(Core, core) msg_info("SofaPython3.Core") << "Sofa.Core unload()"; })); - } } ///namespace sofapython3