Indentation reflecting graph structure in Python scenes #5728
Replies: 4 comments
-
|
Hello Leo, Thank for sharing this. I fully share the objective and support creative hacking of the python syntax. And you proposal is very interesting. There is a lot to say, if these kind of subject interest you I suggest to join the prefab work group animated by @bakpaul and @EulalieCoevoet, meeting are on their way before and during SofaWeek. |
Beta Was this translation helpful? Give feedback.
-
|
In my past experiment focusing on the indendation aspect I was using the python feature called a Context Manager to make the code looking like the following: @Sofa.Scene
def createScene(root):
with Node("Settings"):
Object("VisualStyle", name="flags")
with Node("Plugins"):
Object("RequiredPlugin", name="Sofa.Component.Visual")
Object("RequiredPlugin", name="Sofa.Component.ODESolver.Forward")
Object("RequiredPlugin", name="Sofa.Component.StateContainer")
Object("RequiredPlugin", name="Sofa.Component.Mass")
Object("RequiredPlugin", name="Sofa.Component.MechanicalLoad")
Object("RequiredPlugin", name="Sofa.Component.SolidMechanics.Spring")
Object("RequiredPlugin", name="Sofa.Component.Mapping.NonLinear")
with Node("Modelling"):
with Node("Object1"):
Object("MechanicalObject", name="test", position=1000, template="Rigid3")
with Node("Mapping"):
Object("OglModel", name="visual")
Object("RigidMapping", name="visual")Contexts manager have the advantage of:
Compared to your proposal I also use a stack so we can push/pop and have internal consistency and proper backtrace in case of exception. Experimenting with context manager was very simple as it just need to add enter/exit methods in Binding_Node.cpp. p.def("__enter__", [](py::object self)
{
std::cout << "ENTER CTX MANAGER c++" << std::endl;
//self.attr("onCtxEnter")(self);
return self;
});
p.def("__exit__",
[](py::object self, py::object type, py::object value, py::object traceback)
{
std::cout << "EXIT CTX MANAGER c++" << std::endl;
//self.attr("onCtxExit")(self, type, value, traceback);
});In the following c++ code, un-commenting the self.attr("onCtxEnter") permit to forward to python method "onCtxEnter"/"onCtxExit" (as I do in some of the following example of use) Even without the forwarding to python code, the c++ permits to write well indented code with the existing API and without any hacks/python magic: def createSceneV2(root):
with root.addChild("Settings") as settings:
settings.addObject("VisualStyle", name="flags")
with settings.addChild("Plugins") as plugins:
plugins.addObject("RequiredPlugin", name="XXX")
plugins.addObject("RequiredPlugin", name="YYY")The drawback of the previous lines is that, despite it is well indented, it is also verbose. I think this is to Here is the helper functions I used for my first example... they are pretty straighforward except the MonkeyPatching to inject onCtxEnxter/onCtxExit: class NodeStack(object):
def __init__(self, *args, **kwargs):
self.nodes = []
def push(self, node):
self.nodes.append(node)
def pop(self):
self.nodes = self.nodes[:-1]
def top(self):
return self.nodes[-1]
__nodestack__ = NodeStack()
def Node(name, *args, **kwargs):
global __nodestack__
node = __nodestack__.top().addChild(name, *args, **kwargs)
def enter(self):
__nodestack__.push(self)
def exit(self, exc_type, exc_val, exc_tb):
__nodestack__.pop()
node.onCtxEnter = enter
node.onCtxExit = exit
return node
def Object(type, *args, **kwargs):
global __nodestack__
node = __nodestack__.top().addObject(type, *args, **kwargs)Up to now, I haven't concluded whether or not the use of a global variable to hide the "self/this" the current node was a clever idea or mis-design that would cause us lot of troubles. But, to avoid it, I didn't find better idea that having explicit "parenting" by propagating the "context" node as in the following example: def createSceneV1(root):
with Node("Settings", parent=root) as settings:
Object("VisualStyle", name="flags", parent= settings)
with Node("Plugins", parent=setting) as plugins:
Object("RequiredPlugin", name="XXX", parent=plugins)
Object("RequiredPlugin", name="YYY", parent=plugins)I remember I also experimented if it was possible to as the "Settings" registering the "Settings" variable in the So ... maybe with more people interested on these issues, it is time to give all that a fresh look. |
Beta Was this translation helpful? Give feedback.
-
|
Thank you for your answers! After we talked about it on Friday, I also made a context manager version that looks like yours: def createScene(root):
set_current_node(root)
set_data(gravity=(0, -10, 0), time=0, dt=1e-3, bbox=[[-1, -3, -1], [4, 3, 1]])
with SofaNode("plugins"):
add_object("RequiredPlugin", pluginName="Sofa.Component.Visual")
# [...]
add_object("DefaultAnimationLoop")
add_object('VisualStyle', displayFlags='showBehavior showBehaviorModels showForceFields showMappings')
with SofaNode("point") as point:
add_object("MechanicalObject", template="Vec3d", position=(0, 2, 0))
with SofaNode("rigid") as rigid:
add_object("MechanicalObject", template="Rigid3d", position="1.5 0 0 0 0 0 1")
add_object("UniformMass", template="Rigid3d", totalMass=1.0)
with SofaNode("mapped") as mapped:
add_object("MechanicalObject", template="Vec3d", position=(0, 0.5, 0))
add_object("RigidMapping")
add_object("SpringForceField", object1="@../point", object2="@mapped", indices1=0, indices2=0, length=2, stiffness=100)
# Nodes created by context manager can be used outside of it:
add_object("SpringForceField", object1=point.linkpath, object2=mapped.linkpath, indices1=0, indices2=0, length=2, stiffness=100)Compared to the previous version I just replaced the code for class SofaNode:
def __init__(self, name):
self.name = name
self.parent = None
def __enter__(self):
self.parent = get_current_node()
child = add_child(self.name)
set_current_node(child)
return child
def __exit__(self, exc_type, exc_value, traceback):
set_current_node(self.parent)Comments:
with Node("my_node") as my_node:
my_node.addObject(...) # rather than Object(..., parent=my_node)
with tf.GradientTape() as tape:
y = layer(x)
loss = tf.reduce_mean(y**2)
grad = tape.gradient(loss, layer.trainable_variables) # uses 'tape' outside the context manager
|
Beta Was this translation helpful? Give feedback.
-
|
Hi @leobois67
with Node("my_node") as my_node:
my_node.addObject(...)
or Object(..., parent=my_node)Implicit parenting being like: with Node("my_node"):
Object(RequiredObject(""))
with Node("Settings") as Settings:
Object(RequiredObject(""))We are actually repeating the name "Settings". One for the sofa node, one to declare a python variable's name. So I was referring that in my pas experiment I tried to add variables with the name of the Node/Object that was created (to avoid this repetitions)
def SofaPrefab(func):
rs=inspect.stack()[1:2]
def wrapper(*args, **kwargs):
with Node(*args) as node:
ss=inspect.stack()[1:2]
node.setInstanciationSourceFileName(ss[0][1]) # This allows, in UX friendly GUIs to right click to "Go to Scene ...."
node.setInstanciationSourceFilePos(ss[0][2])
node.setDefinitionSourceFileName(rs[0][1]) # This allows, in UX friendly GUIs to right click to "Go to Implementation ...."
node.setDefinitionSourceFilePos(rs[0][2]+1)
func(*args,**kwargs)
return node
return wrapper
@SofaPrefab
def MyObjectAssembly(self, n=0):
Object("MechanicalObject", name="dofs", template="Rigid3")
Object("UniformMass", name="mass", totalMass=1)
for i in range(n):
Node(f"Child{i}")
with Node("Visual"):
Object("OglModel")
Object("RigidMapping")
@SofaScene
def createScene(root):
set(gravity=[1,2,3]) # implicit use of this_node
with Node("Settings"):
Object("VisualStyle", name="flags")
with Node("Plugins"):
Object("RequiredPlugin", name="Sofa.Component.Visual")
MyObjectAssembly("Prefab1")
MyObjectAssembly("Prefab2", n=3)
with MyObjectAssembly("Prefab3"):
Node("AChildToAnExistingPrefab") |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Writing a scene in xml for the first time I noted how pleasant it was to have the structure of the graph reflected in the indentation of the code, compared to the flat implementation of the scenes I had in Python which makes understanding the graph and locating the different components a bit of a pain.
And so I looked for a way to have this indentation for the scenes in Python. After some thought I decided to use function definition as the means to indent new nodes, and to rely on decorators to make it work:
This works thanks to the following code:
The
nameargument tosofa_nodecan be used to specify the name of the node with a string. This allows to have nodes whith names depending on a variable, for instance:Some thoughts on my implementation:
What do you think?
Beta Was this translation helpful? Give feedback.
All reactions