Skip to main content
`Ctrl`+`K`

# Changelog

All notable changes to `ovphysx` are documented in this file.

## [0.3] - 2026-03-31

### Breaking changes

- Settings API replaced with typed config system. The old string-based settings (`settings_keys`/`settings_values`/`settings_count` on `ovphysx_create_args`; `ovphysx_set_global_setting()`/`ovphysx_get_global_setting()`; Python `PhysX(settings=...)`, `set_setting()`, `get_setting()`; C++ `PhysX::setSetting()`/`getSetting()`) are all removed. Use instead: C — `ovphysx_config_entry_t` array on `config_entries`/`config_entry_count` with builders from `ovphysx_config.h` (`ovphysx_config_entry_num_threads()`, `_disable_contact_processing()`, etc.), runtime `ovphysx_set_global_config()` and typed getters `ovphysx_get_global_config_bool/int32/float/string()`. C++ — `PhysX::create(physx, entries, count)` with the same `ovphysx_config_entry_t` builders. Python — `PhysX(config=PhysXConfig(num_threads=4))` with the `PhysXConfig` dataclass, runtime `set_config_bool()`/`set_config_int32()` and `get_config_bool()`/`get_config_int32()`. Arbitrary Carbonite paths remain accessible via `ovphysx_config_entry_carbonite()` (C/C++) or `PhysXConfig(carbonite_overrides={...})` (Python).

- `OVPHYSX_CONFIG_CUDA_DEVICE` removed from `ovphysx_config_int32_t` and the `ovphysx_config_entry_cuda_device()` convenience builder. Use the `gpu_index` parameter on `ovphysx_create_args` (C) or `PhysX(gpu_index=...)` (Python) instead. Setting `/physics/cudaDevice` via `carbonite_overrides` now raises an error.

- Removed typed config entries for settings not in Physics Preferences: `OVPHYSX_CONFIG_PHYSX_DISPATCHER`, `OVPHYSX_CONFIG_OMNI_PVD_OUTPUT_ENABLED`, `OVPHYSX_CONFIG_UJITSO_COLLISION_COOKING` (bool); `OVPHYSX_CONFIG_PVD_RECORDING_DIRECTORY` (string). These settings remain accessible via the `carbonite_overrides` escape hatch.

- Log level enum reordered to ascending severity (matches ovrtx and industry convention). New values: `OVPHYSX_LOG_VERBOSE=0`, `OVPHYSX_LOG_INFO=1`, `OVPHYSX_LOG_WARNING=2` (unchanged), `OVPHYSX_LOG_ERROR=3`, `OVPHYSX_LOG_NONE=4`. Code using symbolic names (`OVPHYSX_LOG_WARNING`, `LogLevel.WARNING`) is unaffected. Code using raw integer values must update. Python `LogLevel` IntEnum updated to match.

- Error handling switched from inline error strings to thread-local `get_last_error()` query (matches ovrtx pattern). `ovphysx_result_t` and `ovphysx_enqueue_result_t` no longer have an `error` field. On failure, call `ovphysx_get_last_error()` on the same thread to retrieve the error message (valid until the next ovphysx API call on that thread). `ovphysx_op_error_t` struct removed; `ovphysx_op_wait_result_t` now carries `error_op_indices` (array of failed op indices) instead of `errors` (array of `ovphysx_op_error_t`). Call `ovphysx_get_last_op_error(op_index)` per failed index, then `ovphysx_destroy_wait_result(&result)` to free the array. Removed: `ovphysx_destroy_error()`, `ovphysx_destroy_errors()`. Added: `ovphysx_get_last_error()`, `ovphysx_get_last_op_error()`, `ovphysx_destroy_wait_result()`. Python API is unaffected (errors are raised as exceptions).

- `ContactEventHeader.stageId` type changed from `long` to `int64_t` for cross-platform ABI stability (`long` is 4 bytes on Windows, 8 on Linux; `int64_t` is 8 on both). This changes the struct layout on Windows. Bumps `IPhysxSimulation` to 5.0 and `IPhysicsSimulation` to 0.4 in ovruntime.

- `ovphysx_get_contact_report()` C signature changed: now returns typed pointers (`const ovphysx_contact_event_header_t**`, `const ovphysx_contact_point_t**`) instead of `const void**`, and gained 2 new parameters (`out_friction_anchors`, `out_num_friction_anchors`). Existing C callers must update the pointer types and append `, NULL, NULL` for the new friction anchor parameters. C++ callers using `PhysX::getContactReport()` are unaffected (new params default to `nullptr`).

