Changelog#

All notable changes to ovphysx are documented in this file.

[0.4] - 2026-05-27#

Breaking Changes#

  • gpu_index replaced by active_cuda_gpus on ovphysx_create_args. The int32_t gpu_index field is removed. Use ovphysx_string_t active_cuda_gpus instead: a comma-separated string of CUDA device ordinals. Single-GPU usage: args.active_cuda_gpus = ovphysx_cstr("2") (C), PhysX(active_cuda_gpus="2") (Python), createArgs.setActiveCudaGpus("2") (C++). Default (empty) selects GPU 0, matching the old gpu_index=0 default. Multi-GPU patterns "0,1,...,N-1" (all GPUs, round-robin) and "1,2,...,N-1" (all except first) are now supported. The C++ CreateArgs::setGpuIndex(int32_t) is replaced by setActiveCudaGpus(const std::string&).

  • Pre-loaded Carbonite PhysX stacks are rejected. ovphysx can share a process with other OV libraries through namespaced USD reuse, but it no longer reuses a pre-existing omni.physx.plugin / IPhysxSimulation stack. If another Carbonite user already registered PhysX plugins before ovphysx_create_instance(), creation fails with a clear error. Conflicting pre-set /physics/cudaDevice or /physics/suppressReadback values now fail instead of being silently accepted.

  • DirectGPU mode is now opt-in instead of auto-enabled for GPU instances. Previously, ovphysx_create_instance(device=GPU) unconditionally set /physics/suppressReadback=true and /physics/suppressFabricUpdate=true (via two paths: a startup-time CarboniteLoader::setStartupSuppressReadback write before PhysX plugins loaded, and a per-instance write at GPU bootstrap), which routes PhysX into eENABLE_DIRECT_GPU_API mode. DirectGPU is incompatible with contact modification (per PhysX SDK guidance: contact-modify requires a CPU roundtrip), so any scene relying on eMODIFY_CONTACTS – including the surface-velocity / conveyor pattern, custom contact callbacks, and contact-shaping filter rules – silently failed to form kinematic-vs-dynamic broadphase pairs in GPU mode. Both write paths are removed: ovphysx no longer touches /physics/suppressReadback or /physics/suppressFabricUpdate at any point in its lifecycle. The previously-public CarboniteLoader::setStartupSuppressReadback static method is removed (internal API; no public callers). Hosts that want DirectGPU’s tensor-pipeline performance (e.g. Isaac Lab) opt in by either (a) setting /physics/suppressReadback=true via Carbonite settings before ovphysx_create_instance if they have direct ISettings access, or (b) passing ovphysx_config_entry_carbonite("/physics/suppressReadback", "true") in create_args.config_entries (Python: PhysXConfig(carbonite_overrides={"/physics/suppressReadback": True})). At startup ovphysx now logs a [CarboniteLoader] /physics/suppressReadback=... INFO line so misconfigurations are visible. See ovphysx_create_args in include/ovphysx/ovphysx_types.h for the full trade-off documentation. Migration: GPU users who relied on suppressReadback being auto-enabled must set it explicitly via one of the routes above. Perf note: DirectGPU is the faster simulation path for tensor-pipeline workloads, so existing GPU consumers will see slower per-step times until they opt in – the change here is correctness (contact-modify scenes now work in GPU mode), not a perf regression of the new default. Users who need contact-modify in GPU mode now get correct behavior with no code change.

  • ovphysx’s Python import now auto-registers ovphysx’s namespaced USD schema/plugin path by appending it to OV_PXR_PLUGINPATH_2511 once at module load. Eliminates a class of silent-schema-drop bugs in mixed-process apps (import ovphysx; import ovrtx or vice versa) where USD’s PlugRegistry is populated lazily on first stage open and never re-scans, so any subsystem that didn’t get its plugin path published before that moment has its applied schemas silently dropped. The implicit registration in CarboniteLoader continues to cover the ovphysx-only-in-process case; this Python-side change is purely additive. The auto-call is pure-Python (env-var append only) and does not trigger native loading – libcarb / USD / _bindings remain deferred to first native-attribute access (PhysX, ContactBinding, …). Failures (e.g. partially-installed checkout with no plugins/usd directory) are surfaced as a logged warning rather than raised so import ovphysx does not hard-fail. The existing public register_schema_paths() is unchanged and remains idempotent (already-called guard); apps that called it explicitly continue to work and the explicit call becomes a cheap no-op on success. C/C++ apps are unaffected – still use ovphysx_register_schema_paths() explicitly. Migration: none required. Apps that need an env-var-pure import can os.environ.pop("OV_PXR_PLUGINPATH_2511") after import ovphysx.

