Skip to content

Commit 72ff387

Browse files
aleblanc30Alexandre Blanchugtalbot
authored
Add a function to get the kinetic and potential energy of a node from a python script (#504)
* Add binding to return the energy of a node. * Fix bugs * ADD example scene * Apply suggestions from code review --------- Co-authored-by: Alexandre Blanc <[email protected]> Co-authored-by: Hugo <[email protected]>
1 parent 09c18ed commit 72ff387

File tree

3 files changed

+124
-0
lines changed

3 files changed

+124
-0
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <pybind11/numpy.h>
2323

2424
#include <sofa/simulation/Simulation.h>
25+
#include <sofa/simulation/mechanicalvisitor/MechanicalComputeEnergyVisitor.h>
2526
#include <sofa/core/ComponentNameHelper.h>
2627

2728
#include <sofa/core/objectmodel/BaseData.h>
@@ -606,6 +607,15 @@ void sendEvent(Node* self, py::object pyUserData, char* eventName)
606607
self->propagateEvent(sofa::core::execparams::defaultInstance(), &event);
607608
}
608609

610+
py::object computeEnergy(Node* self)
611+
{
612+
sofa::simulation::mechanicalvisitor::MechanicalComputeEnergyVisitor energyVisitor(sofa::core::mechanicalparams::defaultInstance());
613+
energyVisitor.execute(self->getContext());
614+
const SReal kineticEnergy = energyVisitor.getKineticEnergy();
615+
const SReal potentialEnergy = energyVisitor.getPotentialEnergy();
616+
return py::cast(std::make_tuple(kineticEnergy, potentialEnergy));
617+
}
618+
609619
}
610620

611621
void moduleAddNode(py::module &m) {
@@ -660,6 +670,7 @@ void moduleAddNode(py::module &m) {
660670
p.def("getMechanicalState", &getMechanicalState, sofapython3::doc::sofa::core::Node::getMechanicalState);
661671
p.def("getMechanicalMapping", &getMechanicalMapping, sofapython3::doc::sofa::core::Node::getMechanicalMapping);
662672
p.def("sendEvent", &sendEvent, sofapython3::doc::sofa::core::Node::sendEvent);
673+
p.def("computeEnergy", &computeEnergy, sofapython3::doc::sofa::core::Node::computeEnergy);
663674

664675
}
665676
} /// namespace sofapython3

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,10 @@ static auto sendEvent =
397397
:type pyUserData: py::object
398398
:type eventName: string
399399
)";
400+
static auto computeEnergy =
401+
R"(
402+
Returns the tuple (kineticEnergy, potentialEnergy)
403+
)";
400404

401405
}
402406

