Skip to navigation

Add a Physics Backend

View as Markdown

Add a physics engine by implementing the backend interfaces below. See Backend architecture for factory dispatch and shared interfaces.

Prerequisites

Before starting, identify the engine’s lifecycle, native data ownership, and the assets and sensors it can support. The backend must provide a PhysicsManager implementation and follow the module layout expected by FactoryBase.

Create the backend package

Create an extension package using the backend name in its package path. For example, a backend named mybackend uses this layout:

source/isaaclab_mybackend/
└── isaaclab_mybackend/
├── __init__.py
├── physics/
│ ├── __init__.py # lazy_export()
│ ├── __init__.pyi # public exports
│ ├── mybackend_manager.py
│ └── mybackend_manager_cfg.py
├── assets/
│ ├── articulation/
│ │ ├── __init__.py
│ │ ├── __init__.pyi
│ │ ├── articulation.py
│ │ └── articulation_data.py
│ ├── rigid_object/
│ │ └── ...
│ ├── deformable_object/
│ │ └── ...
│ └── rigid_object_collection/
│ └── ...
├── sensors/
│ ├── contact_sensor/
│ └── ...
├── renderers/
│ └── ...
└── cloner/
└── ...

Implement the physics manager

The manager must expose a SceneDataBackend so SceneDataProvider can read the backend’s body transforms in the Warp-native format consumed by renderers and visualizers.

# isaaclab_mybackend/physics/mybackend_manager.py
from typing import ClassVar
from isaaclab.physics import PhysicsEvent, PhysicsManager
from isaaclab.scene_data import SceneDataBackend, SceneDataFormat
class MyBackendSceneDataBackend(SceneDataBackend):
def __init__(self):
self._scene_data = SceneDataFormat.Transform()
@property
def transforms(self) -> SceneDataFormat.Transform:
# Return current world-space body transforms as a Warp transformf array.
self._scene_data.transforms = ... # backend-native tensor view
return self._scene_data
@property
def transform_count(self) -> int:
...
@property
def transform_paths(self) -> list[str]:
# One prim path per transform; used by SceneDataProvider.create_mapping.
...
class MyBackendManager(PhysicsManager):
_scene_data_backend: ClassVar[MyBackendSceneDataBackend | None] = None
@classmethod
def initialize(cls, sim_context):
super().initialize(sim_context)
cls._scene_data_backend = MyBackendSceneDataBackend()
# Initialize the physics engine.
@classmethod
def get_scene_data_backend(cls) -> SceneDataBackend:
return cls._scene_data_backend
@classmethod
def step(cls):
# Advance simulation by one timestep.
...
@classmethod
def forward(cls):
# Update kinematics without stepping.
...
@classmethod
def reset(cls, soft=False):
if not soft:
cls.dispatch_event(PhysicsEvent.PHYSICS_READY)
# Reset simulation state.
@classmethod
def close(cls):
super().close()
# Clean up resources.

Define the physics configuration

Define a PhysicsCfg subclass that identifies the manager class and holds backend-specific settings:

# isaaclab_mybackend/physics/mybackend_manager_cfg.py
from isaaclab.physics import PhysicsCfg
from isaaclab.utils import configclass
@configclass
class MyBackendCfg(PhysicsCfg):
class_type = "{DIR}.mybackend_manager:MyBackendManager"
# Backend-specific settings here.

Register the backend key

FactoryBase does not derive a new backend key from an arbitrary physics manager class name. Add the manager prefix to the core selector in isaaclab.utils.backend_utils before factories can resolve assets and sensors for the backend:

# source/isaaclab/isaaclab/utils/backend_utils.py
@classmethod
def _get_backend(cls, *args, **kwargs) -> str:
from isaaclab.sim.simulation_context import SimulationContext
manager_name = SimulationContext.instance().physics_manager.__name__.lower()
if manager_name.startswith("mybackend"):
return "mybackend"
# Keep the existing newton, ovphysx, and physx cases below.

The existing _get_package_name() convention maps the mybackend key to the isaaclab_mybackend package used above. If the integration uses another package name, add an explicit case to _get_package_name() as well. FactoryBase.register() only caches the concrete class after this key and package mapping has selected and imported the backend module; calling it alone does not register a new physics-manager prefix.

Implement assets and sensors

Each supported asset or sensor extends the matching base class in isaaclab. The implementation class name must match the factory’s expected name. Use lazy_export() in package __init__.py files. Once the core backend-key mapping is in place, the factory imports these modules by package and module-path convention and caches their implementation classes.

# isaaclab_mybackend/assets/articulation/articulation.py
from isaaclab.assets.articulation import BaseArticulation
class Articulation(BaseArticulation):
def __init__(self, cfg):
super().__init__(cfg)
# Set up backend-specific simulation structures.

Validate backend discovery

FactoryBase maps isaaclab.assets.articulation to isaaclab_mybackend.assets.articulation after the registered mybackend key is selected from the active physics manager. Verify the configured discovery path with the following checklist:

  • Construct the backend configuration.
  • Initialize a minimal simulation with that configuration.
  • Instantiate one asset supported by the backend.
  • Step the simulation once.
  • Close the simulation cleanly.