Added#

  • Clone events from ovstage_clone_subtree() are consumed by ovphysx during step ingest. When an ovstage Stage is attached, physx.step() consumes OVSTAGE_EVENT_CLONE events surfaced by ovstage_query_changes() and drives PhysX-side replication via the clone plugin — same plugin the existing ovphysx_clone() entrypoint calls, just routed through ovstage instead of an explicit C / Python call. The standalone clone APIs (ovphysx_clone() C, PhysX::clone() C++, PhysX.clone() Python) remain fully supported for callers that don’t have an attached Stage or that want the async multi-target convenience; both paths funnel into the same ovphysx_clone_replicate_internal helper. Known limitation: clones that arrive after the bridge’s first successful publish_outbound (i.e. once ensure_publish_initialized has bound a non-zero RIGID_BODY_POSE prim list) simulate on the PhysX side but their poses are NOT published back to ovstage — the publish prim list is frozen at first publish. Issue a [OvstageBridge] clone-after-publish: warning per affected target identifies this case in logs. Workarounds: clone everything before the first non-empty physx.step(), or detach + re-attach the stage to rebuild the publish bindings. Refresh-on-clone is tracked as a follow-up because it requires ovstage_unregister_consumer + re-registration through the bridge state machine.

  • ovphysx_attach_stage() / ovphysx_detach_stage() for ovstage consumer integration. Pair an ovstage_instance_t* with an ovphysx instance and physx.step() becomes the canonical “consume control attributes from ovstage and publish poses back” step: the OvstageBridge pulls dirty drive:force, physics:mass, drive:velocity, drive:position_target (Tensor route), physxSurfaceVelocity:surfaceVelocity, physics:gravityMagnitude, physics:gravityDirection (Fabric route), and clone events (ovstage_query_changes) ahead of integrate; after fetchResults it publishes RIGID_BODY_POSE as xformOp:transform (float64 row-major mat4) to ovstage’s output-buffer contract. Init-only attach (rejects re-attach with OVPHYSX_API_ERROR); idempotent detach. Python: PhysX.attach_stage(stage) auto-unwraps via Stage.handle().

  • Cross-device staged reads AND writes in ovphysx_read_tensor_binding / ovphysx_write_tensor_binding / ovphysx_write_tensor_binding_masked. A CPU-declared src/dst/mask/index tensor now works against a GPU-bound binding, and a GPU-declared tensor works against a CPU-bound binding: the call transparently allocates a staging buffer on the binding’s device (or host) and routes through IOptionalCuda::memcpyDtoH / memcpyHtoD. Reads stage-and-copy after the simView read; writes copy-and-stage before the simView write. Same-device combinations are unchanged. Cross-GPU (different ordinals) still returns OVPHYSX_API_DEVICE_MISMATCH.

  • Schema path pre-registration API for multi-subsystem USD processes. New C API ovphysx_register_schema_paths() and Python helper ovphysx.register_schema_paths() append ovphysx’s namespaced USD schema/plugin root to OV_PXR_PLUGINPATH_2511 before ovphysx initialization. Use this with peer subsystem equivalents such as ovrtx_register_schema_paths() before the first USD stage open or schema-registry access.

  • Safer behavior when loading into a process with another Carbonite-owning library. Previously, ovphysx would blindly load its own USD-dependent plugin stack on top of any framework already bootstrapped in the process. loadUsdDependentPluginsusdrt::population::IUtils-availability guard is now narrowed to fire only in full-host mode (Kit / Isaac Sim, i.e. IPhysxSimulation already registered) rather than any-usdrt-user, so partial hosts that provide IUtils but not the physics-schema plugins now trigger ovphysx’s own omni.usdphysics.plugin load instead of silently skipping it. In-process coexistence remains subject to ABI compatibility of the shared deps that both libraries link against (Fabric interface version, Carbonite version, USD build-package); when those don’t line up, the new drift diagnostics (see OVPHYSX_COEXIST_DIAGNOSTICS and config.toml build_package below, plus the refuse-branch in CarboniteLoader::initialize() under Changed or Fixed) convert the failure into a named, actionable error instead of a silent plugin-resolution cascade.

  • OVPHYSX_COEXIST_DIAGNOSTICS=1 diagnostic env var. Set to enable stderr-only diagnostic logging of the plugin-load and Fabric-acquire paths inside ovphysx_create_instance – useful when triaging a “Dependency: [omni::physics::schema::IUsdPhysics v1.1] failed to be resolved” cascade or “Fabric interfaces unavailable” error in a coexistence context. Logs the Framework pointer, plugin-registry contents, omni.physicsschema.plugin registration state, and the Fabric interface-version list as seen by ovphysx. Unset: no behavior change.

  • OVPHYSX_COEXIST_REFUSE=1 opt-out env var. Restores the legacy fail-fast-at-load behavior for users who would rather not attempt coexistence with another Carbonite-owning library. With the default coexistence flip (see Changed or Fixed below), OVPHYSX_COEXIST_REFUSE=1 is the explicit off-ramp: ovphysx detects a foreign Carbonite framework and refuses to initialize with a sharp error message instead of proceeding. Default unset: ovphysx proceeds with coexistence and lets per-phase interface probes name any version skew at the failing plugin.

  • Per-phase plugin-interface probes after loadPlugins(). CarboniteLoader::initialize() now runs tryAcquireInterface<> against the primary interface of each plugin loaded in the foundation and early-PhysX phases (carb.dictionary, carb.settings, carb.tokens, carb.tasking, carb.filesystem, omni.physx.foundation). If any returns null – because a foreign-host plugin advertises an incompatible major/minor or because the plugin failed to load – ovphysx fails fast with a single error line that names every plugin that did not satisfy ovphysx’s expected interface version. Standalone ovphysx is unaffected. The probe converts what was previously an opaque nullptr cascade two layers down into a named, actionable error at load time, mirroring the existing UsdVersionCheck pattern for USD.

  • USD build-package drift diagnostic in config.toml. config.toml now records the exact USD packman package identifier ovphysx was linked against (e.g. build_package = "0.25.11.kit.2-gl.19811", auto-populated at build time from the resolved _build/target-deps/usd/release symlink). At runtime, UsdVersionCheck resolves the loaded USD’s canonical path, extracts its packman id, and compares. If the two differ – e.g. ovphysx built against 0.25.11.kit.2 but another library preloaded 0.25.11.kit.1 – a single warning line names both ids and points at Carbonite-plugin-resolution failure as the probable downstream symptom. Same-build case is silent.

  • New --devschema flag forwards to ovruntime for local physics schema builds.

  • Multi-GPU scene distribution. Pass active_cuda_gpus="0,1,2" (all N GPUs) or active_cuda_gpus="1,2" (all except GPU 0) to distribute scenes round-robin across the specified devices. Internally maps to /physics/sceneMultiGPUMode = eAll or eSkipFirst. Other patterns (e.g. "0,2" on a 4-GPU machine) return OVPHYSX_API_INVALID_ARGUMENT with a descriptive error; arbitrary subset support can be added in a future release without API changes.

  • TensorBindingsAPI: standalone rigid body read-only queries - RIGID_BODY_ACCELERATION ([N,6]), RIGID_BODY_INV_MASS ([N]), and RIGID_BODY_INV_INERTIA ([N,9]). The enum values use previously unused slots after RIGID_BODY_WRENCH; existing tensor enum values were not reordered.

  • Articulation kinematic update API. New C ovphysx_update_articulations_kinematic(), Python PhysX.update_articulations_kinematic(), and experimental C++ PhysX::updateArticulationsKinematic() recompute articulation link poses from current DOF positions. In GPU mode the first call may perform the standard DirectGPU warmup step; after warmup, the FK refresh does not run a normal simulation step or collision/contact work.

  • TensorBinding row metadata for tensor bindings. Python TensorBinding.prim_paths and C ovphysx_tensor_binding_get_prim_paths() return the resolved USD prim path for each rigid-body tensor row and the articulation root prim path for each articulation row.

  • Python tensor-binding empty-match guard. PhysX.create_tensor_binding(..., raise_if_empty=True) now raises ValueError when the binding would match zero prims. The default remains False, preserving zero-count bindings for optional scene elements.

  • Python binding lifetime warnings. TensorBinding and ContactBinding now emit ResourceWarning if garbage collection cleans them up without an explicit destroy() or context-manager exit. This does not affect normal read/write paths; create bindings once outside simulation loops and reuse or destroy them explicitly.

  • ContactBinding detailed contact/friction reads. Added sensor_paths, filter_paths, max_contact_data_count, C ovphysx_contact_binding_get_sensor_paths(), ovphysx_contact_binding_get_filter_paths(), ovphysx_get_contact_binding_capacity(), ovphysx_read_contact_data(), ovphysx_read_friction_data(), and matching Python methods. The API uses flat [C,*] buffers with [S,F] count/start-index tensors. Detailed reads require filtered bindings (filters_per_sensor > 0); unfiltered bindings remain valid for aggregate net-force reads.

  • Experimental C++ CreateArgs type and PhysX::create(out, CreateArgs). CreateArgs is a safe C++ wrapper for ovphysx_create_args that default-constructs to OVPHYSX_CREATE_ARGS_DEFAULT and exposes setters for device, active_cuda_gpus, bundled_deps_path, and config entries. Replaces the previous create(PhysX&, config_entries, count) overload.

  • OmniPVD recording support via typed config. Two new config entries enable .ovd recording: omnipvd_output_enabled (bool) and omnipvd_ovd_recording_directory (string). Both must be set at instance creation via PhysXConfig (Python) or config_entries on ovphysx_create_args (C/C++). These entries were previously available only via carbonite_overrides; they now have first-class typed fields. See OmniPVD Recording tutorial.