examples/access_energy.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import os
2+
import Sofa
3+
4+
dirname = os.path.dirname(__file__)
5+
def createScene(rootNode, dt=0.01, m=1, g=1, L=100, mu=0):
6+
7+
# rootNode
8+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.Collision.Detection.Algorithm')
9+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.Collision.Detection.Intersection')
10+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.Collision.Geometry')
11+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.Collision.Response.Contact')
12+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.IO.Mesh')
13+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.LinearSolver.Iterative')
14+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.Mapping.Linear')
15+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.Mass')
16+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.ODESolver.Backward')
17+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.SolidMechanics.Spring')
18+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.StateContainer')
19+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.Topology.Container.Constant')
20+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.Topology.Container.Grid')
21+
rootNode.addObject('RequiredPlugin', name='Sofa.GL.Component.Rendering3D')
22+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.AnimationLoop')
23+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.Constraint.Lagrangian.Correction')
24+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.LinearSolver.Direct')
25+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.Mapping.NonLinear')
26+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.Topology.Container.Dynamic')
27+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.Constraint.Lagrangian.Solver') # Needed to Use components [GenericConstraintSolver]
28+
rootNode.addObject('RequiredPlugin', name='Sofa.Component.LinearSystem') # Needed to Use components [MatrixLinearSystem]
29+
rootNode.addObject('RequiredPlugin', name='MultiThreading') # Needed to Use components [ParallelBVHNarrowPhase,ParallelBruteForceBroadPhase]
30+
31+
rootNode.addObject('FreeMotionAnimationLoop')
32+
rootNode.addObject('CollisionPipeline', verbose='0', depth='10', draw='0')
33+
rootNode.addObject('ParallelBruteForceBroadPhase')
34+
rootNode.addObject('ParallelBVHNarrowPhase')
35+
rootNode.addObject('MinProximityIntersection', name='Proximity', alarmDistance='10', contactDistance='0.02')
36+
rootNode.addObject('CollisionResponse', name='Response', response='FrictionContactConstraint', responseParams=f'mu={mu}')
37+
rootNode.addObject('GenericConstraintSolver', maxIterations='10', multithreading='true', tolerance='1.0e-3')
38+
39+
boxTranslation = "-20 -0.9 0"
40+
rootNode.addObject('MeshOBJLoader', name='Loader-box', filename='mesh/cube.obj', translation=boxTranslation)
41+
42+
rootNode.dt = dt
43+
44+
rootNode.gravity = [g, 0, -1]
45+
46+
# rootNode/Box
47+
Box = rootNode.addChild('Box')
48+
49+
Box.addObject('EulerImplicitSolver', name='EulerImplicitScheme')
50+
Box.addObject('SparseLDLSolver', name='linearSolver', template='CompressedRowSparseMatrixd', linearSystem='@system')
51+
Box.addObject('MatrixLinearSystem', template='CompressedRowSparseMatrixd', name='system')
52+
53+
Box.addObject('MechanicalObject', name='BoxDOF', template='Rigid3d', position=boxTranslation+' 0 0 0 1')
54+
Box.addObject('UniformMass', totalMass=m)
55+
56+
# rootNode/Box/Collision
57+
Collision = Box.addChild('Collision')
58+
Collision.addObject('PointSetTopologyContainer', name='boxCollision', position=boxTranslation)
59+
Collision.addObject('PointSetTopologyModifier', name='Modifier')
60+
Collision.addObject('MechanicalObject', name='CollisionDOF', template='Vec3d')
61+
Collision.addObject('RigidMapping', name='MappingCollision', input='@../BoxDOF', output='@CollisionDOF', globalToLocalCoords='true')
62+
Collision.addObject('PointCollisionModel', name='CenterLineCollisionModel', proximity='0.02')
63+
64+
# rootNode/Box/Visu
65+
Visu = Box.addChild('Visu')
66+
Visu.addObject('OglModel', name='VisualModel', color='0.7 0.7 0.7 0.8', position='@../../Loader-box.position', triangles='@../../Loader-box.triangles')
67+
Visu.addObject('RigidMapping', name='SurfaceMapping', input='@../BoxDOF', output='@VisualModel', globalToLocalCoords='true')
68+
69+
Box.addObject('LinearSolverConstraintCorrection', name='ConstraintCorrection', linearSolver='@linearSolver')
70+
71+
# rootNode/Floor
72+
Floor = rootNode.addChild('Floor')
73+
Floor.addObject('TriangleSetTopologyContainer', name='FloorTopology', position=f'-20 -15 -2 {L} -15 -2 {L} 15 -2 -20 15 -2', triangles='0 2 1 0 3 2')
74+
Floor.addObject('MechanicalObject', name='FloorDOF', template='Vec3d')
75+
Floor.addObject('TriangleCollisionModel', name='FloorCM', proximity='0.002', moving='0', simulated='0', color='0.3 0.3 0.3 0.1')
76+
77+
return 0
78+
79+
def main():
80+
import plotext
81+
m, g = 1, 1
82+
mu = .1
83+
root = Sofa.Core.Node("root")
84+
createScene(root, dt=1e-4, m=m, g=g, mu=mu, L=1000)
85+
Sofa.Simulation.init(root)
86+
Sofa.Simulation.animate(root, root.dt.value)
87+
88+
Ks, Us = [], []
89+
count = 0
90+
while root.Box.BoxDOF.position.value[0][0] < 1000/1.4142:
91+
K, U = root.Box.computeEnergy()
92+
Ks.append(K)
93+
Us.append(U)
94+
95+
Sofa.Simulation.animate(root, root.dt.value)
96+
97+
if not(count%1000):
98+
plotext.clear_figure()
99+
plotext.plot(Ks)
100+
plotext.plot(Us)
101+
plotext.plot([k+u for k,u in zip(Ks,Us)])
102+
plotext.theme('clear')
103+
plotext.show()
104+
105+
count += 1
106+
107+
108+
if __name__ == '__main__':
109+
main()

0 commit comments

Comments
 (0)