From 7ecdbea760611253bdf3115a5375af2164ae9836 Mon Sep 17 00:00:00 2001 From: Damien Marchal Date: Mon, 13 Jun 2022 23:56:45 +0200 Subject: [PATCH 1/6] [Sofa.Core] Adds tests for linkpath in the test file Base.py and BaseData.py --- bindings/Sofa/tests/Core/Base.py | 16 +++++++++++----- bindings/Sofa/tests/Core/BaseData.py | 27 +++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/bindings/Sofa/tests/Core/Base.py b/bindings/Sofa/tests/Core/Base.py index 01a8daf9..307d33db 100644 --- a/bindings/Sofa/tests/Core/Base.py +++ b/bindings/Sofa/tests/Core/Base.py @@ -99,7 +99,6 @@ def test_addNewDataFromParent(self): # child properly updated from parent value self.assertEqual("test", c1.d2.value) - def test_addNewDataFromParent_brokenLink(self): root = create_scene('root') c1 = root.addObject("MechanicalObject", name="c1") @@ -121,13 +120,21 @@ def test_getClassName(self): def test_getTemplateName(self): root = create_scene("root") c = root.addObject("MechanicalObject", name="t") - self.assertEqual(c.getTemplateName(),"Vec3d") + self.assertEqual(c.getTemplateName(), "Vec3d") def test_getLinkPath(self): root = create_scene("root") obj = root.addObject("MechanicalObject", name="obj") - self.assertEqual(obj.getPathName(),"/obj") - self.assertEqual(obj.getLinkPath(),"@/obj") + self.assertEqual(obj.getPathName(), "/obj") + self.assertEqual(obj.getLinkPath(), "@/obj") + + def test_linkpath(self): + """Accessing the linkpath property should return a LinkPath object with a correctly set 'path'""" + root = create_scene("root") + root.addChild("child") + root.child.addObject("MechanicalObject", name="dofs") + self.assertEqual(type(root.child.dofs.linkpath), Sofa.Core.LinkPath) + self.assertEqual(str(root.child.dofs.linkpath), "@/child/dofs") def test_addExistingDataAsParentOfNewData(self): # TODO(@marques-bruno) @@ -148,4 +155,3 @@ def test_addExistingDataAsParentOfNewData(self): # self.assertEqual(aData.getOwner(), obj1) # self.assertEqual(obj2.aData.value, "pouet") pass - diff --git a/bindings/Sofa/tests/Core/BaseData.py b/bindings/Sofa/tests/Core/BaseData.py index f429360d..311d03bd 100644 --- a/bindings/Sofa/tests/Core/BaseData.py +++ b/bindings/Sofa/tests/Core/BaseData.py @@ -293,16 +293,35 @@ def t(c): numpy.testing.assert_array_equal(wa, v*4.0) def test_linkpath(self): + """Accessing the linkpath property should return a LinkPath object with a correct 'path'""" n = create_scene("rootNode") m = n. addObject("MechanicalObject", name="dofs") - self.assertEqual(m.position.linkpath, "@/dofs.position") + self.assertEqual(type(m.position.linkpath), Sofa.Core.LinkPath) + self.assertEqual(str(m.position.linkpath), "@/dofs.position") + + def test_linkpath_in_add_object_compat(self): + """Checks that passing a link path to the addObject function is still working as it was before sofa 21.12""" + root = create_scene("root") + root.addObject("MechanicalObject", name="dofs1", position=[[1.0,2.0,3.0]]) + root.addObject("MechanicalObject", name="dofs2", position=root.dofs1.position.linkpath) + self.assertEqual(str(root.dofs2.position.getParent().linkpath), "@/dofs1.position") + + def test_linkpath_between_two_scenes(self): + """Checks that passing a link path to the addObject function is still working as it was before sofa 21.12""" + root1 = create_scene("root1") + root1.addChild("child1") + root2 = create_scene("root2") + root2.addChild("child2) + root1.child1.addObject("MechanicalObject", name="dofs1", position=[[1.0,2.0,3.0]]) + root2.chdil2.addObject("MechanicalObject", name="dofs2", position=root1.child1.dofs1.position.linkpath) + self.assertEqual(root2.dofs2.position.getParent().name, "dofs1") def test_set_value_from_string(self): n = create_scene("rootNode") - n.gravity.value = [1.0,2.0,3.0] - self.assertEqual(list(n.gravity.value), [1.0,2.0,3.0]) + n.gravity.value = [1.0, 2.0, 3.0] + self.assertEqual(list(n.gravity.value), [1.0, 2.0, 3.0]) n.gravity.value = "4.0 5.0 6.0" - self.assertEqual(list(n.gravity.value), [4.0,5.0,6.0]) + self.assertEqual(list(n.gravity.value), [4.0, 5.0, 6.0]) def test_DataString(self): n = create_scene("rootNode") From 248429606fc925aec9c620eb7a8b2891c272d039 Mon Sep 17 00:00:00 2001 From: Damien Marchal Date: Mon, 23 May 2022 19:28:09 +0200 Subject: [PATCH 2/6] [SofaPython3] Add a LinkPath object in both the binding and plugin. The existing syntax to make links in Sofa is the following one: ```python node.addObject("MeshObjLoader", name="loader") node.addObject("MechanicalObject", position=node.loader.position.getLinkPath()) ``` In addition to be very verbose the getLinkPath() is returning a string (eg: "@/node/loader.position") Representing linkpath with string has drawback as it force the two objects to be part of the same simulation graph which is not always the case. Example of code that does not work as expected: ```python def MyPrefab(target): node = Sofa.Core.Node("MyPrefab") node.addObject("MeshObjLoader", name="loader") node2.addObject("MechanicalObject", position=target) return node def createScene(root): root.addObject("MeshObjLoader", name="loader") root.addChild( MyPrefab(loader.position.getLinkPath()) ) ``` It does not work because getLinkPath is returning a string, this string is then used to makes a path query into the scene-graph to locate the object or the data field to attach to. But in this case as the object issuing the query is not in the same graph the query fails. To solve this issue this PR introduce a LinkPath object which is pointing to a sofa object. This LinkPath object is exposed in python and can be used to make parenting between objects and data without the need of graph to path then path to graph conversion (so it is also much faster.. but we don't really care here right ?). For consistency with the "value" in data field the PR expose this link path with a dedicated "linkpath" attribute. So the code is now the following: ```python root.addObject("MeshObjLoader", name="loader") root.addChild("MechanicalObject", name="p1", position=root.loader.position.value) # Copy the value root.addChild("MechanicalObject", name="p2", position=root.loader.position.linkpath) # Make a link between data without string query root.addObject("Mapping", name="loader", input=p1.linkpath) ``` --- Plugin/CMakeLists.txt | 2 + Plugin/src/SofaPython3/DataHelper.cpp | 10 ++- Plugin/src/SofaPython3/LinkPath.cpp | 46 ++++++++++ Plugin/src/SofaPython3/LinkPath.h | 46 ++++++++++ Plugin/src/SofaPython3/PythonFactory.cpp | 7 ++ .../SofaPython3/Sofa/Core/Binding_Base.cpp | 38 ++++++--- .../src/SofaPython3/Sofa/Core/Binding_Base.h | 1 + .../Sofa/Core/Binding_BaseData.cpp | 13 ++- .../Sofa/Core/Binding_LinkPath.cpp | 84 +++++++++++++++++++ .../SofaPython3/Sofa/Core/Binding_LinkPath.h | 44 ++++++++++ .../Sofa/Core/Binding_LinkPath_doc.h | 35 ++++++++ .../SofaPython3/Sofa/Core/Binding_Node.cpp | 29 +++++++ .../src/SofaPython3/Sofa/Core/CMakeLists.txt | 3 + .../SofaPython3/Sofa/Core/Submodule_Core.cpp | 3 + 14 files changed, 346 insertions(+), 15 deletions(-) create mode 100644 Plugin/src/SofaPython3/LinkPath.cpp create mode 100644 Plugin/src/SofaPython3/LinkPath.h create mode 100644 bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_LinkPath.cpp create mode 100644 bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_LinkPath.h create mode 100644 bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_LinkPath_doc.h diff --git a/Plugin/CMakeLists.txt b/Plugin/CMakeLists.txt index 1b2023c2..f4652929 100644 --- a/Plugin/CMakeLists.txt +++ b/Plugin/CMakeLists.txt @@ -8,6 +8,7 @@ set(HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/DataCache.h ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/DataHelper.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/LinkPath.h ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/PythonFactory.h ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/Prefab.h ) @@ -19,6 +20,7 @@ set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/DataCache.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/DataHelper.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/LinkPath.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/PythonFactory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/Prefab.cpp ) diff --git a/Plugin/src/SofaPython3/DataHelper.cpp b/Plugin/src/SofaPython3/DataHelper.cpp index f3c1b476..07f4e6a5 100644 --- a/Plugin/src/SofaPython3/DataHelper.cpp +++ b/Plugin/src/SofaPython3/DataHelper.cpp @@ -25,9 +25,11 @@ #include #include +#include #include #include + using sofa::core::objectmodel::BaseLink; namespace sofapython3 @@ -62,6 +64,12 @@ std::string toSofaParsableString(const py::handle& p) return toSofaParsableString(o); } + // if the object is a link path we set it. + if(py::isinstance(p)) + { + return py::str(p); + } + return py::repr(p); } @@ -463,7 +471,7 @@ BaseData* deriveTypeFromParent(sofa::core::objectmodel::BaseContext* ctx, const bool isProtectedKeyword(const std::string& name) { if (name == "children" || name == "objects" || name == "parents" || - name == "data" || name == "links") + name == "data" || name == "links" || name == "linkpath") { return true; } diff --git a/Plugin/src/SofaPython3/LinkPath.cpp b/Plugin/src/SofaPython3/LinkPath.cpp new file mode 100644 index 00000000..bfb24472 --- /dev/null +++ b/Plugin/src/SofaPython3/LinkPath.cpp @@ -0,0 +1,46 @@ +/****************************************************************************** +* 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 . * +******************************************************************************* +* Contact information: contact@sofa-framework.org * +******************************************************************************/ + +#include + +namespace sofapython3 +{ + +LinkPath::LinkPath(sofa::core::sptr target) +{ + targetBase = target; + targetData = nullptr; +} + +LinkPath::LinkPath(sofa::core::objectmodel::BaseData* target) +{ + if(target->getOwner()) + { + targetBase = target->getOwner(); + } + targetData = target; +} + +bool LinkPath::isPointingToData() const +{ + return targetData != nullptr; +} + +}/// namespace sofapython3 diff --git a/Plugin/src/SofaPython3/LinkPath.h b/Plugin/src/SofaPython3/LinkPath.h new file mode 100644 index 00000000..7548a398 --- /dev/null +++ b/Plugin/src/SofaPython3/LinkPath.h @@ -0,0 +1,46 @@ +/****************************************************************************** +* 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 . * +******************************************************************************* +* Contact information: contact@sofa-framework.org * +******************************************************************************/ + +#pragma once + +#include +#include + +namespace sofapython3 +{ + +/// A placeholder class storing a link to a data or a base +/// +/// LinkPath can be used to indicate that a link is needed to be made in place of a string +/// like "@/usr/myobj.position" or "@/usr/myobj" +/// +class LinkPath +{ +public: + sofa::core::sptr targetBase; + sofa::core::objectmodel::BaseData* targetData; + + LinkPath(sofa::core::sptr); + LinkPath(sofa::core::objectmodel::BaseData*); + + bool isPointingToData() const; +}; + +} /// sofapython3 diff --git a/Plugin/src/SofaPython3/PythonFactory.cpp b/Plugin/src/SofaPython3/PythonFactory.cpp index 46ee4bc9..33026695 100644 --- a/Plugin/src/SofaPython3/PythonFactory.cpp +++ b/Plugin/src/SofaPython3/PythonFactory.cpp @@ -52,6 +52,7 @@ using sofa::core::objectmodel::ScriptEvent; using sofa::core::objectmodel::Event; #include +#include #include @@ -293,6 +294,12 @@ void copyFromListOf(BaseData& d, const AbstractTypeInfo& nfo, const void PythonFactory::fromPython(BaseData* d, const py::object& o) { + if(py::isinstance(o)) + { + d->setParent(py::cast(o).targetData); + return; + } + const AbstractTypeInfo& nfo{ *(d->getValueTypeInfo()) }; // Is this data field a container ? diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp index 0f88c558..bb32dadd 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp @@ -33,6 +33,7 @@ using sofa::helper::WriteOnlyAccessor; #include +#include #include #include #include @@ -90,6 +91,10 @@ py::object BindingBase::GetAttr(Base* self, const std::string& s, bool doThrowEx if( s == "__data__") return py::cast( DataDict(self) ); + /// Returns the linkpath to the self object. + if(s == "linkpath") + return py::cast(sofapython3::LinkPath(self)); + if(doThrowException) throw py::attribute_error("Missing attribute: "+s); @@ -101,18 +106,34 @@ bool BindingBase::SetData(BaseData* d, py::object value) if(d==nullptr) return false; - const AbstractTypeInfo& nfo{ *(d->getValueTypeInfo()) }; - PythonFactory::fromPython(d, value); return true; } +bool BindingBase::SetLink(BaseLink* link, py::object value) +{ + if(link==nullptr) + return false; + + if(py::isinstance(value)) + return link->read(py::cast(value)); + + if(py::isinstance(value)) + { + auto& target = py::cast(value); + if(target.isPointingToData()) + throw std::runtime_error("Passing a link to a data field instead of an object."); + link->setLinkedBase(target.targetBase.get()); + } + return true; +} + void BindingBase::SetAttr(py::object self, const std::string& s, py::object value) { Base* self_d = py::cast(self); - BaseData* d = self_d->findData(s); + BaseData* d = self_d->findData(s); if(d!=nullptr) { SetData(d, value); @@ -122,6 +143,7 @@ void BindingBase::SetAttr(py::object self, const std::string& s, py::object valu BaseLink* l = self_d->findLink(s); if(l!=nullptr) { + SetLink(l, value); return; } @@ -132,17 +154,8 @@ void BindingBase::SetAttr(py::object self, const std::string& s, py::object valu void BindingBase::SetAttr(Base& self, const std::string& s, py::object value) { BaseData* d = self.findData(s); - if(d!=nullptr) { - const AbstractTypeInfo& nfo{ *(d->getValueTypeInfo()) }; - - /// We go for the container path. - if(nfo.Container()) - { - PythonFactory::fromPython(d,value); - return; - } PythonFactory::fromPython(d, value); return; } @@ -150,6 +163,7 @@ void BindingBase::SetAttr(Base& self, const std::string& s, py::object value) BaseLink* l = self.findLink(s); if(l!=nullptr) { + SetLink(l, value); return; } diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.h index 127fc534..670ce54b 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.h @@ -48,6 +48,7 @@ class BindingBase /// Set the data field value from the array. static void SetDataFromArray(sofa::core::objectmodel::BaseData* data, const pybind11::array& value); static bool SetData(sofa::core::objectmodel::BaseData* data, pybind11::object value); + static bool SetLink(sofa::core::objectmodel::BaseLink* link, pybind11::object value); static pybind11::object setDataValues(sofa::core::objectmodel::Base& self, pybind11::kwargs kwargs); static pybind11::list getDataFields(sofa::core::objectmodel::Base& self); diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp index a6a54072..a77b1d81 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -139,7 +140,7 @@ py::object __getattr__(py::object self, const std::string& s) return PythonFactory::valueToPython_ro(py::cast(self)); if(s == "linkpath") - return py::cast((py::cast(self))->getLinkPath()); + return py::cast(sofapython3::LinkPath(py::cast(self))); /// BaseData does not support dynamic attributes, if you think this is an important feature /// please request for its integration. @@ -151,11 +152,18 @@ void setParent(BaseData* self, BaseData* parent) self->setParent(parent); } -void setParentFromLinkPath(BaseData* self, const std::string& parent) +void setParentFromLinkPathStr(BaseData* self, const std::string& parent) { self->setParent(parent); } +void setParentFromLinkPath(BaseData* self, const LinkPath& parent) +{ + if(!parent.isPointingToData()) + throw std::runtime_error("The provided linkpath is not containing a linkable data"); + self->setParent(parent.targetData); +} + bool hasParent(BaseData *self) { return (self->getParent() != nullptr); @@ -205,6 +213,7 @@ void moduleAddBaseData(py::module& m) data.def("isPersistent", &BaseData::isPersistent, sofapython3::doc::baseData::isPersistent); data.def("setPersistent", &BaseData::setPersistent, sofapython3::doc::baseData::setPersistent); data.def("setParent", setParent, sofapython3::doc::baseData::setParent); + data.def("setParent", setParentFromLinkPathStr, sofapython3::doc::baseData::setParent); data.def("setParent", setParentFromLinkPath, sofapython3::doc::baseData::setParent); data.def("hasParent", hasParent, sofapython3::doc::baseData::hasParent); data.def("read", &BaseData::read, sofapython3::doc::baseData::read); diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_LinkPath.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_LinkPath.cpp new file mode 100644 index 00000000..d08b0e57 --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_LinkPath.cpp @@ -0,0 +1,84 @@ +/****************************************************************************** +* 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 . * +******************************************************************************* +* Contact information: contact@sofa-framework.org * +******************************************************************************/ + +#include +using sofa::core::objectmodel::BaseLink; + +#include +using sofa::core::objectmodel::BaseObject; + +#include +#include + +/// Makes an alias for the pybind11 namespace to increase readability. +namespace py { using namespace pybind11; } + +// To bring in the `_a` literal +using namespace pybind11::literals; + +namespace sofapython3 +{ + +LinkPath::LinkPath(sofa::core::sptr target) +{ + targetBase = target; + targetData = nullptr; +} + +LinkPath::LinkPath(sofa::core::objectmodel::BaseData* target) +{ + // If the data is attached to + if(target->getOwner()) + { + targetBase = target->getOwner(); + } + targetData = target; +} + +bool LinkPath::isPointingToData() const +{ + return targetData != nullptr; +} + +std::string __str__(const LinkPath& entry) +{ + std::ostringstream s; + if(entry.targetData != nullptr) + return entry.targetData->getLinkPath(); + else if(entry.targetBase.get() != nullptr) + return "@" + entry.targetBase->getPathName(); + throw std::runtime_error("Empty LinkPath"); +} + +std::string __repr__(const LinkPath& entry) +{ + std::ostringstream s; + s << "LinkPath(\"" << __str__(entry) << "\")"; + return s.str(); +} + +void moduleAddLinkPath(py::module& m) +{ + py::class_ link(m, "LinkPath", sofapython3::doc::linkpath::linkpath); + link.def("__str__", &__str__); + link.def("__repr__", &__repr__); +} + +}/// namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_LinkPath.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_LinkPath.h new file mode 100644 index 00000000..76dff72a --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_LinkPath.h @@ -0,0 +1,44 @@ +/****************************************************************************** +* 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 . * +******************************************************************************* +* Contact information: contact@sofa-framework.org * +******************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace sofapython3 { + +/// A placeholder class storing a link to a data or a base +class LinkPath +{ +public: + sofa::core::sptr targetBase; + sofa::core::objectmodel::BaseData* targetData; + + LinkPath(sofa::core::sptr); + LinkPath(sofa::core::objectmodel::BaseData*); + + bool isPointingToData() const; +}; + +void moduleAddLinkPath(pybind11::module& m); + +} /// sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_LinkPath_doc.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_LinkPath_doc.h new file mode 100644 index 00000000..8f026a55 --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_LinkPath_doc.h @@ -0,0 +1,35 @@ +/****************************************************************************** +* 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 . * +******************************************************************************* +* Contact information: contact@sofa-framework.org * +******************************************************************************/ + +#pragma once + +namespace sofapython3::doc::linkpath +{ +static auto linkpath = + R"( + Hold a linkpath to an object or a data. + Example of use: + node.addObject("MechanicalObject", name="o") + node.addObject(position=node.o.position.linkpath) + + str(node.o.linkpath) # prints LinkPath("@/o.position") + repr(node.o.linkpath) # prints "@/o.position" + )"; +} diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp index 137dca3e..bb109ecf 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp @@ -38,6 +38,9 @@ using sofa::helper::logging::Message; #include using sofa::core::ExecParams; +#include +using sofapython3::LinkPath; + #include #include #include @@ -185,6 +188,26 @@ py::object getObject(Node &n, const std::string &name, const py::kwargs& kwargs) return py::none(); } +void setFieldsFromPythonValues(Base* self, const py::kwargs& dict) +{ + // For each argument of the addObject functin we check if this is a + // and argument we can do a raw conversion from. + // currently, only the LinkPath object is supported. + for(auto kv : dict) + { + if(py::isinstance(kv.second)) + { + auto* data = self->findData(py::str(kv.first)); + if(data) + BindingBase::SetData(data, py::cast(kv.second)); + + auto* link = self->findLink(py::str(kv.first)); + if(link) + BindingBase::SetLink(link, py::cast(kv.second)); + } + } +} + /// Implement the addObject function. py::object addObjectKwargs(Node* self, const std::string& type, const py::kwargs& kwargs) { @@ -218,6 +241,12 @@ py::object addObjectKwargs(Node* self, const std::string& type, const py::kwargs object->setName(resolvedName); } + auto finfo = PythonEnvironment::getPythonCallingPointAsFileInfo(); + object->setInstanciationSourceFileName(finfo->filename); + object->setInstanciationSourceFilePos(finfo->line); + + setFieldsFromPythonValues(object.get(), kwargs); + checkParamUsage(desc); /// Convert the logged messages in the object's internal logging into python exception. diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt index 6920bf83..fa936eb7 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt @@ -19,6 +19,8 @@ set(HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Binding_DataEngine_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_ForceField.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_ForceField_doc.h + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_LinkPath.h + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_LinkPath_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_ObjectFactory.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_ObjectFactory_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Node.h @@ -58,6 +60,7 @@ set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Data/Binding_DataLink.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Data/Binding_DataVectorString.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_ForceField.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_LinkPath.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_ObjectFactory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Node.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_NodeIterator.cpp diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp index 2e8d6bac..b48ac814 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 @@ -109,6 +110,7 @@ PYBIND11_MODULE(Core, core) Sofa.Core.DataContainer Sofa.Core.DataString Sofa.Core.DataVectorString + Sofa.Core.LinkPath Sofa.Core.NodeIterator #Sofa.Core.WriteAccessor )doc"; @@ -132,6 +134,7 @@ PYBIND11_MODULE(Core, core) moduleAddDataEngine(core); moduleAddForceField(core); moduleAddObjectFactory(core); + moduleAddLinkPath(core); moduleAddNode(core); moduleAddNodeIterator(core); moduleAddPrefab(core); From a99af0f4cbda183fd5facfe8f71be4f9017fa04e Mon Sep 17 00:00:00 2001 From: Damien Marchal Date: Tue, 21 Jun 2022 23:12:02 +0200 Subject: [PATCH 3/6] =?UTF-8?q?[SofaPython3]=C2=A0LinkPath=20remove=20not?= =?UTF-8?q?=20needed=20condition=20while=20setting=20nullptr=20on=20a=20sm?= =?UTF-8?q?art=20pointer.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Plugin/src/SofaPython3/LinkPath.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Plugin/src/SofaPython3/LinkPath.cpp b/Plugin/src/SofaPython3/LinkPath.cpp index bfb24472..e642577e 100644 --- a/Plugin/src/SofaPython3/LinkPath.cpp +++ b/Plugin/src/SofaPython3/LinkPath.cpp @@ -31,10 +31,7 @@ LinkPath::LinkPath(sofa::core::sptr target) LinkPath::LinkPath(sofa::core::objectmodel::BaseData* target) { - if(target->getOwner()) - { - targetBase = target->getOwner(); - } + targetBase = target->getOwner(); targetData = target; } From bd3fc4ac7c8eecba392554e4a115bbcff9ea5436 Mon Sep 17 00:00:00 2001 From: Damien Marchal Date: Tue, 21 Jun 2022 23:47:19 +0200 Subject: [PATCH 4/6] =?UTF-8?q?[FIXUP]=C2=A0Passager=20clandestin.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp index bb109ecf..1e4800b4 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp @@ -241,10 +241,6 @@ py::object addObjectKwargs(Node* self, const std::string& type, const py::kwargs object->setName(resolvedName); } - auto finfo = PythonEnvironment::getPythonCallingPointAsFileInfo(); - object->setInstanciationSourceFileName(finfo->filename); - object->setInstanciationSourceFilePos(finfo->line); - setFieldsFromPythonValues(object.get(), kwargs); checkParamUsage(desc); From 6a230fab9b5a23eb1ce3fefdc349ae601791c4cd Mon Sep 17 00:00:00 2001 From: Damien Marchal Date: Wed, 22 Jun 2022 14:19:04 +0200 Subject: [PATCH 5/6] Update bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp Co-authored-by: Alex Bilger --- .../Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp index 1e4800b4..6c2f540c 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp @@ -193,17 +193,17 @@ void setFieldsFromPythonValues(Base* self, const py::kwargs& dict) // For each argument of the addObject functin we check if this is a // and argument we can do a raw conversion from. // currently, only the LinkPath object is supported. - for(auto kv : dict) + for(auto [key, value] : dict) { - if(py::isinstance(kv.second)) + if(py::isinstance(value)) { - auto* data = self->findData(py::str(kv.first)); + auto* data = self->findData(py::str(key)); if(data) - BindingBase::SetData(data, py::cast(kv.second)); + BindingBase::SetData(data, py::cast(value)); - auto* link = self->findLink(py::str(kv.first)); + auto* link = self->findLink(py::str(key)); if(link) - BindingBase::SetLink(link, py::cast(kv.second)); + BindingBase::SetLink(link, py::cast(value)); } } } From 57b3ab2b1bfac4d57ff3a309ffe623c43ef46978 Mon Sep 17 00:00:00 2001 From: Damien Marchal Date: Wed, 22 Jun 2022 14:22:56 +0200 Subject: [PATCH 6/6] Update Binding_Node.cpp --- bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp index 6c2f540c..b68bb094 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp @@ -190,9 +190,8 @@ py::object getObject(Node &n, const std::string &name, const py::kwargs& kwargs) void setFieldsFromPythonValues(Base* self, const py::kwargs& dict) { - // For each argument of the addObject functin we check if this is a - // and argument we can do a raw conversion from. - // currently, only the LinkPath object is supported. + // For each argument of the addObject function we check if this is an argument we can do a raw conversion from. + // Doing a raw conversion means that we are not converting the argument anymore into a sofa parsable string. for(auto [key, value] : dict) { if(py::isinstance(value))