Physics Scene and Simulation Configuration#

Every stage that ovphysx simulates needs a physics scene: a PhysicsScene prim that defines gravity and the PhysX solver configuration. This page explains how to author the scene and configure global simulation behavior (solver, iteration counts, GPU buffers, sleeping, stabilization) for scenes that ovphysx loads and steps.

This is the foundation the other Simulation Setup pages build on (Colliders, Rigid Bodies, Joints, Articulations, Deformables, Particles).

The code examples on this page are fragments, not complete files. Each USDA example shows the prims to author inside the stage’s defaultPrim hierarchy; where a later example repeats the physicsScene prim, it replaces the earlier definition instead of adding a second scene. Each Python example after Setting Up a USD Stage and a Physics Scene extends that section’s script and reuses its stage and scene variables. For a complete, CI-tested file, refer to the simple_physics_scene.usda reference used by Hello World.

How ovphysx Simulates a Scene#

ovphysx consumes pre-authored USD owned by the application through ovstage.

Before you step a simulation, confirm the following:

  • An ovphysx instance exists, created with ovphysx.PhysX() in Python or ovphysx_create_instance() in C.

  • The application has populated its scene into ovstage, and that scene contains at least one PhysicsScene prim.

  • The bundled codeless schemas are registered if the stage uses any Physx* or OmniPhysics* schema or API, as described in Physics Schemas.

A step then proceeds as follows:

  1. Seal the populated ordinal with advance_write_floor().

  2. Call attach_ovstage() with the sealed ordinal. Attach parses that ordinal and creates PhysX objects for the prims that carry physics schemas.

  3. Colliders that need cooking (convex hull, convex decomposition, SDF, triangle mesh) are cooked. Results are cached on disk so recomputation is usually avoided — refer to the cooked-collider cache (UJITSO) in the Developer Guide.

  4. Call step(dt) to advance the simulation by dt. step() is asynchronous: it enqueues the step and returns an op_index immediately. Subsequent in-stream ovphysx calls (such as tensor read() / write()) automatically wait for it, but to consume results outside the ovphysx stream you must synchronize — wait_op() on the returned index (or wait_all()). step_sync(dt) is a convenience that steps and waits in one call (the typical choice for RL and control loops). Refer to the Execution Model.

  5. Read results back explicitly — either with the ovstage output read API (the session read, ovphysx_read / PhysX.read) or the deprecated tensor bindings. Unlike Omni PhysX in Kit, ovphysx does not write simulation output back to the attached USD stage; the application owns writing state back to ovstage.

The pipeline is working when attach_ovstage() succeeds, step(dt) reports no error through wait_op() or wait_all(), and a read of the simulated state changes between steps. The clearest observable check is a dynamic body released above a static ground: its position along the gravity axis decreases over successive steps and then stops changing after the body rests on the ground.

The rest of this page is about authoring the PhysicsScene prim and the attributes that configure this pipeline.

Setting Up a USD Stage and a Physics Scene#

To simulate physics, a stage must have at least one PhysicsScene prim. The scene is authored with the core UsdPhysics schema (part of stock usd-core). PhysX-specific solver settings live on the codeless PhysxSceneAPI schema (refer to Physics Schemas).

Before you author the scene, install usd-core and confirm that ovphysx.codeless_schema_paths() resolves, because the PhysX schemas must be registered before the first stage is created. The scene is authored correctly when the stage opens without a schema warning, UsdPhysics.Scene.Get() returns a valid prim at the authored path, and the gravity attributes read back the values you set.

USDA#

The following example defines a minimal stage and scene in a .usda file:

#usda 1.0
(
    defaultPrim = "World"
    metersPerUnit = 1
    upAxis = "Z"
    kilogramsPerUnit = 1
)

def Xform "World"
{
    def PhysicsScene "physicsScene"
    {
        vector3f physics:gravityDirection = (0, 0, -1)
        float physics:gravityMagnitude = 9.81
    }
}

Python#

The following script builds the same stage and scene, and sets the units and up axis described in Units and Up Axis:

import ovphysx
from pxr import Plug, Usd, UsdGeom, UsdPhysics, Gf

# Register the bundled codeless PhysX schemas first: USD builds its schema
# registry on first access and never rebuilds it, so this must precede the
# first stage. Refer to Physics Schemas.
Plug.Registry().RegisterPlugins([str(p) for p in ovphysx.codeless_schema_paths()])

