|
| 1 | +# Required import for python |
| 2 | +import Sofa |
| 3 | +import numpy as np |
| 4 | +import math |
| 5 | + |
| 6 | + |
| 7 | +# Choose in your script to activate or not the GUI |
| 8 | +USE_GUI = True |
| 9 | + |
| 10 | + |
| 11 | +def main(): |
| 12 | + import SofaRuntime |
| 13 | + import Sofa.Gui |
| 14 | + |
| 15 | + root = Sofa.Core.Node("root") |
| 16 | + createScene(root) |
| 17 | + Sofa.Simulation.initRoot(root) |
| 18 | + |
| 19 | + if not USE_GUI: |
| 20 | + import SofaQt |
| 21 | + |
| 22 | + for iteration in range(10): |
| 23 | + Sofa.Simulation.animate(root, root.dt.value) |
| 24 | + else: |
| 25 | + Sofa.Gui.GUIManager.Init("myscene", "qglviewer") |
| 26 | + Sofa.Gui.GUIManager.createGUI(root, __file__) |
| 27 | + Sofa.Gui.GUIManager.SetDimension(1080, 1080) |
| 28 | + Sofa.Gui.GUIManager.MainLoop(root) |
| 29 | + Sofa.Gui.GUIManager.closeGUI() |
| 30 | + |
| 31 | + |
| 32 | +def createScene(root): |
| 33 | + root.gravity=[0, -9.81, 0] |
| 34 | + root.dt=0.02 |
| 35 | + |
| 36 | + root.addObject("RequiredPlugin", pluginName=[ 'Sofa.Component.Collision.Detection.Algorithm', |
| 37 | + 'Sofa.Component.Collision.Detection.Intersection', 'Sofa.Component.Collision.Geometry', |
| 38 | + 'Sofa.Component.Collision.Response.Contact', 'Sofa.Component.Constraint.Projective', |
| 39 | + 'Sofa.Component.IO.Mesh','Sofa.Component.LinearSolver.Iterative', |
| 40 | + 'Sofa.Component.Mapping.Linear', 'Sofa.Component.Mass', 'Sofa.Component.ODESolver.Backward', |
| 41 | + 'Sofa.Component.SolidMechanics.FEM.Elastic','Sofa.Component.StateContainer', |
| 42 | + 'Sofa.Component.Topology.Container.Dynamic','Sofa.Component.Visual', |
| 43 | + 'Sofa.GL.Component.Rendering3D','Sofa.Component.Constraint.Lagrangian.Correction', |
| 44 | + 'Sofa.Component.Constraint.Lagrangian.Solver', 'Sofa.Component.MechanicalLoad', |
| 45 | + 'Sofa.Component.LinearSolver.Direct','Sofa.Component.AnimationLoop' |
| 46 | + ]) |
| 47 | + |
| 48 | + root.addObject('FreeMotionAnimationLoop') |
| 49 | + # Constraint solver computing the constraint/contact forces, stored in the constraint space (normal , tangential_1, tangential_2) |
| 50 | + constraint_solver = root.addObject('GenericConstraintSolver', maxIterations=1000, tolerance=1e-6, computeConstraintForces=True) |
| 51 | + |
| 52 | + root.addObject('VisualStyle', displayFlags="showCollisionModels hideVisualModels showForceFields") |
| 53 | + root.addObject('CollisionPipeline', name="CollisionPipeline") |
| 54 | + root.addObject('BruteForceBroadPhase', name="BroadPhase") |
| 55 | + root.addObject('BVHNarrowPhase', name="NarrowPhase") |
| 56 | + root.addObject('DiscreteIntersection') |
| 57 | + root.addObject('CollisionResponse', name="CollisionResponse", response="FrictionContactConstraint", responseParams="mu=0.1") |
| 58 | + |
| 59 | + root.addObject('MeshOBJLoader', name="LiverSurface", filename="mesh/liver-smooth.obj") |
| 60 | + |
| 61 | + liver = root.addChild('Liver') |
| 62 | + liver.addObject('EulerImplicitSolver', name="cg_odesolver", rayleighStiffness=0.1, rayleighMass=0.1) |
| 63 | + liver.addObject('SparseLDLSolver', name="linear_solver") |
| 64 | + liver.addObject('MeshGmshLoader', name="meshLoader", filename="mesh/liver.msh") |
| 65 | + liver.addObject('TetrahedronSetTopologyContainer', name="topo", src="@meshLoader") |
| 66 | + # Liver MechanicalObject where the constraint/contact forces will be stored in the (x,y,z) coordinate system |
| 67 | + liverMO = liver.addObject('MechanicalObject', name="dofs", src="@meshLoader") |
| 68 | + liver.addObject('TetrahedronSetGeometryAlgorithms', template="Vec3d", name="GeomAlgo") |
| 69 | + liver.addObject('DiagonalMass', name="Mass", massDensity=1.0) |
| 70 | + liver.addObject('TetrahedralCorotationalFEMForceField', template="Vec3d", name="FEM", method="large", poissonRatio=0.3, youngModulus=3000, computeGlobalMatrix=False) |
| 71 | + liver.addObject('FixedProjectiveConstraint', name="FixedConstraint", indices="3 39 64") |
| 72 | + |
| 73 | + # Forcefield only used for visualization purposes (of the contact forces) |
| 74 | + CFF = liver.addObject('ConstantForceField', name="CFF", forces=[0,0,0], showArrowSize=10) |
| 75 | + liver.addObject('LinearSolverConstraintCorrection') |
| 76 | + |
| 77 | + visu = liver.addChild('Visu') |
| 78 | + visu.addObject('OglModel', name="VisualModel", src="@../../LiverSurface") |
| 79 | + visu.addObject('BarycentricMapping', name="VisualMapping", input="@../dofs", output="@VisualModel") |
| 80 | + |
| 81 | + surf = liver.addChild('Surf') |
| 82 | + surf.addObject('SphereLoader', name="sphereLoader", filename="mesh/liver.sph") |
| 83 | + surf.addObject('MechanicalObject', name="spheres", position="@sphereLoader.position") |
| 84 | + surf.addObject('SphereCollisionModel', name="CollisionModel", listRadius="@sphereLoader.listRadius") |
| 85 | + surf.addObject('BarycentricMapping', name="CollisionMapping", input="@../dofs", output="@spheres") |
| 86 | + |
| 87 | + |
| 88 | + particle = root.addChild('Particle') |
| 89 | + particle.addObject('EulerImplicitSolver') |
| 90 | + particle.addObject('CGLinearSolver', threshold='1e-09', tolerance='1e-09', iterations='200') |
| 91 | + # Particle MechanicalObject where the constraint/contact forces will be stored in the (x,y,z) coordinate system |
| 92 | + particleMO = particle.addObject('MechanicalObject', showObject=True, position=[-2, 10, 0, 0, 0, 0, 1], name=f'ParticleDoFs', template='Rigid3d') |
| 93 | + particle.addObject('UniformMass', totalMass=1) |
| 94 | + particle.addObject('ConstantForceField', name="CFF", totalForce=[0, -1, 0, 0, 0, 0] ) |
| 95 | + particle.addObject('SphereCollisionModel', name="SCM", radius=1.0 ) |
| 96 | + particle.addObject('UncoupledConstraintCorrection') |
| 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 | + forcefield_visu=CFF, |
| 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.forcefield_visu = kwargs.get("forcefield_visu") |
| 113 | + self.root_node = kwargs.get("root_node") |
| 114 | + |
| 115 | + |
| 116 | + def onAnimateEndEvent(self, event): |
| 117 | + |
| 118 | + lambda_vector = self.constraint_solver.constraintForces.value |
| 119 | + if(len(lambda_vector) > 0): |
| 120 | + print("Forces in the contact space (n, t1, t2) = "+str(lambda_vector)+" (at time = "+str(round(self.root_node.time.value,3))+")") |
| 121 | + |
| 122 | + self.forcefield_visu.forces.value = -self.soft_liver.getData("lambda").value |
| 123 | + visuScale = self.forcefield_visu.showArrowSize.value |
| 124 | + fact =8.0/np.max(lambda_vector) |
| 125 | + self.forcefield_visu.showArrowSize.value = fact |
| 126 | + else: |
| 127 | + self.forcefield_visu.forces.value = np.zeros((181,3)) |
| 128 | + |
| 129 | + |
| 130 | + |
| 131 | +# Function used only if this script is called from a python environment |
| 132 | +if __name__ == '__main__': |
| 133 | + main() |
0 commit comments