sofa-framework / sofa

Real-time multi-physics simulation with an emphasis on medical simulation.
https://www.sofa-framework.org
GNU Lesser General Public License v2.1
934 stars 312 forks source link

MeshSpringForceField Does Not Support Index-Based Spring Creation #5135

Closed antennae closed 1 day ago

antennae commented 1 day ago

Problem

Description MeshSpringForceField fails to create springs based on specified indices. Instead, it always derives the springs directly from the topology.

This behavior appears to have changed following PR #4649.

Previously:

Currently:

As a result, it is no longer possible to create springs based on indices using MeshSpringForceField. However, SOFA does not provide a warning or error when indices are specified but unused, which can lead to confusion.

Steps to reproduce Attempt to add springs using MeshSpringForceField by specifying indices and lengths.

Expected behavior Springs should be created using the specified indices and lengths.


Example

Context

Below are screenshots comparing the behavior of MeshSpringForceField and SpringForceField when trying to create a spring between points 1 and 3.

using MeshSpringForceField: Screenshot from 2024-11-21 16-28-44

using SpringForceField: Screenshot from 2024-11-21 16-28-34

Example Scene:

import Sofa

def createScene(rootNode):
    # required plugins:
    rootNode.addObject("RequiredPlugin", name="Sofa.Component.AnimationLoop")
    rootNode.addObject(
        "RequiredPlugin",
        name="Sofa.Component.Constraint.Lagrangian.Correction",
    )
    rootNode.addObject(
        "RequiredPlugin", name="Sofa.Component.Constraint.Lagrangian.Solver"
    )
    rootNode.addObject("RequiredPlugin", name="Sofa.Component.IO.Mesh")
    rootNode.addObject(
        "RequiredPlugin", name="Sofa.Component.LinearSolver.Direct"
    )
    rootNode.addObject(
        "RequiredPlugin", name="Sofa.Component.ODESolver.Backward"
    )
    rootNode.addObject(
        "RequiredPlugin", name="Sofa.Component.SolidMechanics.Spring"
    )
    rootNode.addObject("RequiredPlugin", name="Sofa.Component.StateContainer")
    rootNode.addObject(
        "RequiredPlugin", name="Sofa.Component.Topology.Container.Constant"
    )
    rootNode.findData("gravity").value = [0, 0, 0]
    rootNode.addObject(
        "VisualStyle",
        displayFlags="showVisualModels showBehaviorModels showCollisionModels \
            hideBoundingCollisionModels showForceFields \
                showInteractionForceFields hideWireframe",
    )
    rootNode.findData("dt").value = 0.01
    rootNode.addObject("FreeMotionAnimationLoop")
    rootNode.addObject(
        "GenericConstraintSolver",
        name="ConstraintSolver",
        tolerance=1e-10,
        maxIterations=2000,
        multithreading=True,
    )

    solver_node = rootNode.addChild("solver_node")
    solver_node.addObject(
        "EulerImplicitSolver",
        firstOrder=False,
        rayleighStiffness=0.05,
        rayleighMass=0.02,
    )
    solver_node.addObject(
        "SparseLDLSolver",
        name="Solver",
        template="CompressedRowSparseMatrixd",
    )
    solver_node.addObject(
        "GenericConstraintCorrection",
        name="ConstraintCorrection",
        linearSolver=solver_node.Solver.getLinkPath(),
    )
    solver_node.addObject(
        "MeshSTLLoader",
        name="SurfaceMeshLoader",
        filename="./test.stl",
    )
    solver_node.addObject(
        "MeshTopology", name="topology", src="@SurfaceMeshLoader"
    )
    solver_node.addObject(
        "MechanicalObject",
        name="states",
        showObject=True,
        showIndices=True,
        showIndicesScale=0.1,
    )
    solver_node.addObject(
        "MeshSpringForceField",
        name="spring",
        stiffness=1000,
        indices1=1,
        indices2=3,
    )
    # solver_node.addObject(
    #     "SpringForceField",
    #     name="spring",
    #     stiffness=1000,
    #     indices1=1,
    #     indices2=3,
    #     lengths=2.82843,
    # )

Example Mesh:

solid 
facet normal 0 0 1
 outer loop
  vertex -1 -1 0
  vertex 1 -1 0
  vertex 1 1 0
 endloop
endfacet
facet normal -0 0 1
 outer loop
  vertex -1 -1 0
  vertex 1 1 0
  vertex -1 1 0
 endloop
endfacet
endsolid 

Proposed fix

bakpaul commented 1 day ago

Hello,

It seems like the SpringForceField is doing what you expect from the MeshSpringForcefield. The MeshSpringForcefield, as its name states, is meant to add springs on edges based on the mesh. The mesh is its input data to create the springs. If you don't want to use the mesh to define the springs and want to define them by hand, then you should use the SpringForceField.

Maybe I didn't understand your need well, if so, please tell me why you can't use the SpringForceField and need to use the MeshSpringForcefield but without taking the mesh information into account ?

damienmarchal commented 1 day ago

Hello,

It seems like the SpringForceField is doing what you expect from the MeshSpringForcefield. The MeshSpringForcefield, as its name states, is meant to add springs on edges based on the mesh. The mesh is its input data to create the springs. If you don't want to use the mesh to define the springs and want to define them by hand, then you should use the SpringForceField.

Maybe I didn't understand your need well, if so, please tell me why you can't use the SpringForceField and need to use the MeshSpringForcefield but without taking the mesh information into account ?

I think the point is that the behavior changed on MeshSpringForceField between the old and new version without a warning to users.

antennae commented 1 day ago

Hi,

Yes, SpringForceField is doing what i expected and it works well.

I was actually working with a scene that was created in an older SOFA version, which creates springs using indices with MeshSpringForceField. And in the newer SOFA version, the behaviour of MeshSpringForceField changed and the scene doesn't work anymore. I have to do some research to find out why. So in my opinion, this behaviour change can be confusing for users working with older scenes and a clearer guidance could be very useful.

bakpaul commented 1 day ago

OK I get it, we can add a warning message in the init of MeshSpringForcefield to warn when the data is set. Thank you !

bakpaul commented 1 day ago

TEll me if that solves you issue : https://github.com/sofa-framework/sofa/pull/5136

antennae commented 1 day ago

Thanks for the updates! I just tested and it did solve my issue.