Skip to content

Commit f87ea00

Browse files
[Examples] Add a script to access contact forces (#514)
* [Examples] Add a script to access contact forces * Apply suggestions from code review Co-authored-by: Damien Marchal <[email protected]> * use VisualVectorField to render contact forces and take other reviews into account * add comments --------- Co-authored-by: Damien Marchal <[email protected]>
1 parent e42fc90 commit f87ea00

File tree

1 file changed

+140
-0
lines changed

1 file changed

+140
-0
lines changed

examples/access_contact_forces.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Required import for python
2+
import Sofa
3+
import numpy as np
4+
import math
5+
6+
7+
# Main function taking a boolean as parameter to choose whether or not to use the GUI
8+
def main(use_gui=True):
9+
import SofaImGui
10+
import Sofa.Gui
11+
12+
root = Sofa.Core.Node("root")
13+
createScene(root)
14+
Sofa.Simulation.initRoot(root)
15+
16+
if not USE_GUI:
17+
import SofaQt
18+
19+
for iteration in range(10):
20+
Sofa.Simulation.animate(root, root.dt.value)
21+
else:
22+
Sofa.Gui.GUIManager.Init("myscene", "imgui")
23+
Sofa.Gui.GUIManager.createGUI(root, __file__)
24+
Sofa.Gui.GUIManager.SetDimension(1080, 1080)
25+
Sofa.Gui.GUIManager.MainLoop(root)
26+
Sofa.Gui.GUIManager.closeGUI()
27+
28+
29+
def createScene(root):
30+
root.gravity=[0, -9.81, 0]
31+
root.dt=0.02
32+
33+
root.addObject("RequiredPlugin", pluginName=[ 'Sofa.Component.Collision.Detection.Algorithm',
34+
'Sofa.Component.Collision.Detection.Intersection', 'Sofa.Component.Collision.Geometry',
35+
'Sofa.Component.Collision.Response.Contact', 'Sofa.Component.Constraint.Projective',
36+
'Sofa.Component.IO.Mesh','Sofa.Component.LinearSolver.Iterative',
37+
'Sofa.Component.Mapping.Linear', 'Sofa.Component.Mass', 'Sofa.Component.ODESolver.Backward',
38+
'Sofa.Component.SolidMechanics.FEM.Elastic','Sofa.Component.StateContainer',
39+
'Sofa.Component.Topology.Container.Dynamic','Sofa.Component.Visual',
40+
'Sofa.GL.Component.Rendering3D','Sofa.Component.Constraint.Lagrangian.Correction',
41+
'Sofa.Component.Constraint.Lagrangian.Solver', 'Sofa.Component.MechanicalLoad',
42+
'Sofa.Component.LinearSolver.Direct','Sofa.Component.AnimationLoop'
43+
])
44+
45+
root.addObject('FreeMotionAnimationLoop')
46+
# Constraint solver computing the constraint/contact forces, stored in the constraint space (normal , tangential_1, tangential_2)
47+
constraint_solver = root.addObject('GenericConstraintSolver', maxIterations=1000, tolerance=1e-6, computeConstraintForces=True)
48+
49+
root.addObject('VisualStyle', displayFlags="showCollisionModels hideVisualModels showForceFields")
50+
root.addObject('CollisionPipeline', name="collision_pipeline")
51+
root.addObject('BruteForceBroadPhase', name="broad_phase")
52+
root.addObject('BVHNarrowPhase', name="narrow_phase")
53+
root.addObject('DiscreteIntersection')
54+
root.addObject('CollisionResponse', name="collision_response", response="FrictionContactConstraint", responseParams="mu=0.1")
55+
56+
root.addObject('MeshOBJLoader', name="load_liver_surface", filename="mesh/liver-smooth.obj")
57+
58+
liver = root.addChild('Liver')
59+
liver.addObject('EulerImplicitSolver', name="cg_odesolver", rayleighStiffness=0.1, rayleighMass=0.1)
60+
liver.addObject('SparseLDLSolver', name="linear_solver", template="CompressedRowSparseMatrixMat3x3d")
61+
liver.addObject('MeshGmshLoader', name="loader_liver_volume", filename="mesh/liver.msh")
62+
liver.addObject('TetrahedronSetTopologyContainer', name="topo", src="@loader_liver_volume")
63+
# Liver MechanicalObject where the constraint/contact forces will be stored in the (x,y,z) coordinate system
64+
liverMO = liver.addObject('MechanicalObject', name="dofs", src="@loader_liver_volume")
65+
liver.addObject('TetrahedronSetGeometryAlgorithms', template="Vec3d", name="geom_algo")
66+
liver.addObject('DiagonalMass', name="mass", massDensity=1.0)
67+
liver.addObject('TetrahedralCorotationalFEMForceField', template="Vec3d", name="FEM", method="large", poissonRatio=0.3, youngModulus=3000, computeGlobalMatrix=False)
68+
liver.addObject('FixedProjectiveConstraint', name="fixed_constraint", indices=[3,39,64])
69+
liver.addObject('LinearSolverConstraintCorrection')
70+
71+
# Forcefield only used for visualization purposes (of the contact fborces)
72+
contactVisu = liver.addChild('RenderingContactForces')
73+
contactVisu.addObject('VisualStyle', displayFlags="showVisualModels")
74+
renderingForces = contactVisu.addObject('VisualVectorField', name="drawing_contact_forces", vectorScale="100", vector="@../dofs.lambda", position="@../dofs.position", color="orange", drawMode="Arrow")
75+
76+
visu = liver.addChild('Visu')
77+
visu.addObject('OglModel', name="visual_model", src="@../../load_liver_surface")
78+
visu.addObject('BarycentricMapping', name="visual_mapping", input="@../dofs", output="@visual_model")
79+
80+
surf = liver.addChild('Surf')
81+
surf.addObject('SphereLoader', name="loader_sphere_model", filename="mesh/liver.sph")
82+
surf.addObject('MechanicalObject', name="spheres", position="@loader_sphere_model.position")
83+
surf.addObject('SphereCollisionModel', name="collision_model", listRadius="@loader_sphere_model.listRadius")
84+
surf.addObject('BarycentricMapping', name="collision_mapping", input="@../dofs", output="@spheres")
85+
86+
87+
particle = root.addChild('Particle')
88+
particle.addObject('EulerImplicitSolver')
89+
particle.addObject('CGLinearSolver', threshold=1e-09, tolerance=1e-09, iterations=200)
90+
# Particle MechanicalObject where the constraint/contact forces will be stored in the (x,y,z) coordinate system
91+
particleMO = particle.addObject('MechanicalObject', showObject=True, position=[-2, 10, 0, 0, 0, 0, 1], name=f'particle_DoFs', template='Rigid3d')
92+
particle.addObject('UniformMass', totalMass=1)
93+
particle.addObject('ConstantForceField', name="CFF", totalForce=[0, -1, 0, 0, 0, 0] )
94+
particle.addObject('SphereCollisionModel', name="SCM", radius=1.0 )
95+
particle.addObject('UncoupledConstraintCorrection')
96+
97+
98+
# Python controller accessing and displaying the contact forces in the ConstantForceField
99+
root.addObject(AccessContactForces('AccessContactForces', name='AccessContactForces',
100+
constraint_solver=constraint_solver,
101+
soft_liver=liverMO,
102+
forces_visu=renderingForces,
103+
root_node=root))
104+
105+
106+
class AccessContactForces(Sofa.Core.Controller):
107+
108+
def __init__(self, *args, **kwargs):
109+
Sofa.Core.Controller.__init__(self, *args, **kwargs)
110+
self.constraint_solver = kwargs.get("constraint_solver")
111+
self.soft_liver = kwargs.get("soft_liver")
112+
self.forces_visu = kwargs.get("forces_visu")
113+
self.root_node = kwargs.get("root_node")
114+
# Initialize the rendered vector with zero vec<Vec3>
115+
self.forces_visu.vector.value = np.zeros((181,3))
116+
117+
def onAnimateEndEvent(self, event):
118+
119+
lambda_vector = self.constraint_solver.constraintForces.value
120+
# If there is a contact
121+
if(len(lambda_vector) > 0):
122+
print(f"At time = {round(self.root_node.time.value,3)}, forces in the contact space (n, t1, t2) equals:\n {lambda_vector} ")
123+
124+
# Compute the inverse (reaction force)
125+
self.forces_visu.vector.value = -self.soft_liver.getData("lambda").value
126+
127+
# Scale automatically the displayed force vector
128+
visuScale = self.forces_visu.vectorScale.value
129+
fact = np.max(lambda_vector)
130+
self.forces_visu.vectorScale.value = 10/fact
131+
132+
# If no contact
133+
else:
134+
self.forces_visu.vector.value = np.zeros((181,3))
135+
136+
137+
138+
# Function used only if this script is called from a python environment
139+
if __name__ == '__main__':
140+
main(True)

0 commit comments

Comments
 (0)