From 8536c1cc3b845c338981750140ff3a5ef5ec2606 Mon Sep 17 00:00:00 2001 From: Damien Marchal Date: Wed, 22 Sep 2021 19:38:31 +0200 Subject: [PATCH 1/8] [SofaPython3] Add type coercion from numpy array to sofa parsable string (in object creation) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So we can write: ```python def createScene(root): root.addObject("MechanicalObject", name="loader", filename="mesh/cube.obj") root.addObject("MechanicalObject", name="dofs", position=root.loader.position) # to make a link root.addObject("MechanicalObject", name="dofs", position=root.loader.position.value) # to make a copy by value ``` --- Plugin/src/SofaPython3/DataHelper.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Plugin/src/SofaPython3/DataHelper.cpp b/Plugin/src/SofaPython3/DataHelper.cpp index 3d108284..02acc98f 100644 --- a/Plugin/src/SofaPython3/DataHelper.cpp +++ b/Plugin/src/SofaPython3/DataHelper.cpp @@ -52,8 +52,16 @@ std::string toSofaParsableString(const py::handle& p) if(py::isinstance(p)) { sofa::core::objectmodel::BaseData* data = py::cast(p); - return data->getValueString(); + return data->getLinkPath(); } + + // If the object is a numpy array we convert it to a list then to a sofa string + if(py::isinstance(p)) + { + py::object o = p.attr("tolist")(); + return toSofaParsableString(o); + } + return py::repr(p); } From e7a9eaf9842e32be2279e2c5862a76ff4371c03a Mon Sep 17 00:00:00 2001 From: Damien Marchal Date: Wed, 22 Sep 2021 19:49:31 +0200 Subject: [PATCH 2/8] [SofaPython] Restore the copy by default approach in toSofaParsableString Because it breaks bckward compatibility and was not really better. --- Plugin/src/SofaPython3/DataHelper.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Plugin/src/SofaPython3/DataHelper.cpp b/Plugin/src/SofaPython3/DataHelper.cpp index 02acc98f..8dc2c855 100644 --- a/Plugin/src/SofaPython3/DataHelper.cpp +++ b/Plugin/src/SofaPython3/DataHelper.cpp @@ -48,14 +48,14 @@ std::string toSofaParsableString(const py::handle& p) if(py::isinstance(p)) return py::str(p); - // Insure compatibility with data field code returning value instead of data. + // If the object is a data field we take its value to copy them. if(py::isinstance(p)) { sofa::core::objectmodel::BaseData* data = py::cast(p); - return data->getLinkPath(); + return data->getValueString(); } - // If the object is a numpy array we convert it to a list then to a sofa string + // If the object is a numpy array we convert it to a list then to a sofa string. if(py::isinstance(p)) { py::object o = p.attr("tolist")(); From f1bd43591bc5091141465956cc5b6b46b972edf9 Mon Sep 17 00:00:00 2001 From: Damien Marchal Date: Wed, 22 Sep 2021 19:50:46 +0200 Subject: [PATCH 3/8] [Sofa.Core] Add an attribute 'link' to Binding_BaseData. So we can write: object.position.link instead of object.position.getLinkPath() Which will make the syntax a bit more compact. Signed-off-by: Damien Marchal --- bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp index a701c119..2b175d6f 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp @@ -137,6 +137,9 @@ py::object __getattr__(py::object self, const std::string& s) if(s == "value") return PythonFactory::valueToPython_ro(py::cast(self)); + if(s == "link") + return py::cast((py::cast(self))->getLinkPath()); + /// BaseData does not support dynamic attributes, if you think this is an important feature /// please request for its integration. throw py::attribute_error("There is no attribute '"+s+"'"); From 5c12fc440ddce45c3f28f26cc6ae8812f660d5e8 Mon Sep 17 00:00:00 2001 From: Damien Marchal Date: Wed, 22 Sep 2021 20:02:32 +0200 Subject: [PATCH 4/8] [Sofa.Core] Add getLinkPath() in the Base binding. --- .../src/SofaPython3/Sofa/Core/Binding_Base.cpp | 11 +++++++++-- .../src/SofaPython3/Sofa/Core/Binding_Base.h | 1 + .../src/SofaPython3/Sofa/Core/Binding_Base_doc.h | 16 ++++++++++++++++ bindings/Sofa/tests/Core/Base.py | 6 +++++- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp index 3eafdd5e..dd68681d 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp @@ -387,7 +387,13 @@ std::string BindingBase::getPathName(Base& self) { return self.toBaseNode() ? self.toBaseNode()->getPathName() : self.toBaseObject()->getPathName(); } - + +std::string BindingBase::getLinkPath(Base& self) +{ + return "@"+getPathName(self); +} + + py::object BindingBase::setDataValues(Base& self, py::kwargs kwargs) { for(auto key : kwargs) @@ -435,7 +441,8 @@ void moduleAddBase(py::module &m) base.def("getLoggedMessagesAsString", &BindingBase::getLoggedMessagesAsString, sofapython3::doc::base::getLoggedMessagesAsString); base.def("countLoggedMessages", &BindingBase::countLoggedMessages, sofapython3::doc::base::countLoggedMessages); base.def("clearLoggedMessages", &BindingBase::clearLoggedMessages, sofapython3::doc::base::clearLoggedMessages); - base.def("getPathName", &BindingBase::getPathName); + base.def("getPathName", &BindingBase::getPathName, sofapython3::doc::base::setDataValues); + base.def("getPathName", &BindingBase::getLinkPath, sofapython3::doc::base::setDataValues); base.def("setDataValues", &BindingBase::setDataValues, sofapython3::doc::base::setDataValues); } diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.h index 7f5a6c15..127fc534 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.h @@ -64,6 +64,7 @@ class BindingBase static pybind11::object countLoggedMessages(sofa::core::objectmodel::Base& self); static pybind11::object clearLoggedMessages(sofa::core::objectmodel::Base& self); static std::string getPathName(sofa::core::objectmodel::Base& self); + static std::string getLinkPath(sofa::core::objectmodel::Base& self); }; diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base_doc.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base_doc.h index 2086ac27..76a37638 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base_doc.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base_doc.h @@ -63,6 +63,22 @@ static auto findData = :type name: string :return: the data field )"; +static auto getLinkPath = + R"( + returns the path name as a parsable string. + eg: + if object.getPathName() is "/child1/object" + then the linkPath is @"/child1/object" + )"; +static auto getPathName = + R"( + returns the path name as a string. + .. code-block:: python + a = Sofa.Core.Node("root") + b.addObject("Camera", name="camera") + b.getPathName() # should returns "/root/camera" + ``` + )"; static auto setDataValues = R"( Set values for a the given data field, multiple pairs of args are allowed. diff --git a/bindings/Sofa/tests/Core/Base.py b/bindings/Sofa/tests/Core/Base.py index 1b4849d0..caac133a 100644 --- a/bindings/Sofa/tests/Core/Base.py +++ b/bindings/Sofa/tests/Core/Base.py @@ -123,7 +123,11 @@ def test_getTemplateName(self): c = root.addObject("MechanicalObject", name="t") 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") def test_addExistingDataAsParentOfNewData(self): # TODO(@marques-bruno) From e2d1234ec9a4763a4ad5d8d48c617ef1b87cd134 Mon Sep 17 00:00:00 2001 From: Damien Marchal Date: Thu, 23 Sep 2021 13:49:24 +0200 Subject: [PATCH 5/8] SofaPython3] Do link by default. Signed-off-by: Damien Marchal --- Plugin/src/SofaPython3/DataHelper.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugin/src/SofaPython3/DataHelper.cpp b/Plugin/src/SofaPython3/DataHelper.cpp index 8dc2c855..36cf7f52 100644 --- a/Plugin/src/SofaPython3/DataHelper.cpp +++ b/Plugin/src/SofaPython3/DataHelper.cpp @@ -48,11 +48,11 @@ std::string toSofaParsableString(const py::handle& p) if(py::isinstance(p)) return py::str(p); - // If the object is a data field we take its value to copy them. + // If the object is a data field we link the data field if(py::isinstance(p)) { sofa::core::objectmodel::BaseData* data = py::cast(p); - return data->getValueString(); + return data->getLinkPath(); } // If the object is a numpy array we convert it to a list then to a sofa string. From 292cceca726c5cee9e4bbc574918c57cb1528606 Mon Sep 17 00:00:00 2001 From: Damien Marchal Date: Thu, 23 Sep 2021 14:04:26 +0200 Subject: [PATCH 6/8] [SofaPython3] Add linkpath property in Binding_BaseData --- bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp | 4 ++-- bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp index dd68681d..ef7f14d3 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp @@ -441,8 +441,8 @@ void moduleAddBase(py::module &m) base.def("getLoggedMessagesAsString", &BindingBase::getLoggedMessagesAsString, sofapython3::doc::base::getLoggedMessagesAsString); base.def("countLoggedMessages", &BindingBase::countLoggedMessages, sofapython3::doc::base::countLoggedMessages); base.def("clearLoggedMessages", &BindingBase::clearLoggedMessages, sofapython3::doc::base::clearLoggedMessages); - base.def("getPathName", &BindingBase::getPathName, sofapython3::doc::base::setDataValues); - base.def("getPathName", &BindingBase::getLinkPath, sofapython3::doc::base::setDataValues); + base.def("getPathName", &BindingBase::getPathName, sofapython3::doc::base::getPathName); + base.def("getLinkPath", &BindingBase::getLinkPath, sofapython3::doc::base::getLinkPath); base.def("setDataValues", &BindingBase::setDataValues, sofapython3::doc::base::setDataValues); } diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp index 2b175d6f..f7804a8b 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp @@ -112,6 +112,7 @@ py::object writeableArray(BaseData* self) void __setattr__(py::object self, const std::string& s, py::object value) { + SOFA_UNUSED(s); BaseData* selfdata = py::cast(self); if(py::isinstance(value)) @@ -137,7 +138,7 @@ py::object __getattr__(py::object self, const std::string& s) if(s == "value") return PythonFactory::valueToPython_ro(py::cast(self)); - if(s == "link") + if(s == "linkpath") return py::cast((py::cast(self))->getLinkPath()); /// BaseData does not support dynamic attributes, if you think this is an important feature From 9c1d0c91b1bb98272de28d3ff831203af40991a8 Mon Sep 17 00:00:00 2001 From: Damien Marchal Date: Thu, 23 Sep 2021 14:15:00 +0200 Subject: [PATCH 7/8] [Sofa.Core.tests] Add a test for the BaseData.linkpath property --- bindings/Sofa/tests/Core/BaseData.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bindings/Sofa/tests/Core/BaseData.py b/bindings/Sofa/tests/Core/BaseData.py index 34d99d9a..f429360d 100644 --- a/bindings/Sofa/tests/Core/BaseData.py +++ b/bindings/Sofa/tests/Core/BaseData.py @@ -292,6 +292,11 @@ def t(c): self.assertEqual(wa[2, 2], 8.0) numpy.testing.assert_array_equal(wa, v*4.0) + def test_linkpath(self): + n = create_scene("rootNode") + m = n. addObject("MechanicalObject", name="dofs") + self.assertEqual(m.position.linkpath, "@/dofs.position") + def test_set_value_from_string(self): n = create_scene("rootNode") n.gravity.value = [1.0,2.0,3.0] From 44dbe9c83d6cdea6d184b4adc4e16bc44d3adece Mon Sep 17 00:00:00 2001 From: Damien Marchal Date: Thu, 28 Oct 2021 23:19:20 +0200 Subject: [PATCH 8/8] [Sofapython3] Restore "copy" by default behavior while setting data from data. --- Plugin/src/SofaPython3/DataHelper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugin/src/SofaPython3/DataHelper.cpp b/Plugin/src/SofaPython3/DataHelper.cpp index 36cf7f52..80aaa449 100644 --- a/Plugin/src/SofaPython3/DataHelper.cpp +++ b/Plugin/src/SofaPython3/DataHelper.cpp @@ -52,7 +52,7 @@ std::string toSofaParsableString(const py::handle& p) if(py::isinstance(p)) { sofa::core::objectmodel::BaseData* data = py::cast(p); - return data->getLinkPath(); + return data->getValueString(); } // If the object is a numpy array we convert it to a list then to a sofa string.