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()
from SimRender.core import Player
# Create the player
player = Player(sync=False)
# Create 3D object --> see the dedicated section bellow
...
# Launch the rendering process
player.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
player.render()
# Close the rendering process
player.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()