Changed or Fixed#

  • ContactBinding device-mismatch errors now explain the DirectGPU opt-in. When a CPU-backed ContactBinding receives a CUDA output tensor, the error now points out that device="gpu" enables GPU dynamics only and that TensorAPI/ContactBinding CUDA views require /physics/suppressReadback=true before instance creation. Behavior is unchanged.

  • ovphysx startup now appends its namespaced USD schema path when OV_PXR_PLUGINPATH_2511 is already set. Previously CarboniteLoader only set the variable when it was empty, so pre-existing ovrtx or application paths could prevent ovphysx’s own schema root from being published. Startup now shares the same append/dedupe logic as ovphysx_register_schema_paths() and does not modify PXR_PLUGINPATH_NAME. Startup and explicit pre-registration fail fast if ovphysx cannot find an existing plugins/usd directory, rather than publishing a missing path that USD silently ignores.

  • Improved automatic GPU selection on multi-GPU systems. When the physics device is not set explicitly, ovphysx now prefers the GPU already in use by the renderer, and otherwise picks the discrete GPU with the most memory. Previously device 0 was always chosen, which could result in physics running on a different GPU than the renderer on multi-GPU workstations. Users on single-GPU machines are unaffected; users on multi-GPU systems may see a different device selected than before.

  • ovphysx_clone() now preserves xformOp:scale from source prims. Previously, clones of prims with non-unit scale appeared at incorrect world-space size. The fix reads the source’s scale (supporting both double3 and float3 attributes), writes it to each target, includes it in xformOpOrder, and incorporates it into the localMatrix.

  • Contact binding now matches runtime-cloned sensor bodies. Runtime clones created with ovphysx_clone() can exist only in Fabric/usdrt, where copied USD API-schema metadata may not be visible. Contact binding now keeps the strict PhysxContactReportAPI requirement for real USD prims while allowing clone-only runtime paths to validate through their PhysX actor pointer, so cloned environments produce the same contact-binding rows as rigid-body tensor bindings.

  • ovphysx_step_sync() now calls ensure_physics_attached() before stepping, matching the async ovphysx_step() path. Previously, the first step_sync after add_usd would run against an unattached stage — features like OmniPVD recording finalization silently failed.

  • Config entries are now applied before PhysX plugin loading so that settings read during createPhysics() (e.g., OmniPVD recording) are in place at initialization time.

  • TensorBindingsAPI shape property tensors now accept CPU buffers in GPU simulations. Rigid-body and articulation material/contact/rest-offset tensors are PhysX parameter tensors and remain CPU-resident even when state tensors use CUDA buffers.

  • ovphysx wheel bootstrap no longer sets PYTHONHOME. Fixes mis-resolution of sys.prefix in child processes that spawn venv interpreters (Windows; e.g. rerun.io).

  • Unified build flags. build.sh / build.bat now accept the same flags as ovruntime: -c/--clean, -x/--rebuild, -d/--debug, -r/--release (default), -t/--target <name>, -g/--generate. Note: default is now release-only (was debug+release); -t is now --target <name> (use --test-venv for the old test-venv behavior).

  • create_articulation_view() and create_rigid_body_view() pattern matching extended to reach nested bodies. Patterns now support ** for recursive descent and — by default — match a named final component against descendants at any depth so patterns like /World/envs/env_*/Robot/<name> keep working on deeper hierarchies. Views dedup by backing PhysX pointer. Set /physics/tensors/recursiveLeafPatternMatch to false to restore strict per-level matching. Migration: existing patterns may pick up additional prims at deeper levels. If that changes behavior, either set the Carbonite flag to false or switch to explicit absolute paths.

  • /ovphysx/latest/ on GitHub Pages now serves the docs directly instead of redirecting to /ovphysx/<version>/. This fixes deep links such as the PyPI Changelog URL (/latest/changelog.html) which previously 404’d. Bookmarks to /latest/ continue to work; bookmarks to versioned URLs are unchanged.