stage = Usd.Stage.CreateNew("scene.usda")

# Units and up axis.
UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.z)
UsdGeom.SetStageMetersPerUnit(stage, 1.0)
UsdPhysics.SetStageKilogramsPerUnit(stage, 1.0)

world = UsdGeom.Xform.Define(stage, "/World")

scene = UsdPhysics.Scene.Define(stage, "/World/physicsScene")
scene.CreateGravityDirectionAttr().Set(Gf.Vec3f(0.0, 0.0, -1.0))
scene.CreateGravityMagnitudeAttr().Set(9.81)

For a complete, CI-tested minimal scene (physics scene + static ground + dynamic bodies), refer to the simple_physics_scene.usda reference used by Hello World.

Units and Up Axis#

USD supports scaling of units through layer metadata. For correct simulation results, the units and up axis of your stage must match your content.

  • metersPerUnit sets the length unit (1 = meters). Keep all sizes, positions, and velocities in that unit.

  • upAxis is "Y" or "Z". Point physics:gravityDirection down the same axis (up axis Z -> gravity (0, 0, -1)).

  • kilogramsPerUnit (set through UsdPhysics.SetStageKilogramsPerUnit) sets the mass unit.

  • If no gravity is authored, ovphysx scales default and auto-computed values by the layer unit metadata. For example, at metersPerUnit = 0.01 (centimeter scale) the default gravity magnitude is 981.0 cm/s^2.

Angles are always in degrees. Every rotation-related physics parameter is in degrees, including drive stiffness (units of torque/degree for an angular drive).

Keep units consistent across all layers. Automatic reconciliation of unit scales between layers is not supported; author every layer and asset at the same scale.

Multiple Physics Scenes#

A stage can contain multiple PhysicsScene prims. PhysicsRigidBodyAPI and PhysicsCollisionAPI have a physics:simulationOwner relationship that assigns a body or collider to a specific scene. If it is not set, the first PhysicsScene found during traversal is used. Objects in separate physics scenes do not collide with each other.

def PhysicsScene "physicsScene0"
{
}

def PhysicsScene "physicsScene1"
{
}

def Cube "cube0" (
    prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysicsCollisionAPI"]
)
{
    rel physics:simulationOwner = </World/physicsScene0>
}

ovphysx steps all scenes together. Multiple PhysicsScene prims in one stage become separate PhysX scenes, but they all advance together on each step(); per-scene stepping is not surfaced. For isolated stages or truly independent parallel sims, use separate subprocesses (refer to the threading and single-attached-stage notes in the Developer Guide).

Physics Solver#

The solver iteratively resolves constraints (joints, contacts) to reach a physically plausible state without overlaps or disconnected joints. Two strategies are available, selected on PhysxSceneAPI:

  • TGS (Temporal Gauss-Seidel, the default): converges faster; can overshoot (objects gaining excess velocity) in complex collision scenarios. TGS benefits from physxScene:enableExternalForcesEveryIteration and needs only very few (around 1) velocity iterations.

  • PGS (Projected Gauss-Seidel): converges more slowly but is less prone to overshooting.

Iteration counts trade accuracy against cost. Position iterations keep bodies from overlapping; velocity iterations prevent bodies from picking up artificial velocity on interaction. Both are clamped per-scene with min/max attributes.

USDA#

The following example extends the physicsScene prim authored earlier with PhysxSceneAPI and explicit solver settings:

def PhysicsScene "physicsScene" (
    prepend apiSchemas = ["PhysxSceneAPI"]
)
{
    vector3f physics:gravityDirection = (0, 0, -1)
    float physics:gravityMagnitude = 9.81
    uniform token physxScene:solverType = "TGS"
    uniform uint physxScene:minPositionIterationCount = 4
    uniform uint physxScene:maxPositionIterationCount = 16
    uniform uint physxScene:minVelocityIterationCount = 1
    uniform uint physxScene:maxVelocityIterationCount = 1
}

Python for the Codeless PhysX Schema#

PhysX-specific attributes live on codeless schemas that have no typed Python class. Apply them by identifier and author the attributes generically. The following example extends the script in Setting Up a USD Stage and a Physics Scene, whose registration call already ran before the stage was created:

