Getting started

This light tutorial reviews the whole project API to create, update and render 3D objects from numerical simulation data.

Create a Viewer

Three viewer options are available depending on the requirements. The Viewer and the Player have exactly the same API, while the ViewerBatch is used to gather several Viewers in a single display window.

Viewer / Player

The Viewer is used to simply render a unique numerical simulation at runtime. By default, the simulation process and the rendering process are asynchronous, allowing to run the numerical simulation as fast as possible while rendering it’s current state in real time. It is possible to synchronize these processes to ensure that every single simulation step will be rendered.

The Player is used to animate a unique numerical simulation. It is very similar to the previous viewer, except that it is always synchronous and adds widgets in the display window to play / pause the simulation process and to navigate trough time steps. The storage of the past steps is automatically handled by the rendering process.

Note

Press b key to switch between backgrounds !

from SimRender.core import Viewer

# Create the viewer
viewer = Viewer(sync=False)

# Create 3D object --> see the dedicated section bellow
...

# Launch the rendering process
viewer.launch()

# Process any Python script
for _ in range(N):

    # Step of simulation, update 3D object --> see the dedicated section bellow
    ...

    # Render the current state of the simulation
    viewer.render()

# Close the rendering process
viewer.shutdown()

Batch

The ViewerBatch is used when several numerical simulation are running simultaneously and a viewer is needed for each. Instead of launching a rendering process per simulation - resulting in multiple display windows - the viewers can be launched as a batch to gather all the rendering sources in the same display window. A tab menu is created to easily switch between the simulation renderings.

from SimRender.core import Viewer, ViewerBatch

# Initialize the batch with the number of sources to get the batch keys
batch = ViewerBatch()
batch_keys = batch.start(nb_view=5)

# Create several simulations with several viewers normally
viewers = [Viewers(sync=False) for _ in range(5)]

# Create 3D object for each viewer normally
...

# Launch the viewers with the given batch keys
for viewer, key in zip(viewers, batch_keys):
    viewer.launch(batch_key=key)

# Process any Python script
for _ in range(N):

    # Step of simulations, update 3D object normally
    ...

    # Render the current state of the simulations
    for viewer in viewers:
        viewer.render()

# Close the viewers and stop the batch
for viewer in viewers:
    viewer.shutdown()
batch.stop()

Create and update 3D objects

The API to create and update 3D objects is exposed in the Viewer.objects variable. Each object has an index identifier (following the creation order) required by the update methods.

Create objects

Several object types can be created using add_mesh, add_points, add_arrows or add_text. Bellow are only the required variables, click on the respective button to get the detailed list of available options for an object.

from SimRender.core import Viewer

# Create the viewer
viewer = Viewer()

# Add a mesh to the viewer
idx_mesh = viewer.objects.add_mesh(positions=...,
                                   cells=...,
                                   **kwargs)

# Add a point cloud to the viewer
idx_points = viewer.objects.add_points(positions=...,
                                       **kwargs)

# Add a vector field to the viewer
idx_arrows = viewer.object.add_arrows(positions=...,
                                      vectors=...,
                                      **kwargs)

# Add a text to the viewer
idx_text = viewer.objects.add_text(content=...)

Update objects

To update the created objects, the respective methods (update_mesh, update_points, update_arrows or update_text) require the object index that was given following the creation order. Bellow are only the required variables, click on the respective button to get the detailed list of available options for an object.

# Update a mesh in the viewer
viewer.objects.update_mesh(object_id=idx_mesh,
                           positions=...,
                           **kwargs)

# Update a point cloud in the viewer
viewer.objects.update_points(object_id=idx_points,
                             positions=...,
                             **kwargs)

# Update a vector in the viewer
viewer.object.update_arrows(object_id=idx_arrows,
                            positions=...,
                            vectors=...,
                            **kwargs)

# Add a text to the viewer
viewer.objects.update_text(object_id=idx_text,
                           content=...)

Using SOFA simulations