Removed#

  • Environment variables OVPHYSX_ROOT, OVPHYSX_BIN_DIR, OVPHYSX_PLUGINS_DIR removed. All runtime paths are now derived from OVPHYSX_LIB (the single dev-mode override for the shared library path). Migration: replace any of the removed env vars with OVPHYSX_LIB pointing at the shared library.

  • ovphysx_set_shutting_down() removed. Full teardown now happens automatically when the last instance is destroyed via ovphysx_destroy_instance() (C/C++) or release() (Python). Migration: remove any ovphysx_set_shutting_down() calls.

  • Legacy Python TensorAPI compatibility removed. ovphysx.tensors is no longer shipped or exported. Migration: replace ovphysx.tensors imports with TensorBindingsAPI: use physx.create_tensor_binding() to create bindings, then binding.read() / binding.write() for data access.

[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. Migration: C — use ovphysx_config_entry_t array on config_entries/config_entry_count with builders from ovphysx_config.h, runtime ovphysx_set_global_config() and typed getters ovphysx_get_global_config_bool/int32/float/string(). Python — use PhysX(config=PhysXConfig(num_threads=4)), runtime set_config_bool()/set_config_int32() and matching getters. 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. Migration: use active_cuda_gpus on ovphysx_create_args (C) or PhysX(active_cuda_gpus=...) (Python). 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). Migration: these settings remain accessible via carbonite_overrides.

  • 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. Migration: code using symbolic names is unaffected; code using raw integer values must update.

  • Error handling switched from inline error strings to thread-local get_last_error() query. ovphysx_result_t and ovphysx_enqueue_result_t no longer have an error field. Migration: 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). For wait_op, iterate error_op_indices and call ovphysx_get_last_op_error() per failed index, then ovphysx_destroy_wait_result(&result). Removed: ovphysx_destroy_error(), ovphysx_destroy_errors(). Python API is unaffected (errors are raised as exceptions).

  • ContactEventHeader.stageId type changed from long to int64_t for cross-platform ABI stability. This changes the struct layout on Windows.

  • 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). Migration: update pointer types and append , NULL, NULL for friction anchor parameters. C++ callers are unaffected (new params default to nullptr).

  • Removed ovphysx_articulation_get_dof_count, _body_count, _joint_count, _is_fixed_base, _fixed_tendon_count, _spatial_tendon_count. Migration: use ovphysx_get_articulation_metadata(handle, binding, &meta). Python TensorBinding properties are unchanged.

  • ovphysx_clone() parameter parent_positions_xyz replaced with parent_transforms (7 floats per target: px, py, pz, qx, qy, qz, qw). 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). Migration: no replacement needed — the first clone(), simulate(), or warmup_gpu() call triggers stage attach lazily.

  • ovphysx_device_t enum reordered: OVPHYSX_DEVICE_AUTO = 0 (was 2) so zero-initialized args default to AUTO. Migration: code using symbolic names is unaffected; code using raw integers must update (0=AUTO, 1=GPU, 2=CPU).

  • OVPHYSX_TENSOR_ARTICULATION_CORIOLIS_FORCE_F32 renamed to ARTICULATION_CORIOLIS_AND_CENTRIFUGAL_FORCE (enum value 72 unchanged). Migration: update references to the old name.

  • Scene query enum constants disambiguated: OVPHYSX_SCENE_QUERY_CLOSESTOVPHYSX_SCENE_QUERY_MODE_CLOSEST (and _ANY, _ALL); OVPHYSX_SCENE_QUERY_SPHEREOVPHYSX_SCENE_QUERY_GEOMETRY_SPHERE (and _BOX, _SHAPE). Migration: update to the new constant names.

  • Python enum cleanup. All OVPHYSX_TENSOR_*_F32 bare-int constants removed (use TensorType.* members); OVPHYSX_API_*, OVPHYSX_LOG_*, OVPHYSX_DEVICE_* constants removed (use ApiStatus.*, LogLevel.*, DeviceType.*); OVPHYSX_OP_INDEX_ALL renamed to OP_INDEX_ALL; kDLCPU, kDLCUDA, kDLInt, kDLUInt, kDLFloat removed (use DLDeviceType.kDLCPU / DLDataTypeCode.kDLFloat).

  • Removed ovphysx_finalize(). Full Carbonite framework teardown now happens automatically when the last instance is destroyed. Migration: remove any ovphysx_finalize() calls.