- Removed `ovphysx_articulation_get_dof_count`, `ovphysx_articulation_get_body_count`, `ovphysx_articulation_get_joint_count`, `ovphysx_articulation_get_is_fixed_base`, `ovphysx_articulation_get_fixed_tendon_count`, `ovphysx_articulation_get_spatial_tendon_count`. Use the new `ovphysx_get_articulation_metadata(handle, binding, &meta)` instead. The Python `TensorBinding` properties (`dof_count`, `body_count`, etc.) are unchanged.

- `ovphysx_clone()` parameter `parent_positions_xyz` replaced with `parent_transforms` (7 floats per target: px, py, pz, qx, qy, qz, qw instead of 3). This allows specifying both position and rotation for clone parent prims. Quaternion convention: imaginary-first, matching the tensor API pose format. Identity rotation = (0, 0, 0, 1). Python: `parent_positions_xyz` parameter renamed to `parent_transforms` (7-tuples). Migration: replace `parent_positions_xyz=[x, y, z]` with `parent_transforms=[px, py, pz, 0, 0, 0, 1]` (append identity quaternion).

- Removed `ovphysx_set_clone_env_root()` (C) and `set_clone_env_root()` (Python). No longer needed — the first `clone()`, `simulate()`, or `warmup_gpu()` call triggers stage attach lazily. The API ordering simplifies to: `add_usd()` → `clone()` → `warmup_gpu()`/`step()`.

- `ovphysx_device_t` enum reordered: `OVPHYSX_DEVICE_AUTO = 0` (was 2) so that zero-initialized `ovphysx_create_args` defaults to AUTO instead of GPU. Code that passes raw integer device values must update (`0=AUTO, 1=GPU, 2=CPU`). Code using symbolic names or string device names is unaffected.

- `OVPHYSX_TENSOR_ARTICULATION_CORIOLIS_FORCE_F32` renamed to `TensorType.ARTICULATION_CORIOLIS_AND_CENTRIFUGAL_FORCE` (enum value 72 unchanged). The old name was misleading — the getter computes both Coriolis and centrifugal terms.

- Scene query enum constants disambiguated: `OVPHYSX_SCENE_QUERY_CLOSEST` → `OVPHYSX_SCENE_QUERY_MODE_CLOSEST` (and `_ANY`, `_ALL`); `OVPHYSX_SCENE_QUERY_SPHERE` → `OVPHYSX_SCENE_QUERY_GEOMETRY_SPHERE` (and `_BOX`, `_SHAPE`).

- Python: All `OVPHYSX_TENSOR_*_F32` bare-int constants removed. Use `TensorType.*` members instead (e.g. `TensorType.ARTICULATION_ROOT_POSE`).

- Python: `OVPHYSX_API_*`, `OVPHYSX_LOG_*`, `OVPHYSX_DEVICE_*` constants removed. Use `ApiStatus.*`, `LogLevel.*`, `DeviceType.*` IntEnum members instead.

- Python: `OVPHYSX_OP_INDEX_ALL` renamed to `OP_INDEX_ALL`.

- Python: `kDLCPU`, `kDLCUDA`, `kDLInt`, `kDLUInt`, `kDLFloat` removed from the public API. Use `DLDeviceType.kDLCPU` / `DLDataTypeCode.kDLFloat` etc. directly.

- Removed `ovphysx_finalize()`. Full Carbonite framework teardown now happens automatically when the last instance is destroyed via `ovphysx_destroy_instance()` (C/C++) or `release()` (Python). Migration: remove any `ovphysx_finalize()` calls — they are no longer needed or available.

### Added

- `OVPHYSX_CONFIG_SCENE_MULTI_GPU_MODE` (int32) config entry for `/physics/sceneMultiGPUMode`.

- `ConfigBool`, `ConfigInt32`, `ConfigFloat`, `ConfigString` IntEnums in Python `types.py` for typed config key access.

- `PhysXConfig` dataclass in Python `config.py` for typed initialization config.

- `ovphysx_articulation_metadata_t` struct (in `ovphysx_types.h`) — plain C struct with 6 scalar topology fields.

- `ovphysx_get_articulation_metadata()` — fills the struct in one call, one lock acquire, one stream fence. Result is automatically cached by the Python `TensorBinding`.