Creating and updating 3D objects can be automated for SOFA numerical simulation:

  • either with automated updates of manually defined 3D objects;

  • either with automated creation and updates of 3D objects for some components detected in the scene graph.

Automated updates

A dedicated Viewer must be used to access the API to create 3D objects for SOFA. The methods to create and update objects is still available using the viewer.objects, with new methods called viewer.objects.add_sofa_<object>(). These create methods do no longer require a static value for each data fields, but a SOFA Data that the Factory will access at each call to the viewer.render() method to automatically update the 3D object.

# This is also working with the BatchViewer and the Player
from SimRender.sofa import Viewer
import Sofa


def create_scene(root: Sofa.Core.Node):
    """
    Define a SOFA scene graph
    """

    root.addObject('RequiredPlugin', ...)

    root.addObject('DefaultAnimationLoop')
    root.addObject('CollisionPipeline', ...)
    root.addObject('BruteForceBroadPhase', ...)
    root.addObject('BVHNarrowPhase', ...)
    root.addObject('DefaultContactManager', ...)

    root.addObject('MeshOBJLoader', ...)

    mecha = root.addChild('mecha')
    mecha.addObject('EulerImplicitSolver', ...)
    mecha.addObject('CGLinearSolver', ...)
    mecha.addObject('TetrahedronSetTopologyContainer', ...)
    mecha.addObject('TetrahedronSetGeometryAlgorithms', ...)
    mecha.addObject('MechanicalObject', ...)
    mecha.addObject('DiagonalMass', ...)
    mecha.addObject('TetrahedronFEMForceField', ...)
    mecha.addObject('FixedConstraint', ...)

    visu = mecha.addChild('visu')
    visu.addObject('OglModel', ...)
    visu.addObject('BarycentricMapping', ...)


if __name__ == '__main__':

    # SOFA: create and init the scene graph
    root = Sofa.Core.Node('root')
    create_scene(root)
    Sofa.Simulation.init(root)

    # SimRender: create the viewer, create objects and start the rendering
    viewer = Viewer(root_node=root)
    viewer.objects.add_sofa_mesh(positions_data=root.mecha.visu.ogl.position,
                                 cells_data=root.mecha.visu.ogl.triangles,
                                 **kwargs)
    viewer.objects.add_sofa_points(positions_data=root.mecha.mo.position,
                                   **kwargs)
    viewer.launch()

    # SOFA: run the time steps
    while viewer.is_open:
        Sofa.Simulation.animate(root, root.dt.value)
        # SimRender: update the rendering view, 3D objects are automatically updated
        viewer.render()

    # SimRender: close the rendering
    viewer.shutdown()

Automated scene graph detection

The viewer.objects also has an additional method to automatically create and update some SOFA components: update_mesh. The scene graph is explored to detect component types in a pre-defined list (soon extended):

Models

Components

Visual

OglModel

Behavior

FixedConstraint, MechanicalObject

ForceField

ConstantForceField

Collision

PointCollisionModel, LineCollisionModel, TriangleCollisionModel

Then, 3D objects are automatically created like in the section above to be automatically updated then.

# This is also working with the BatchViewer and the Player
from SimRender.sofa import Viewer
import Sofa


def create_scene(root: Sofa.Core.Node):
    ...


if __name__ == '__main__':

    # SOFA: create and init the scene graph
    root = Sofa.Core.Node('root')
    create_scene(root)
    Sofa.Simulation.init(root)

    # SimRender: create the viewer, explore scene graph and start the rendering
    viewer = Viewer(root_node=root)
    viewer.objects.add_scene_graph(visual_models=True,
                                   behavior_models=True,
                                   force_fields=True,
                                   collision_models=True)
    viewer.launch()

    # SOFA: run the time steps
    while viewer.is_open:
        Sofa.Simulation.animate(root, root.dt.value)
        # SimRender: update the rendering view
        viewer.render()

    # SimRender: close the rendering
    viewer.shutdown()