= README
ISSofaPython contains
- a python module which defines bindings for the
SofaCorelibrary andSofaSimulationTreelibrary. The source code for the python module is in themodulefolder - a dedicated sofa plugin which contains a specific scene loader that can be used to load directly python scene files from the main application
It serves as a replacement for the SofaPython plugin. It uses the [[ https://pybind11.readthedocs.io/en/stable/intro.html | pybind11 library ]] to expose c++ types in Python.
== TL;DR
Write your python scene directly from the interpreter
import ISSofaPython as Sofa
class MyObject(Sofa.BaseObject):
def __init__(self,name):
Sofa.BaseObject.__init__(self)
self.setName(name)
def init(self):
print self.getName() + ": initialized"
rootObject = MyObject("rootObject")
root = Sofa.createRootNode("root")
root.addObject(rootObject)
childObject = MyObject("childObject")
child = root.createChild("child")
child.addObject(childObject)
Sofa.initializeGraph(root)
Sofa.step(root,0.02) # advance 0.02 seconds in time
Sofa.cleanupGraph(root)
Or write it so that it can be loaded from runSofa provided the ISSofaPythonPlugin is loaded
import ISSofaPython as Sofa
def createScene(root):
rootObject = Sofa.BaseObject()
rootObject.setName("rootObject")
root.addObject(rootObject)
childObject = Sofa.BaseObject()
childObject.setName("childObject")
child = root.createChild("child")
child.addObject(childObject)
child.addObject(Sofa.BaseObject() )
return root
== Data and Links as as special attributes of python wrapped Sofa objects
For python objects which wraps a Sofa object ( ie any object which derive from sofa::core::objectmodel::Base ) the list of Data and Link
are bound as special attributes on top of the python __dict__ using their name property.
Note that these special attributes do not belong to the __dict__ of the python object instance
For example the following syntax works:
obj.printLog.value = True
# equivalent syntax
obj.findData("printLog").value = True
Also some automatic casting is performed when setting the Data or Link of a python wrapped Sofa object
Example for Data attribute casting:
mstate1 = node.createObject("MechanicalObject",template="Vec3d", name="mstate1", position=[(1,0,0),(0,1,0),(0,0,1)])
mstate2 = node.createObject("MechanicalObject",template="Vec3d", name="mstate2", position=mstate1.position.getPath() ) # mstate1.position becomes the parent Data of mstate2.position
mstate3 = node.createObject("MechanicalObject",template="Vec3d", name="mstate3", position=mstate2.position.value ) # mstate3.position is a deep copy of mstate2.position
Example for Link attribute casting:
mstate1 = parent.createObject("MechanicalObject",template="Vec3d", name="mstate1")
mstate2 = child.createObject("MechanicalObject",template="Vec3d", name="mstate2")
mapping = child.createObject("BarycentricMapping", input=mstate1.getPath(),output=mstate2.getPath())
== Main differences compared to SofaPython
From a user perspective several aspects differ from SofaPython
=== Accessing Data value from a python wrapped Sofa object
In SofaPython accessing the value stored in a Data result in typing something like
obj.printLog = False
In ISSofaPython it requires typing
obj.printLog.value = False
This change is however more natural when dealing with other part of the Data API
For example setting the parent of a Data in SofaPython requires to write:
obj.findData('printLog').setParent(parentData)
However in ISSofaPython since it is really the Data that is bound as an attribute
this works
obj.printLog.setParent(parentData)
=== Setting the value of a Data which wraps a vector/array type
ISSofaPython uses more advanced reflection mechanisms for Data types compared to SofaPython.
For example to assign a python object to a Data which internally represents a vector< Vec3d >, a similar structure must also exist in the python object.
The syntax is currently permissive as shown in the following example:
import ISSofaPython as Sofa
#...
root = Sofa.createRootNode("root")
mobj = root.createObject("MechanicalObject", template="Vec3d", mame="MO" )
# The following gives a very different result compared to SofaPython
mobj.position.value = [ 0,1,0 ]
mobj.position.value # prints [(0.0, 0.0, 0.0), (1.0, 1.0, 1.0), (0.0, 0.0, 0.0)]
# This is the correct way to assign to a `Data` which stores a `vector<Vec3d>` with ISSofaPython
mobj.position.value = [ (0,0,0), (1,0,0) ]
# This works as well
mobj.position.value = [ [0,0,0], [1,0,0] ]
== Limitations
=== Accessing the value of a Data
This version implements only partially bindings for the reflection mechanism of Data.
=== Subclassing a C++ pybind11 class in Python
Currently inheriting from a Sofa type has only practical limited support in terms of virtual method override, as explained in the comments of tests/test_pythonsceneloader_inheritbaseobject.py and tests/test_pythonsceneloader_inherit_controller.py.
One way this limitation could be alleviated would be to have the python Sofa graph also manage the lifetime of the instances of the python object that compose it.
For reference, this limitation has also been reported [[pybind/pybind11#1145 | here (issue 1145)]] and also [[pybind/pybind11#1333 | here (issue 1333)]] on the pybind11 github.
So this particular limitation could be well be removed in future versions of pybind11, but it is also likely that a feasible workaround can be implemented in the specific case of lifetime management of python objects in a Sofa graph instance.