- Remote USD loading: `add_usd()` now accepts remote URIs (`omniverse://`, S3 via HTTPS, Azure Blob `https://`) in addition to local file paths. Powered by the Omniverse Client Library, loaded automatically at startup. Use HTTPS virtual-hosted S3 URLs (e.g. `https://bucket.s3.region.amazonaws.com/path/scene.usd`).

- `ovphysx_configure_s3()` / `configure_s3()` — configure S3 credentials (host, bucket, region, access key, secret key, optional session token) for loading USD scenes from S3 buckets.

- `ovphysx_configure_azure_sas()` / `configure_azure_sas()` — configure an Azure SAS token for loading USD scenes from Azure Blob Storage.

- TensorBindingsAPI: DOF property tensors — stiffness, damping, limits, max velocity, max force, armature, friction properties (read/write, indexed, masked).

- TensorBindingsAPI: body property tensors — mass, center-of-mass pose, inertia tensor (read/write, indexed, masked).

- TensorBindingsAPI: link acceleration tensor (`[N, L, 6]`, read-only) for linear + angular acceleration per link.

- TensorBindingsAPI: dynamics query tensors (read-only) — Jacobian, generalized mass matrix, Coriolis + centrifugal forces, gravity compensation forces, link incoming joint force.

- TensorBindingsAPI: DOF projected joint forces (read-only, `[N, D]`).

- TensorBindingsAPI: standalone rigid body properties — mass (`[N]`), inertia (`[N, 9]`), center-of-mass pose (`[N, 7]`), all read/write with indexed and masked write support.

- TensorBindingsAPI: articulation body inverse mass (`[N, L]`) and inverse inertia (`[N, L, 9]`), both read-only.

- TensorBindingsAPI: fixed tendon properties — stiffness, damping, limit stiffness, limits, rest length, offset (read/write, indexed, masked). Write uses read-back + batch setter internally since PhysX requires all tendon properties to be set together.

- TensorBindingsAPI: spatial tendon properties — stiffness (`[N,T]`), damping (`[N,T]`), limit stiffness (`[N,T]`), offset (`[N,T]`) (read/write, indexed, masked). Same read-back + batch setter pattern as fixed tendons.

- TensorBindingsAPI: shape-level tensor types — `RIGID_BODY_SHAPE_FRICTION_AND_RESTITUTION` (`[N,S,3]`), `RIGID_BODY_CONTACT_OFFSET` (`[N,S]`), `RIGID_BODY_REST_OFFSET` (`[N,S]`), and articulation equivalents (`ARTICULATION_SHAPE_FRICTION_AND_RESTITUTION`, `ARTICULATION_CONTACT_OFFSET`, `ARTICULATION_REST_OFFSET`). All support read, indexed write, and masked write.

- Articulation metadata name queries: `ovphysx_articulation_get_dof_names`, `get_body_names`, `get_joint_names` (variable-length string arrays; separate from scalar metadata). Scalar topology fields (`dof_count`, `body_count`, `joint_count`, `is_fixed_base`, `fixed_tendon_count`, `spatial_tendon_count`) are now returned in one call via `ovphysx_get_articulation_metadata()`.

- Contact binding API: `ovphysx_create_contact_binding` / `destroy` / `get_spec` / `read_net_forces` / `read_force_matrix` backed by `IRigidContactView`.

- Python `ContactBinding` class for reading contact forces via DLPack tensors.

- PhysX object interop: `ovphysx_get_physx_ptr(handle, prim_path, physx_type, &out_ptr)` returns raw PhysX SDK pointers (`void*`) by USD prim path and type enum (`ovphysx_physx_type_t`). Covers PxScene, PxRigidActor, PxArticulationLink, PxJoint, PxArticulationReducedCoordinate, PxShape, PxMaterial, and PxArticulationJointReducedCoordinate. The SDK now ships PhysX headers under `include/physx/` (`ovphysx_PHYSX_INCLUDE_DIR` in CMake). See the PhysX Interop tutorial and `tests/c_samples/physx_interop_cpp/` for a complete example using `setKinematicTarget()`.

- Python API: `PhysX.get_physx_ptr(prim_path, physx_type)` unified accessor.

- Python API: `PhysX.handle` read-only property exposing the raw `ovphysx_handle_t`.

- Contact report API: `ovphysx_get_contact_report()` exposes the Omni PhysX runtime’s per-step contact data as typed C struct pointers (`ovphysx_contact_event_header_t`, `ovphysx_contact_point_t`). Struct definitions and `ovphysx_friction_anchor_t` are in `ovphysx_types.h`. Optional friction anchor output params (`out_friction_anchors`, `out_num_friction_anchors`) — pass NULL to skip.

