Skip to content

Commit 091f07f

Browse files
committed
[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) ```
1 parent 7ecdbea commit 091f07f

14 files changed

+342
-15
lines changed

Plugin/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ set(HEADER_FILES
88

99
${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/DataCache.h
1010
${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/DataHelper.h
11+
${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/LinkPath.h
1112
${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/PythonFactory.h
1213
${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/Prefab.h
1314
)
@@ -19,6 +20,7 @@ set(SOURCE_FILES
1920

2021
${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/DataCache.cpp
2122
${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/DataHelper.cpp
23+
${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/LinkPath.cpp
2224
${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/PythonFactory.cpp
2325
${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/Prefab.cpp
2426
)

Plugin/src/SofaPython3/DataHelper.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
#include <sofa/simulation/Node.h>
2626

2727
#include <SofaPython3/DataHelper.h>
28+
#include <SofaPython3/LinkPath.h>
2829
#include <SofaPython3/DataCache.h>
2930
#include <SofaPython3/PythonFactory.h>
3031

32+
3133
using sofa::core::objectmodel::BaseLink;
3234

3335
namespace sofapython3
@@ -62,6 +64,12 @@ std::string toSofaParsableString(const py::handle& p)
6264
return toSofaParsableString(o);
6365
}
6466

67+
// if the object is a link path we set it.
68+
if(py::isinstance<sofapython3::LinkPath>(p))
69+
{
70+
return py::str(p);
71+
}
72+
6573
return py::repr(p);
6674
}
6775

@@ -463,7 +471,7 @@ BaseData* deriveTypeFromParent(sofa::core::objectmodel::BaseContext* ctx, const
463471
bool isProtectedKeyword(const std::string& name)
464472
{
465473
if (name == "children" || name == "objects" || name == "parents" ||
466-
name == "data" || name == "links")
474+
name == "data" || name == "links" || name == "linkpath")
467475
{
468476
return true;
469477
}

Plugin/src/SofaPython3/LinkPath.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/******************************************************************************
2+
* SOFA, Simulation Open-Framework Architecture *
3+
* (c) 2021 INRIA, USTL, UJF, CNRS, MGH *
4+
* *
5+
* This program is free software; you can redistribute it and/or modify it *
6+
* under the terms of the GNU Lesser General Public License as published by *
7+
* the Free Software Foundation; either version 2.1 of the License, or (at *
8+
* your option) any later version. *
9+
* *
10+
* This program is distributed in the hope that it will be useful, but WITHOUT *
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
13+
* for more details. *
14+
* *
15+
* You should have received a copy of the GNU Lesser General Public License *
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
17+
*******************************************************************************
18+
* Contact information: [email protected] *
19+
******************************************************************************/
20+
21+
#include <SofaPython3/LinkPath.h>
22+
23+
namespace sofapython3
24+
{
25+
26+
LinkPath::LinkPath(sofa::core::sptr<sofa::core::objectmodel::Base> target)
27+
{
28+
targetBase = target;
29+
targetData = nullptr;
30+
}
31+
32+
LinkPath::LinkPath(sofa::core::objectmodel::BaseData* target)
33+
{
34+
if(target->getOwner())
35+
{
36+
targetBase = target->getOwner();
37+
}
38+
targetData = target;
39+
}
40+
41+
bool LinkPath::isPointingToData() const
42+
{
43+
return targetData != nullptr;
44+
}
45+
46+
}/// namespace sofapython3

Plugin/src/SofaPython3/LinkPath.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/******************************************************************************
2+
* SOFA, Simulation Open-Framework Architecture *
3+
* (c) 2021 INRIA, USTL, UJF, CNRS, MGH *
4+
* *
5+
* This program is free software; you can redistribute it and/or modify it *
6+
* under the terms of the GNU Lesser General Public License as published by *
7+
* the Free Software Foundation; either version 2.1 of the License, or (at *
8+
* your option) any later version. *
9+
* *
10+
* This program is distributed in the hope that it will be useful, but WITHOUT *
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
13+
* for more details. *
14+
* *
15+
* You should have received a copy of the GNU Lesser General Public License *
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
17+
*******************************************************************************
18+
* Contact information: [email protected] *
19+
******************************************************************************/
20+
21+
#pragma once
22+
23+
#include <sofa/core/objectmodel/Base.h>
24+
#include <sofa/core/objectmodel/BaseData.h>
25+
26+
namespace sofapython3
27+
{
28+
29+
/// A placeholder class storing a link to a data or a base
30+
///
31+
/// LinkPath can be used to indicate that a link is needed to be made in place of a string
32+
/// like "@/usr/myobj.position" or "@/usr/myobj"
33+
///
34+
class LinkPath
35+
{
36+
public:
37+
sofa::core::sptr<sofa::core::objectmodel::Base> targetBase;
38+
sofa::core::objectmodel::BaseData* targetData;
39+
40+
LinkPath(sofa::core::sptr<sofa::core::objectmodel::Base>);
41+
LinkPath(sofa::core::objectmodel::BaseData*);
42+
43+
bool isPointingToData() const;
44+
};
45+
46+
} /// sofapython3

Plugin/src/SofaPython3/PythonFactory.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ using sofa::core::objectmodel::ScriptEvent;
5252
using sofa::core::objectmodel::Event;
5353

5454
#include <SofaPython3/PythonEnvironment.h>
55+
#include <SofaPython3/LinkPath.h>
5556

5657
#include <sofa/core/topology/Topology.h>
5758

@@ -293,6 +294,12 @@ void copyFromListOf<std::string>(BaseData& d, const AbstractTypeInfo& nfo, const
293294

294295
void PythonFactory::fromPython(BaseData* d, const py::object& o)
295296
{
297+
if(py::isinstance<sofapython3::LinkPath>(o))
298+
{
299+
d->setParent(py::cast<LinkPath&>(o).targetData);
300+
return;
301+
}
302+
296303
const AbstractTypeInfo& nfo{ *(d->getValueTypeInfo()) };
297304

298305
// Is this data field a container ?

bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ using sofa::helper::WriteOnlyAccessor;
3333

3434
#include <SofaPython3/PythonFactory.h>
3535

36+
#include <SofaPython3/Sofa/Core/Binding_LinkPath.h>
3637
#include <SofaPython3/Sofa/Core/Binding_Base.h>
3738
#include <SofaPython3/Sofa/Core/Binding_Base_doc.h>
3839
#include <SofaPython3/Sofa/Core/Binding_DataDict.h>
@@ -90,6 +91,10 @@ py::object BindingBase::GetAttr(Base* self, const std::string& s, bool doThrowEx
9091
if( s == "__data__")
9192
return py::cast( DataDict(self) );
9293

94+
/// Returns the linkpath to the self object.
95+
if(s == "linkpath")
96+
return py::cast(sofapython3::LinkPath(self));
97+
9398
if(doThrowException)
9499
throw py::attribute_error("Missing attribute: "+s);
95100

@@ -101,18 +106,34 @@ bool BindingBase::SetData(BaseData* d, py::object value)
101106
if(d==nullptr)
102107
return false;
103108

104-
const AbstractTypeInfo& nfo{ *(d->getValueTypeInfo()) };
105-
106109
PythonFactory::fromPython(d, value);
107110
return true;
108111
}
109112

113+
bool BindingBase::SetLink(BaseLink* link, py::object value)
114+
{
115+
if(link==nullptr)
116+
return false;
117+
118+
if(py::isinstance<py::str>(value))
119+
return link->read(py::cast<py::str>(value));
120+
121+
if(py::isinstance<LinkPath>(value))
122+
{
123+
auto& target = py::cast<LinkPath&>(value);
124+
if(target.isPointingToData())
125+
throw std::runtime_error("Passing a link to a data field instead of an object.");
126+
link->setLinkedBase(target.targetBase.get());
127+
}
128+
return true;
129+
}
130+
110131

111132
void BindingBase::SetAttr(py::object self, const std::string& s, py::object value)
112133
{
113134
Base* self_d = py::cast<Base*>(self);
114-
BaseData* d = self_d->findData(s);
115135

136+
BaseData* d = self_d->findData(s);
116137
if(d!=nullptr)
117138
{
118139
SetData(d, value);
@@ -122,6 +143,7 @@ void BindingBase::SetAttr(py::object self, const std::string& s, py::object valu
122143
BaseLink* l = self_d->findLink(s);
123144
if(l!=nullptr)
124145
{
146+
SetLink(l, value);
125147
return;
126148
}
127149

@@ -132,24 +154,16 @@ void BindingBase::SetAttr(py::object self, const std::string& s, py::object valu
132154
void BindingBase::SetAttr(Base& self, const std::string& s, py::object value)
133155
{
134156
BaseData* d = self.findData(s);
135-
136157
if(d!=nullptr)
137158
{
138-
const AbstractTypeInfo& nfo{ *(d->getValueTypeInfo()) };
139-
140-
/// We go for the container path.
141-
if(nfo.Container())
142-
{
143-
PythonFactory::fromPython(d,value);
144-
return;
145-
}
146159
PythonFactory::fromPython(d, value);
147160
return;
148161
}
149162

150163
BaseLink* l = self.findLink(s);
151164
if(l!=nullptr)
152165
{
166+
SetLink(l, value);
153167
return;
154168
}
155169

bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class BindingBase
4848
/// Set the data field value from the array.
4949
static void SetDataFromArray(sofa::core::objectmodel::BaseData* data, const pybind11::array& value);
5050
static bool SetData(sofa::core::objectmodel::BaseData* data, pybind11::object value);
51+
static bool SetLink(sofa::core::objectmodel::BaseLink* link, pybind11::object value);
5152
static pybind11::object setDataValues(sofa::core::objectmodel::Base& self, pybind11::kwargs kwargs);
5253

5354
static pybind11::list getDataFields(sofa::core::objectmodel::Base& self);

bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseData.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <SofaPython3/Sofa/Core/Binding_Base.h>
3030
#include <SofaPython3/Sofa/Core/Binding_BaseData.h>
3131
#include <SofaPython3/Sofa/Core/Binding_BaseData_doc.h>
32+
#include <SofaPython3/Sofa/Core/Binding_LinkPath.h>
3233
#include <SofaPython3/Sofa/Core/Data/Binding_DataContainer.h>
3334
#include <SofaPython3/DataHelper.h>
3435
#include <SofaPython3/PythonFactory.h>
@@ -139,7 +140,7 @@ py::object __getattr__(py::object self, const std::string& s)
139140
return PythonFactory::valueToPython_ro(py::cast<BaseData*>(self));
140141

141142
if(s == "linkpath")
142-
return py::cast((py::cast<BaseData*>(self))->getLinkPath());
143+
return py::cast(sofapython3::LinkPath(py::cast<BaseData*>(self)));
143144

144145
/// BaseData does not support dynamic attributes, if you think this is an important feature
145146
/// please request for its integration.
@@ -151,11 +152,18 @@ void setParent(BaseData* self, BaseData* parent)
151152
self->setParent(parent);
152153
}
153154

154-
void setParentFromLinkPath(BaseData* self, const std::string& parent)
155+
void setParentFromLinkPathStr(BaseData* self, const std::string& parent)
155156
{
156157
self->setParent(parent);
157158
}
158159

160+
void setParentFromLinkPath(BaseData* self, const LinkPath& parent)
161+
{
162+
if(!parent.isPointingToData())
163+
throw std::runtime_error("The provided linkpath is not containing a linkable data");
164+
self->setParent(parent.targetData);
165+
}
166+
159167
bool hasParent(BaseData *self)
160168
{
161169
return (self->getParent() != nullptr);
@@ -205,6 +213,7 @@ void moduleAddBaseData(py::module& m)
205213
data.def("isPersistent", &BaseData::isPersistent, sofapython3::doc::baseData::isPersistent);
206214
data.def("setPersistent", &BaseData::setPersistent, sofapython3::doc::baseData::setPersistent);
207215
data.def("setParent", setParent, sofapython3::doc::baseData::setParent);
216+
data.def("setParent", setParentFromLinkPathStr, sofapython3::doc::baseData::setParent);
208217
data.def("setParent", setParentFromLinkPath, sofapython3::doc::baseData::setParent);
209218
data.def("hasParent", hasParent, sofapython3::doc::baseData::hasParent);
210219
data.def("read", &BaseData::read, sofapython3::doc::baseData::read);
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/******************************************************************************
2+
* SOFA, Simulation Open-Framework Architecture *
3+
* (c) 2021 INRIA, USTL, UJF, CNRS, MGH *
4+
* *
5+
* This program is free software; you can redistribute it and/or modify it *
6+
* under the terms of the GNU Lesser General Public License as published by *
7+
* the Free Software Foundation; either version 2.1 of the License, or (at *
8+
* your option) any later version. *
9+
* *
10+
* This program is distributed in the hope that it will be useful, but WITHOUT *
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
13+
* for more details. *
14+
* *
15+
* You should have received a copy of the GNU Lesser General Public License *
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
17+
*******************************************************************************
18+
* Contact information: [email protected] *
19+
******************************************************************************/
20+
21+
#include <sofa/core/objectmodel/BaseLink.h>
22+
using sofa::core::objectmodel::BaseLink;
23+
24+
#include <sofa/core/objectmodel/BaseObject.h>
25+
using sofa::core::objectmodel::BaseObject;
26+
27+
#include <SofaPython3/Sofa/Core/Binding_LinkPath.h>
28+
#include <SofaPython3/Sofa/Core/Binding_LinkPath_doc.h>
29+
30+
/// Makes an alias for the pybind11 namespace to increase readability.
31+
namespace py { using namespace pybind11; }
32+
33+
// To bring in the `_a` literal
34+
using namespace pybind11::literals;
35+
36+
namespace sofapython3
37+
{
38+
39+
LinkPath::LinkPath(sofa::core::sptr<sofa::core::objectmodel::Base> target)
40+
{
41+
targetBase = target;
42+
targetData = nullptr;
43+
}
44+
45+
LinkPath::LinkPath(sofa::core::objectmodel::BaseData* target)
46+
{
47+
// If the data is attached to
48+
if(target->getOwner())
49+
{
50+
targetBase = target->getOwner();
51+
}
52+
targetData = target;
53+
}
54+
55+
bool LinkPath::isPointingToData() const
56+
{
57+
return targetData != nullptr;
58+
}
59+
60+
std::string __str__(const LinkPath& entry)
61+
{
62+
std::ostringstream s;
63+
if(entry.targetData != nullptr)
64+
return entry.targetData->getLinkPath();
65+
else if(entry.targetBase.get() != nullptr)
66+
return "@" + entry.targetBase->getPathName();
67+
throw std::runtime_error("Empty LinkPath");
68+
}
69+
70+
std::string __repr__(const LinkPath& entry)
71+
{
72+
std::ostringstream s;
73+
s << "LinkPath(\"" << __str__(entry) << "\")";
74+
return s.str();
75+
}
76+
77+
void moduleAddLinkPath(py::module& m)
78+
{
79+
py::class_<LinkPath> link(m, "LinkPath", sofapython3::doc::linkpath::linkpath);
80+
link.def("__str__", &__str__);
81+
link.def("__repr__", &__repr__);
82+
}
83+
84+
}/// namespace sofapython3

0 commit comments

Comments
 (0)