Tutorial 3: Coupling pySimBlocks with SOFA

This tutorial builds on Tutorial 1 and Tutorial 2. The linear plant is replaced with a SOFA scene so the controller interacts with a physics-based simulation.

Goals

The objective is to:

  • Configure the environment needed to run SOFA from pySimBlocks

  • Replace the plant with a SofaPlant block

  • Create a SOFA controller that exchanges signals with the block diagram

  • Run the coupled model in both execution modes

By the end of this tutorial, you will be able to connect a pySimBlocks control loop to a SOFA simulation.

Required Files

This tutorial uses a SOFA scene and mesh files that are provided for you. Download the archive below — it contains everything you need:

  • finger/Finger.py — the SOFA scene

  • finger/mesh/ — mesh assets required by the scene

  • finger/FingerController.py — the SOFA controller (also shown in full below)

  • project.yaml — the tutorial 2 project as a starting point

  • project_solution.yaml — the completed reference to compare against

Download tutorial_3_sofa.zip

If you have cloned the repository, the files are already in examples/tutorials/tutorial_3_sofa/.

Extract the archive so that finger/ and project.yaml sit in the same folder.

System Description

We build the same closed-loop structure as in the previous tutorials:

  • A constant reference

  • A PI controller

  • A SOFA simulation of a tendon-driven finger

Block diagram

The control input is the cable actuation, and the measured output is the vertical fingertip position.

SOFA scene

Prerequisites

Before coupling pySimBlocks with SOFA, make sure:

  • SOFA is installed

  • Python can import Sofa

  • runSofa is available through SOFA_ROOT

pySimBlocks has been tested with SOFA v24.06 and later.

Linux and macOS

export SOFA_ROOT=/path/to/your/sofa

# Binary release:
export PYTHONPATH=$SOFA_ROOT/plugins/SofaPython3/lib/python3/site-packages:$PYTHONPATH

# Built from source:
# export PYTHONPATH=$SOFA_ROOT/lib/python3/site-packages:$PYTHONPATH

Windows

$env:SOFA_ROOT = "C:\path\to\your\sofa"

# Binary release:
$env:PYTHONPATH = "$env:SOFA_ROOT\plugins\SofaPython3\lib\python3\site-packages;$env:PYTHONPATH"

# Built from source:
# $env:PYTHONPATH = "$env:SOFA_ROOT\lib\python3\site-packages;$env:PYTHONPATH"

Verify the Setup

Check that Python can import SOFA:

import Sofa
import SofaRuntime

Then verify that runSofa is available:

  • Linux and macOS: $SOFA_ROOT/bin/runSofa

  • Windows: $env:SOFA_ROOT\bin\runSofa.exe

SOFA Controller Contract

To exchange data between the diagram and the SOFA scene, subclass SofaPysimBlocksController from pySimBlocks.blocks.systems.sofa.

Your controller must define:

  • self.project_yaml — path to the project.yaml file

  • self.inputs — dict of signals received from pySimBlocks (keys must match input_keys on the block)

  • self.outputs — dict of signals sent back to pySimBlocks (keys must match output_keys on the block)

It must implement:

  • set_inputs() — apply self.inputs values to the SOFA scene

  • get_outputs() — read SOFA state and write results into self.outputs

Example Controller

The tutorial example uses the following controller:

examples/tutorials/tutorial_3_sofa/finger/FingerController.py
from pathlib import Path

import numpy as np

from pySimBlocks.blocks.systems.sofa import SofaPysimBlocksController

BASE_DIR = Path(__file__).resolve().parent


class FingerController(SofaPysimBlocksController):

    def __init__(self, actuator, mo, tip_index=121, name="FingerController"):
        super().__init__(name=name)
        self.project_yaml = str((BASE_DIR / '../project.yaml').resolve())

        self.mo = mo
        self.actuator = actuator
        self.tip_index = tip_index
        self.verbose = False # Set to True to print debug information at each step

        # Inputs & outputs dictionaries
        self.inputs = { "cable": None }
        self.outputs = { "tip": None, "measure": None }


    def get_outputs(self):
        tip = self.mo.position[self.tip_index].copy()
        self.outputs["tip"] = np.asarray(tip).reshape(-1, 1)
        self.outputs["measure"] = np.asarray(tip[1]).reshape(-1, 1)

    def set_inputs(self):
        val = self.inputs["cable"]
        if val is None:
            raise ValueError("Input 'cable' is not set")
        self.actuator.value = [val.item()]

The scene must instantiate that controller and return it from createScene(...) alongside the root node.

Configure the SofaPlant Block

In the GUI, replace the LinearStateSpace block from the previous tutorials with a SofaPlant block from the Systems category.

SofaPlant block

Set the block parameters as follows:

Parameter

Value

scene_file

finger/Finger.py

input_keys

["cable"]

output_keys

["measure"]

slider_params

{"ref.value": [-10.0, 50.0], "PID.Kp": [0.01, 3.0], "PID.Ki": [0.01, 3.0]}

The key names must match the dictionaries declared in the SOFA controller.

Note:: Time step constraint
The SOFA scene dt (set in createScene via rootNode.dt) must be a positive integer divisor of the SofaPlant sample time. If no sample_time is set on the block, the global simulation dt is used. pySimBlocks enforces this at startup and raises an error if the constraint is not satisfied. Example: if rootNode.dt = 0.01 in your scene, valid block sample times are 0.01, 0.02, 0.05, etc.

Execution Modes

pySimBlocks as Master

In this mode, pySimBlocks drives the clock. The SofaPlant block runs SOFA headlessly and advances it by one step at each control iteration.

pySimBlocks master loop

Run the model as you would in Tutorial 2, either from the GUI or from the exported Python runner.

pySimBlocks master run

SOFA as Master

In this mode, SOFA drives the clock and the controller triggers the pySimBlocks diagram during the SOFA animation loop.

SOFA master loop

Open the SOFA panel from the toolbar and click runSofa to launch the scene with the SOFA GUI.

SOFA master run

Live Sliders and Plots

If your SOFA installation includes the Compliance Robotics GUI, the parameters declared in slider_params become live sliders in SOFA, and the plots defined in the pySimBlocks project are displayed live in the same window.

Enhanced SOFA GUI

This makes it possible to tune ref.value, PID.Kp, and PID.Ki while the simulation is running.

Try It Yourself

Experiment with the coupled model to better understand the workflow:

  • Modify Kp and Ki and compare the tracking behavior

  • Change the reference and observe the fingertip response

  • Run the same project with both execution modes

  • If available, use the live SOFA sliders and plots for real-time tuning