from pxr import Sdf

scene_prim = scene.GetPrim()
scene_prim.ApplyAPI("PhysxSceneAPI")
scene_prim.CreateAttribute("physxScene:solverType", Sdf.ValueTypeNames.Token).Set("TGS")
scene_prim.CreateAttribute("physxScene:maxPositionIterationCount", Sdf.ValueTypeNames.UInt).Set(16)

Refer to Physics Schemas for the full codeless-schema registration story, and Performance for guidance on clamping iteration counts in bulk/RL scenes.

PhysX GPU Memory Buffers#

When simulating on the GPU, PhysX allocates internal buffers at initialization. If a buffer overflows, the runtime reports an error and you must increase its capacity on PhysxSceneAPI. The most common one to tune is the found/lost pairs capacity:

def PhysicsScene "physicsScene" (
    prepend apiSchemas = ["PhysxSceneAPI"]
)
{
    uint physxScene:gpuFoundLostPairsCapacity = 10240
}

CPU and GPU Simulation#

GPU dynamics are enabled per scene in USD with physxScene:enableGPUDynamics (and a GPU broadphase through physxScene:broadphaseType = "GPU"):

def PhysicsScene "physicsScene" (
    prepend apiSchemas = ["PhysxSceneAPI"]
)
{
    bool physxScene:enableGPUDynamics = true
    uniform token physxScene:broadphaseType = "GPU"
}

ovphysx also exposes process- and instance-level controls that complement the per-scene USD settings:

  • ovphysx.PhysX.set_cpu_mode(True) forces a process-wide CPU-only mode (must be called before any instance is created; it cannot be reverted in that process).

  • PhysX(active_cuda_gpus="0,1") selects explicit CUDA ordinals and their supported scene-distribution mode. Any non-empty value takes precedence over PhysXConfig.scene_multi_gpu_mode; one ordinal disables distribution.

  • With active_cuda_gpus empty, PhysXConfig(scene_multi_gpu_mode=...) controls distribution of multiple PhysicsScene prims across GPUs.

GPU features (deformables, particles, SDF meshes, DirectGPU tensor workloads) require GPU dynamics. Refer to the GPU/CPU and determinism sections of the Developer Guide.

Sleeping#

Sleeping lets PhysX save computation when dynamic objects are at rest. An object that stops moving for a short time goes to sleep until it is woken — by the application (for example, applying a force or changing a joint drive target) or by another object colliding with it.

Each dynamic object type exposes sleep-related properties: for rigid bodies, physxRigidBody:sleepThreshold sets the mass-normalized kinetic-energy threshold below which the body can sleep (set it to 0 to keep a body always awake).

From the runtime, rigid-body tensor bindings (deprecated) expose explicit wake_up() / sleep() controls.

Sleeping and effective iteration counts (GPU). In a GPU simulation it is efficient to apply the same iteration count to every awake object — the maximum over all awake objects in the scene. If a high-iteration object goes to sleep, the effective iteration count for the remaining awake objects can drop, which can change their behavior (notably for deformables). Keep this in mind when mixing objects with very different iteration settings.

Stabilization#

Stabilization improves the settling behavior of rigid bodies in large-pile or high-interaction scenes by applying extra damping to slow-moving objects. When a body’s mass-normalized kinetic energy falls below its stabilization threshold, damping is applied so it settles faster.

Stabilization reduces momentum and can cause unphysical effects, so it is not recommended for robotics, industrial applications, or any simulation that needs precise interactions.

Enable it at the scene level, then set the threshold per rigid body:

def PhysicsScene "physicsScene" (
    prepend apiSchemas = ["PhysxSceneAPI"]
)
{
    bool physxScene:enableStabilization = true
}

def Cube "box" (
    prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysxRigidBodyAPI", "PhysicsCollisionAPI"]
)
{
    float physxRigidBody:stabilizationThreshold = 0.0001
}

Reading Simulation Output#

ovphysx does not write results back to the attached stage. To observe simulated state, read it explicitly:

  • ovstage output read — the session read (ovphysx_read / PhysX.read) of positions, orientations, velocities, and joint state.

  • Tensor bindings (deprecated) — bulk, DLPack-friendly read/write of poses, velocities, joint state, and more.