Added#

  • ovphysx_articulation_metadata_t struct and ovphysx_get_articulation_metadata() — fills 6 scalar topology fields in one call, one lock acquire, one stream fence.

  • OVPHYSX_CONFIG_SCENE_MULTI_GPU_MODE (int32) config entry for /physics/sceneMultiGPUMode.

  • ConfigBool, ConfigInt32, ConfigFloat, ConfigString IntEnums in Python for typed config key access.

  • PhysXConfig dataclass in Python for typed initialization config.

  • Remote USD loading: add_usd() now accepts remote URIs (omniverse://, S3, Azure Blob). Use HTTPS virtual-hosted S3 URLs. New ovphysx_configure_s3() / configure_s3() and ovphysx_configure_azure_sas() / configure_azure_sas() for credential setup.

  • 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).

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

  • TensorBindingsAPI: standalone rigid body properties — mass, inertia, COM pose (read/write with indexed/masked write support). Articulation body inverse mass and inverse inertia (read-only).

  • TensorBindingsAPI: fixed tendon properties — stiffness, damping, limit stiffness, limits, rest length, offset (read/write, indexed, masked).

  • TensorBindingsAPI: spatial tendon properties — stiffness, damping, limit stiffness, offset (read/write, indexed, masked).

  • TensorBindingsAPI: shape-level tensors — RIGID_BODY_SHAPE_FRICTION_AND_RESTITUTION ([N,S,3]), RIGID_BODY_CONTACT_OFFSET ([N,S]), RIGID_BODY_REST_OFFSET ([N,S]), and articulation equivalents. All support read, indexed write, and masked write.

  • Articulation metadata name queries: ovphysx_articulation_get_dof_names, get_body_names, get_joint_names.

  • Contact binding API: ovphysx_create_contact_binding / destroy / get_spec / read_net_forces / read_force_matrix. Python ContactBinding class for reading contact forces via DLPack tensors.

  • PhysX object interop: ovphysx_get_physx_ptr() returns raw PhysX SDK pointers by USD prim path and type enum. SDK ships PhysX headers under include/physx/. See PhysX Interop tutorial.

  • Contact report API: ovphysx_get_contact_report() exposes per-step contact data as typed C struct pointers. Python PhysX.get_contact_report() returns a dict with ctypes arrays.

  • Scene query API: ovphysx_raycast(), ovphysx_sweep(), ovphysx_overlap() — raycast, geometry sweep, and overlap queries. Supports CLOSEST/ANY/ALL hit modes with sphere, box, and arbitrary-shape geometry.

  • Synchronous stepping: ovphysx_step_sync() / step_sync() combines step + wait into one call. ovphysx_step_n_sync() / step_n_sync() batches N steps.

  • OVPHYSX_API_BUFFER_TOO_SMALL = 6 status code.

  • CMake package config: find_package(ovphysx) provides ovphysx::ovphysx target and Windows ovphysx_copy_runtime_dlls() helper.

  • Visual sample using Rerun for rigid body visualization (Rendering Handoff).

  • Linux aarch64 support.

Changed or Fixed#

  • 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 sequence: add_usd()clone()warmup_gpu()/step().

  • A GPU and CUDA installation is no longer required for 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 now validate dtype is float32 before dispatching.

  • ContactBinding.destroy() now checks status and raises on failure; validates parent SDK is alive.

  • create_contact_binding now validates sensor_patterns is non-empty and filter_patterns length.

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

  • Fixed shutdown crash where stepper tasks could access freed CUDA context.

  • Fixed process-exit crashes caused by non-deterministic DLL/SO unload ordering. ovphysx_destroy_instance() now performs full teardown when the last instance is destroyed.

  • ovphysx_remove_usd() now validates usd_handle and returns OVPHYSX_API_NOT_FOUND for unknown handles.

Removed#

  • 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.