- Python API: `PhysX.get_contact_report()` returns a dict with ctypes arrays of `ContactEventHeader` and `ContactPoint` structs for zero-copy field access. Optional `include_friction_anchors=True` adds friction anchor data.

- Scene query API: `ovphysx_raycast()`, `ovphysx_sweep()`, `ovphysx_overlap()` — raycast, geometry sweep, and overlap queries against the physics scene. Supports CLOSEST/ANY/ALL hit modes with sphere, box, and arbitrary-shape (UsdGeomGPrim) geometry. Hit results stored in internal buffer (valid until next query).

- Python API: `PhysX.raycast()`, `PhysX.sweep()`, `PhysX.overlap()` scene query methods.

- `SceneQueryMode` and `SceneQueryGeometryType` IntEnums in Python `types.py`.

- Synchronous stepping APIs: `ovphysx_step_sync()` (C) / `PhysX.step_sync()` (Python) combines step + wait into a single call, bypassing the async event machinery for lower per-step overhead in synchronous use patterns. `ovphysx_step_n_sync()` / `PhysX.step_n_sync()` batches N steps in one call, saving (N-1) ctypes round-trips when using decimation > 1.

- New status code `OVPHYSX_API_BUFFER_TOO_SMALL = 6` added to `ovphysx_api_status_t`.

- CMake package config (`find_package(ovphysx)`) provides the imported target `ovphysx::ovphysx` and a Windows helper `ovphysx_copy_runtime_dlls()`.

- Visual sample using Rerun to visualize rigid body simulation (Rendering Handoff).

- Linux aarch64 support.

### Changed or fixed

- Carbonite host coexistence: ovphysx now detects when a host application (Kit, Isaac Sim) has already bootstrapped Carbonite and loaded the PhysX plugin stack. In this scenario, ovphysx skips its own plugin loading, USD preloading, and version checks, and instead acquires the host’s existing interfaces. Settings are applied with `setDefault*()` to avoid overwriting host configuration; device-related settings produce a warning on mismatch. The same ovphysx version works in both standalone and host-embedded environments without configuration changes.

- Physics scene is now parsed lazily on the first `simulate()` call instead of during `add_usd()`. This avoids GPU buffer corruption when `clone()` adds environments after the initial load. Correct usage sequence: `add_usd()` → `clone()` → `warmup_gpu()`/`step()`.

- A GPU and CUDA installation is no longer a requirement for using ovphysx with CPU-only simulation.

- Fixed a fatal TF_DEBUG crash when ovphysx and OVRTX co-loaded in Python due to a collision with USD libraries.

- Fixed various memory leaks across C, C++, and Python error-handling and cleanup paths.

- Contact binding read functions (`read_net_forces`, `read_force_matrix`) now validate dtype is float32 before dispatching.

- `ContactBinding.destroy()` now checks the result status and raises on failure; also validates parent SDK is still alive.

- `create_contact_binding` now validates `sensor_patterns` is non-empty, `filters_per_sensor >= 0`, and `filter_patterns` length matches `n_sensors * filters_per_sensor`.

- All new tensor type constants and `ContactBinding` are now exported from the `ovphysx` package `__init__.py`.

- Wheel metadata corrected: `Root-Is-Purelib: false`, OS classifiers set to Linux and Windows.

- Fixed doubled path bug in `ovphysxConfig.cmake` Windows DLL copy helper. Fixed shutdown crash where stepper tasks could access freed CUDA context (OMPE-81088).

- Fixed process-exit crashes (OMPE-84139) caused by non-deterministic DLL/SO unload ordering at process exit. `ovphysx_destroy_instance()` now performs full Carbonite framework teardown (OmniClient shutdown + plugin unload in dependency order) when the last instance is destroyed. No separate finalize call is needed – just call `release()` (Python) or `ovphysx_destroy_instance()` (C/C++).

- `ovphysx_remove_usd()` now validates `usd_handle` against the instance-owned handle map and returns `OVPHYSX_API_NOT_FOUND` for unknown handles (Python raises `ValueError: Failed to remove USD: USD handle not found`).

### Removed

- AGENTS.md is no longer shipped in the SDK or wheel (monorepo developer doc only).

- `ovphysx.pc` (pkg-config file) removed; CMake is the supported integration path.

- `hdStorm` (Hydra Storm renderer) removed from SDK; not needed for headless physics simulation.

## [0.2] - 2026-03-03

First released version. No changelog was maintained prior to v0.3.