Changelog#
All notable changes to ovphysx are documented in this file.
[0.5.9] - 2026-07-21#
Fixed#
Linux wheel startup reuses already-loaded compatible OVStage/USD SONAMEs. This prevents duplicate static USD registration when OVRTX-first applications later import ovphysx.
[0.5.8] - 2026-07-21#
Fixed#
ARTICULATION_JOINToutput read now returns every unlocked joint DOF, with or withoutJointStateAPI(NvBugs 6481083 / OMPE-102219). Previously thejointPosition/jointVelocityread only emitted a group for joint axes carrying an authoredJointStateAPI, so a valid articulation whose joints had none returned zero groups, and a joint withJointStateAPIon only a subset of its DOF dropped the remaining unlocked DOF. The read now enumerates the joint’s actual unlocked reduced-coordinate DOF (angular axes in degrees, linear in base units), honoring an authored per-axisconvertToDegreesflag when present. UndereENABLE_DIRECT_GPU_APIthe per-DOF state is sourced live from the direct-GPU API instead of the frozen CPU-side accessor.Fixed-base articulations reject centroidal-momentum tensor bindings at creation (NVBugs 6481094 / OMPE-102210).
create_tensor_bindingforARTICULATION_CENTROIDAL_MOMENTUMnow fails withINVALID_ARGUMENT(raisesRuntimeErrorin Python) when any matched articulation is fixed-base, instead of accepting the binding and only surfacing the failure later at read time. Centroidal momentum is defined for floating-base articulations only. The check inspects every matched articulation, so a pattern that resolves to a mix of fixed- and floating-base articulations is rejected too. Empty matches remain valid empty bindings.Consumed operation indices now honor single-use wait semantics (NVBugs 6473891, 6481089, 6481092). Explicit waits are tracked separately from internal synchronization, so a second blocking or polling
wait_op()reportsNOT_FOUND(and raisesRuntimeErrorin Python) as documented, even after later stream operations synchronize pending work. The simulation wait fast path also defensively permits only one competing waiter to consume an index; callers must still serialize same-instance API use.Wheel-bundled
OVPHYSX_LIBselection preserves the paired OVStage runtime (NVBugs 6481086). Setting the override to the installed wheel’s ownlibovphysx.soorovphysx.dllnow retains cross-wheel dependency setup. Overrides that select an external SDK remain isolated from wheel dependencies.
[0.5.7] - 2026-07-21#
Versioned ABI break introduced in 0.5.4. The 0.5 release line changes exported C ABI (
ovphysx_clonegains a sixthenv_idsparameter — see the cloning entry), so the wheel/SDK version was bumped to0.5.4. A binary built against a0.5.3-or-earlier header must be recompiled, and can pin<0.5.4to stay on the old five-argument surface. The internalIPhysxReplicator::replicate/isReplicatorStage/IPhysxSimulation::cloneEnvironmentsfunction-table shapes also change, but ovruntime is linked statically into ovphysx, so there is no independently-versioned ovruntime binary and this single0.5.4bump covers the whole shipped surface.
Changed#
Windows PE binaries codesigned before packaging. The Windows build job now runs
repo_codesignagainst_install/after install and before SDK/wheel/packman packaging, so all distributed.dlland.pydfiles carry a production Authenticode signature. The packman.7zis signed during packaging viasign_binary_filesinrepo.toml. Linux.sofiles are not affected (repo_codesignis a PE/Authenticode-only tool).PhysX.overlap()now raisesValueErrorforSceneQueryMode.CLOSEST. CLOSEST has no meaning for overlap queries (no ray or sweep direction). The C API still treats CLOSEST as ALL; only the Python API rejects the mode. Closes NVBugs 6172863.Contact report batched buffers are reclaimed on simulation reset/detach (NVBugs 6172770).
PhysXScenenow deletes its ownedContactReportduring teardown (previously leaked peak-capacity vectors). Documented in the developer guide.ovstage compatibility pin centralized at
0.1.0.346039. ovstage is fetched as a single self-contained wheel (C++ SDK + python package) resolved from a PEP 503 index selected byfetch_ovstage_release.py --source(defaultinternal; the open-source drop uses the default public PyPI, pypi.org).
Added#
Documented x86_64 AVX CPU requirement and fail-fast init check (NVBugs 6447187). Pre-built Linux and Windows x86_64 binaries require AVX; Linux aarch64 is unaffected. README, overview, quickstart, PyPI metadata, and agent guidance now state the requirement.
ovphysx_initialize()(and PythonPhysX()construction) now returns a clear error when x86_64 hosts lack AVX hardware/OS support instead of failing later with SIGILL at the first physics step. No non-AVX fallback is provided.Simulation Setup, Physics Schemas, and Guides documentation. New docs sections covering the physics content ovphysx simulates and how to author it in USD:
docs/simulation_setup/(physics scene, colliders, rigid bodies, joints, articulations, deformables, particles),docs/physics_schemas.md(schema layers and codeless PhysX schema registration), anddocs/guides/(performance, collision tuning, articulation stability, gripper tuning, known limitations). Adapted from the Omni PhysX developer guide and scoped to the ovphysx public API (codeless PhysX schemas +PhysXConfig).ovphysx-output-readskill. New agent skill underskills/for the ovstage-native physics output query/read API, available since ovphysx 0.5.0, covering Python and C reads, borrowed group lifetime,ALLversusACTIVEscope, and closed-loop ovstage write-back with disjoint control/output ordinal lanes. Bundled Python, C, and closed-loop references keep standalone agent installs self-contained. Ships in the wheel and SDK with product-owned behavior and routing evals.ovphysx-usd-authoringskill. New agent/human skill underskills/for authoring USD physics content that ovphysx loads and simulates (physics scene, rigid bodies, colliders, mass), covering both the hand-authored.usdaroute and the Python route (stockusd-core; PhysX-specific attributes via the codeless schemas). Ships in the wheel and SDK alongside the existing skills.TensorBindingsAPI deformable material extended properties. Four additional deformable material tensor types:
DEFORMABLE_MATERIAL_ELASTICITY_DAMPING_F32(133, volume + surface),DEFORMABLE_MATERIAL_BENDING_STIFFNESS_F32(134),DEFORMABLE_MATERIAL_THICKNESS_F32(135), andDEFORMABLE_MATERIAL_BENDING_DAMPING_F32(136). The bending/thickness properties are surface-material-only – reads return 0.0 for volume material entries and writes are silently skipped. RequiresIDeformableMaterialViewextension via newgetSurfaceProperty/setSurfacePropertyhelpers inBaseDeformableMaterialView.SDF shape evaluation API. New C
ovphysx_create_sdf_view(),ovphysx_evaluate_sdf(),ovphysx_destroy_sdf_view(), and PythonPhysX.create_sdf_view()/SdfView.evaluate(). Evaluates signed distance fields of PhysX collision shapes at caller-supplied query points. Input shape[N, Q, 3], output shape[N, Q, 4](gradient xyz + distance). GPU instances only (CPU SDF evaluation is not yet implemented). Uses the existingISdfShapeViewtensor backend.Environment cloning API is now replicator-backed (Fabric-free).
ovphysx_clone(handle, source_path_in_usd, target_paths[], num_target_paths, parent_transforms, env_ids)(C) + PythonPhysX.clone(source_path, target_paths, parent_transforms=None, env_ids=None)+ experimentalPhysX::clonekeep the pre-0.5 clone semantics (the trailing optionalenv_idsis a versioned C ABI addition – see the env-ids entry below), but the implementation now drives the PhysX SDK replicator (binary serialization) instead of the removed Fabric scenegraph copy – so cloned articulations are real articulations (what IsaacLab-style batched RL needs), with no Fabric involved. Each copy is created at the caller’starget_paths[i];parent_transforms[i](a flat[num_target_paths * 7]array: position + imaginary-first quaternion) positions the copy’s parent, and every cloned body keeps its pose relative to the source’s parent, so an at-origin source lands each body exactly atparent_transforms[i](NULL co-locates every copy on the source pose). Behavior change vs the old Fabric clone: the clones exist as runtime physics only – no USD/scenegraph prims are authored (scenegraph duplication is now an ovstage responsibility,ovstage_clone+ instancing). env-id cross-environment collision filtering follows the/ovphysx/clone/useEnvIdssetting (default on); with explicit per-clone transforms the copies are already spatially separated, so it is an optional add-on. Replication executes inline and returns an already-completeop_index;wait_op()remains valid and returns immediately (IsaacLab ClonePlan contract). Must be called before GPU warmup / the first step. Path-string API (no USD type crosses the boundary). Restores theLab.cartpole_*/Lab.anymal_*GPU benchmarks. The clone’s replicator registration is one-shot (released as soon as the clone completes, even on failure), so a laterreset_stage()-> reattach -> clone works and no replicator state leaks; a clone that throws internally is reported as an error instead of crossing the C boundary. Multipleclone()calls on one attach are env-id-safe: env-ids are offset per batch, so distinct environments never alias to one id under GPU broadphase. When one logical environment is assembled from several clone calls (a heterogeneous ClonePlan: one call per source row), pass the new optional per-targetenv_ids(Cconst uint32_t*, Pythonenv_ids=[...], C++envIds) with the same ids in every call: the same logical id always maps to the same runtime environment (env_ids[i] + 1; 0 stays the source’s), so/env1/Robotand/env1/Objectcloned by different calls keep colliding with each other while staying isolated from other environments — per-call automatic numbering would silently stop same-environment contacts. A laterenv_ids=NULLcall numbers past every explicitly-placed id, so mixed usage cannot alias.env_idsvalues must be <0x00FFFFFF(PhysX supports at most 1<<24 environments and the runtime id isenv_ids[i] + 1); the runtime also checkssetEnvironmentID’s result and guards the automatic id base against exhaustion, warning rather than silently disabling isolation. Breaking (C ABI):ovphysx_clonegains the trailingenv_idsparameter — existing C callers must addNULLand relink; the ovphysx wheel/SDK version is bumped to0.5.4for this (see the release-header note above). Python/C++ callers are unaffected (optional keyword/defaulted argument). Invalid targets are rejected up front: empty, equal to the source, duplicated in one call, or already cloned on the current attach. A rejected clone reports its error synchronously with no dangling operation, so a later attach / reset / clone is unaffected. Targets carrying an embedded NUL are also rejected (the C-string seam would otherwise truncate them to a different path, e.g. back to the source). A target that is already populated with physics from the initial parse (a real USD subtree carrying physics, not just a prior-clone target) is rejected by a runtime ObjectDb subtree check before the replicator runs, so cloning onto it can no longer add duplicate actors. The experimental C++PhysX::clonecompletes the clone synchronously and now surfaces the operation index through an optionaloutOpIndexout-param, matching the C/Python forms. The Python wrapper also validates eachparent_transformsentry is exactly seven finite floats (short/long tuples are rejected rather than causing an out-of-bounds native read or shifted poses). Co-located clones (nullparent_transforms) are fully env-id-isolated:attach_ovstageenables creation-time environment-id assignment (/physics/replicatorEnvIdsOnAttach, following/ovphysx/clone/useEnvIds), so on GPU-dynamics + GPU-broadphase scenes the source environment’s dynamic bodies and articulations are created holding environment id 0 (clones get 1..N) and never collide with their co-located copies. Ids are assigned at body creation — live scene objects are never removed and re-added, which corrupts DirectGPU scenes. Static bodies keep the collide-with-all default (PX_INVALID_U32) — statics never collide with each other, so this keeps a shared static ground plane colliding with every environment, so cloned envs keep resting on it. (Known limitation: a co-located clone stack whose envs each carry their own static obstacle is not isolated from those overlapping statics — use explicit transforms for that layout; a shared dynamic object without a scene-partition primvar lands in env 0.)
Known limitations#
Point-instancer rigid bodies are not available to TensorBindingsAPI.
UsdGeom.PointInstancerinstances are simulated and available through the ovstageRIGID_BODYoutput-read path, but they are not exposed as individual rigid-body tensor-binding rows. With the default empty-binding behavior, a binding that targets only the point instancer has count zero. Read instance state through output read; control it by authoring the point-instancer arrays through ovstage. (NVBugs 6481085 / OMPE-102217.)ovstage-authored clones are not available to TensorBindingsAPI in 0.5. Draining an
ovstage.Stage.clone()/ovstage_clone()delta withupdate_from_ovstage()creates the corresponding physics objects, but TensorBindingsAPI does not discover them. After the source Stage is populated and attached, and after any later committed source edits are drained, tensor-based multi-environment workloads must usePhysX.clone()/ovphysx_clone()and complete direct cloning beforewarmup_gpu()or the first simulation step. Direct cloning invalidates existing tensor and contact bindings; destroy and recreate them before use. Do not also drain a duplicate ovstage clone delta for the same targets. Complete support for ovstage-authored clones in TensorBindingsAPI is scheduled for ovstage after 0.5.
Fixed#
Object-change callbacks no longer fire for the initial stage population (NVBugs 6473870 / OMPE-102206). A subscriber registered before an
attach_ovstage()/update_from_ovstage()populated its scene received a spuriouson_object_createdcallback for every actor and shape of the initial population, contradicting the documented contract that the initial population is not notified (the caller already has that state from setup). ovphysx subscribes withstopCallbackWhenSimStopped=falseso it also observes reset/clone events while stopped; that flag used to bypass the runtime’s initial-population suppression as well. The runtime now suppresses the initial population for every subscriber by default, independent of the simulation-stopped gate.SDF mesh colliders no longer hang attach under CPU-only mode (NVBugs 6480595). Authoring a dynamic rigid body with an SDF mesh collider (
PhysxSDFMeshCollisionAPI,physics:approximation="sdf") and attaching while in CPU-only mode (PhysX.set_cpu_mode(True)) previously hungattachforever: the synchronous attach-time SDF cook requested GPU cooking on the ujitso agent, which could not create a CUDA context, and attach then blocked on an infinite cook wait that the canceled build never signaled. SDF cooking now falls back to the CPU builder whenever no CUDA device is available, so the body cooks and simulates on CPU. GPU cooking is unchanged when a device is present.ovphysx_create_instance()rejects a null config-entry array with a nonzero count (NVBug 6481076 / OMPE-102221). With an active lifecycle, the C API now returnsOVPHYSX_API_INVALID_ARGUMENTwithout changing the output handle or starting the runtime, instead of silently ignoring the pair and returning a live instance.Malformed DLPack metadata is rejected before traversal (NVBug 6394727). The Python
__dlpack__ingestion path now raisesValueErrorwhen a producer reports a rank outside the supported range of 1 through 8 or a null shape, before indexing its shape or stride metadata.ManagedDLTensorcleanup waits for all DLPack borrowers (NVBug 6473877 / OMPE-102207). The Python wrapper and every capsule exported from it now share the wrapper’s lifetime. A capsule retains its producer until the capsule or its consumer releases the managed tensor, while the producer remains the sole owner of the user cleanup callback. The callback therefore runs exactly once after the wrapper and all borrowers are gone, preventing both premature cleanup and the previous double callback.Large
clone()batches retain every runtime target path (NVBugs 6471168). The replicator rename callback previously received integer path handles encoded from temporarySdfPathobjects that had already been destroyed. At large batch sizes, allocator reuse made some clones unavailable under their requested runtime paths and from tensor bindings even though all PhysX actors were created. The clone call now owns every targetSdfPathuntil synchronous replication completes, so wildcard and explicit tensor bindings resolve the full batch deterministically.Ovstage output reads now return live DirectGPU rigid-body and articulation-link state. Position, orientation, linear velocity, and angular velocity are gathered from PhysX DirectGPU state, using the scene-wide articulation-link stride.
Live deformable output reads under DirectGPU. The ovstage-native output read now refreshes volume and surface sim-mesh positions and velocities from the device when per-frame USD write-back is disabled. In scenes where sleeping is enabled,
ALLscope also refreshes sleeping deformables instead of returning last-awake data. (NVBugs 6464833 / OMPE-101753.)Tensor path matcher: same-name over-match, unanchored alternation, and
**duplicates fixed. Three corrections to the leaf-recursive pattern matching that shipped in 0.4.1 forcreate_articulation_view()/create_rigid_body_view()/create_rigid_contact_view(). (1) A leaf that recurs nested inside its own match (e.g.Robot/Disc001/robot/Disc001under pattern/envs/*/robot/Disc001) is now suppressed – shallowest wins – so a match is counted once; this fixes the IsaacLab 3.0 contact-sensor “expected 1, found 2” filter-count error. (2) An alternation leaf likebase_link|link_0is anchored as^(base_link|link_0)$, so it no longer also matchesbase_link_extra/prefix_link_0and silently enlarges views. (3) After an explicit**the final leaf is direct-matched rather than re-recursed from every root, so contactfilterPathsand SDF views (which do not pointer-dedup) no longer receive duplicate paths.The resolver and OmniClient runtime now come from the exact OVStage package selected by the build. The direct
omniusdresolver_ov_openusd_0.25.11_nopyandomni_client_libraryPackman dependencies are removed. Native packaging stages the matched OVStage resolver, registry, OmniClient, andomniverse_connectionset, while wheel packaging continues to leave the USD and resolver singleton to the paired OVStage wheel. OVStage-provided notices are preserved in the SDK and wheel license payloads. Windows wheel startup registers both OVStage runtime directories before loadingovphysx.dll, so the external USD monolith resolves without relying onPATH. Startup also verifies the loaded OmniClient version against the staged OVStage provider and fails closed instead of reusing a foreign Kit client.Debug compilation now consistently uses Release runtime dependencies. The published OVStage package provides a Release-only resolver, OmniClient, and
omniverse_connectionset. Internal and public builds therefore default to Release runtime dependencies, and an explicit true-Debug runtime request fails at configuration time instead of mixing incompatible runtime variants.Wildcard tensor bindings over a top-level runtime clone now resolve the cloned bodies. After
clone()-ing a source subtree onto a brand-new top-level target (e.g./World->/World_clone0), a pattern binding such ascreate_tensor_binding(pattern="/World_clone0/*")resolved 0 bodies even though the clone materialized real bodies (explicit prim paths found them). The replicator registered the clone root under the absolute root/instead of the (empty) pseudo-root, so the internal-DB path matcher – which selects roots by an empty parent – never enumerated it. Nested clone targets under an existing parent (e.g./World/envs/env0->/World/envs/env1) were unaffected. (NVBugs 6421194 / OMPE-100487.)Embedded NUL bytes in prim paths and patterns are rejected consistently across the whole API.
get_object_type(),create_tensor_binding(),create_sdf_view(),get_physx_ptr(),clone(),create_contact_binding(), and the scene-query SHAPE geometry (sweep()/overlap()) no longer silently mis-resolve paths carrying an embedded NUL byte (truncation, glob broadening, or wrong-object matches); they now returnOVPHYSX_API_INVALID_ARGUMENT. To make this safe once, the remainingconst char*path inputs at the C ABI were converted to the length-prefixedovphysx_string_t:ovphysx_get_object_type(),ovphysx_get_physx_ptr(), and theprim_pathfield ofovphysx_scene_query_geometry_desc_t.shape. Migration (C): wrap literals withOVPHYSX_LITERAL("/path")or runtime strings withovphysx_cstr(str). The experimental C++PhysX::getPhysXPtr()and Pythonget_object_type()/sweep()/overlap()signatures are unchanged. Closes NVBugs 6433621.clone()now invalidates the tensor backend’s cached per-stage simulation data (NVBug 6428316). The DirectGPU tensor backend builds itsGpuSimulationDatalazily on the firstcreateSimulationView()and snapshots the rigid-dynamic population at that instant – the actor->row map and the shared DirectGPU staging buffers (mRdPoseDev,mRdLinearVelAccDev, …) are sized by the then-current rigid-dynamic count. Acreate_tensor_binding()issued beforeclone()locked that count in at the pre-clone value;clone()then added bodies without rebuilding the cache. Every binding created afterward loggedInternal error: Unresolved rigid dynamic index!for the cloned actors and overflowed the undersized buffers, producing silent, non-deterministic partial writes (only a subset of environments received a batchedRIGID_BODY_VELOCITYwrite, whilewrite()still returned success) and an intermittent CUDA illegal memory access (cuda_status=700) on theRIGID_BODY_POSEread path.clone()now callsresetStage()so the nextcreateSimulationView()rebuilds the cache against the post-clone population. Existing tensor and contact bindings are invalidated byclone()and must be recreated before use. Best practice remainsattach -> clone -> warmup_gpu -> create bindings.clone()after the firststep()is now rejected on CPU as well as GPU, and duplicatetarget_pathsare rejected. Previously the after-step precondition only fired on GPU (gpu_warmup_donewas never set in CPU mode), so code validated on CPU could clone after stepping with no error and then fail with aRuntimeErrorthe first time it ran on GPU.clone()also silently collapsed a batch of duplicatetarget_pathsinto a single clone with no diagnostic. Both are now rejected withOVPHYSX_API_INVALID_ARGUMENT/RuntimeErrorin either mode. Closes NVBug 6433668.ovstage-backed loads now preserve child-collider poses and disabled state. Collision shapes below a rigid-body prim retain their body-relative translation, rotation, and scale, and
physics:collisionEnabled=falseis honored. Previously, child colliders could be created at an identity pose and authored-disabled shapes could remain active, producing incorrect contact geometry and destabilizing constrained bodies.Hard CPU-only mode rejects CUDA TensorBinding buffers before CUDA access. When
ovphysx_set_cpu_mode(true)is active orOVPHYSX_DISABLE_GPUis set, tensor reads and writes returnOVPHYSX_API_DEVICE_MISMATCHforkDLCUDAandkDLCUDAManagedbuffers without loading the optional CUDA interface or attempting a copy. Normal CPU/CUDA cross-device staging remains supported outside hard CPU-only mode.ovstage attach/detach now preserves reusable runtime state. Runtime attach failures are reported instead of being returned as success, and partial attachment state retains no pointer to the caller-owned Stage. Ovstage-only and nonresident-backing attachments keyed internally by zero now detach cleanly and can be reattached. Backing-stage query errors fail instead of silently becoming stageless attachments.
ovphysx_reset_stage()now releases TensorAPI per-stage simulation data. Stage reset and ovstage detach now invalidate the stage’s tensor views and release cached CPU/GPU simulation buffers. Previously this cleanup happened only when destroying the wholePhysXinstance, so repeated reset-and-reload cycles could exhaust GPU memory.Closed-loop output-read samples now verify ovstage control writes. The Python sample now seals control and output ordinals before consuming or publishing them, and the C sample updates the existing
physics:velocityattribute without replacing its semantic metadata. Both samples alternate the requested x velocity, fail if the simulation does not observe the change, and print the resulting velocity and displacement.ovstage compatibility is pinned to
0.1.0.342061(C++ build0.1.0.342061.191b00ed). The integration uses the renamedovstage_api_status_tand enqueueop_indexfields, declares fixed versus array writes explicitly, and uses population-owned USD files instead of the removed caller-owned StageCache entry point. The artifact fetch rejects a wheel whose embedded build version does not match the C++ package, avoiding a silent mismatch at the moving0.1.0release URL. The SDK preserves the release’s curated third-party notices asovstage-THIRD-PARTY-NOTICES.txtat the package root.ovstage consumers must rebuild against the pinned
342061headers. This release replacescuda_eventin the data and write payloads withcuda_sync { stream, wait_event }, changing the public read-group layout.Repeated ovstage wholesale reloads no longer corrupt Fabric change tracking. Build
342061clears stale dirty-bucket IDs when wholesale population resets and recycles its buckets, so anALL -> reset -> ALLsequence produces a valid change snapshot instead of crashing hierarchy consumers. Ovstage owns and loads the matching Fabric implementation from its released package.Initial ovstage snapshots are no longer replayed after attach.
ovphysx_attach_ovstage(..., read_ordinal)already parses that ordinal, so the shipped helpers, samples, tests, and docs now callovphysx_update_from_ovstage()only for later application-authored writes. This avoids treating the population’susd-prim-typecolumns as a second structural resync before the first DirectGPU step.Whole-prim removals from ovstage now remove their runtime physics objects. Build
342061emits ranged-read tombstones for deletedusd-prim-typeandusd-schemasmetadata even when their write floors do not advance. The change feed uses those tombstones to trigger a final structural snapshot, soovphysx_update_from_ovstage()destroys only paths that remain absent and preserves a prim deleted and recreated within the same range. The previous latest-state liveness probe is gone.Runtime clones preserve contact-report registration after ovstage loads. Replication now finalizes deferred source setup even when the source objects were already parsed by an ovstage update. This resolves pending contact-report actor pairs before cloning, so a clone created before the first simulation step reports contacts like its source body.
The namespaced USD runtime follows the ovrtx 0.4 / ovstage 0.1 release pin. Source builds and Python runtime tests use
0.25.11.kit.4-gl.21081, kept together to preserve ABI alignment.ovstage’s nested Cubric plugin is excluded from CPU-safe ovphysx packages. Build
0.1.0.342061keeps the plugin underbin/plugins/omni.cubric/; installation now removes that directory alongside the older layout so unused CUDA startup code is not exposed on CPU-only hosts.ovphysx_write_tensor_bindingnow accepts int64 index tensors. Previously only int32 was accepted; int64 indices are now staged down to int32 before the write. Fixes silent no-op on tendon and other indexed writes when callers (e.g. the IsaacSim umbrella adapter) supply int64 indices.Windows CPU-only ovstage Stage creation is restored. Build
342061retains the GPU-optional bootstrap fix from Kit!47029, so the Windows no-GPU CI again exercises both C++cpu_usdingestion and the Python Stage smoke instead of skipping them. On a GPU-capable host, ovstage Stage creation still activates a CUDA primary context before ovphysx attach; the separate hard CPU-only no-driver-touch test remains skipped at that upstream boundary.Windows ovruntime teardown is a blocking gate again. Build
342061retains the corrected Cubric/usdrt plugin load order. The temporary339558-specific allow-failure and pin guard are removed; the complete Windows unit suite must now exit cleanly after its assertions pass.
Removed#
Fabric-based cloning implementation removed (the
ovphysx_clone/PhysX.clone()API is retained, reimplemented on the PhysX replicator – see Added). Deleted the Fabric scenegraph- duplication path behind the old clone: the sidecar clone entry points,ovphysxReplicator.cpp, theovphysx_set_clone_env_root()remnants, and the Fabric prim duplication itself. Part of the Fabric retirement (ADR-0009): this was the last consumer of Fabric-based runtime prim duplication in ovphysx. Data-plane duplication of the scenegraph is now an ovstage responsibility (ovstage_clone+ instancing); ovphysx’sclone()handles the physics instantiation via the replicator. Behavior change: the old Fabric path authored per-clone scenegraph prims; the new path does not – clones exist as runtime physics only.Vestigial Fabric data-movement layer (internal). Deleted the orphaned
DataMovementImplFabric stage-lifecycle / DLPack holder – dead code superseded by the ovstage read/write API (0.5.1, ADR-0007); no public API or behavior change. Part of the Fabric retirement (ADR-0009).Obsolete Fabric config knobs. Dropped the internally-forced
/physics/fabricEnabledsetting and removed/physics/fabricUpdateVelocitiesfrom the config docs/examples/tests. Both had zero readers after the runtime Fabric removal (no-ops); they may still be passed viacarbonite_overridesbut no longer do anything.Host Fabric/USDRT bootstrap payload removed. Standalone Physics tests and packaged ovphysx artifacts no longer preload or flatten a second Fabric, scenegraph, hierarchy, or population stack from
ovruntime_deps. ovstage owns and loads its matching population runtime from its module-relative plugin tree. ovphysx retainsovruntime_depsfor unrelated services such as datastore, UJITSO, blobkey, Cubric, GPU compute, and USD support.
[0.5.1] - Date TBD#
Added#
Physics output read API (ovstage), ADR-0007. New C surface to read simulation output the ovstage way and feed it straight back into the attached Stage with no repack:
ovphysx_query(type, scope)→ovphysx_fetch_query_result(attribute / total-prim discovery) →ovphysx_read(attrs…)→ loopovphysx_fetch_read_next(&group_ptr)→ovphysx_release_group→ovphysx_release_read/ovphysx_release_query. Generic over simulated type (ovphysx_sim_object_type_t: rigid body incl. point-instancer arrays, articulation link, joint state, vehicle wheel, deformable volume/surface, particle set) and scope (ovphysx_object_scope_tall/active).ovstage-native types (no ovphysx mirror). The public headers
#includethe ovstage/ovx headers and use ovstage’s own types directly — a read group isovstage_read_group_t, discovery isovstage_query_result_t, attribute names areovx_string_or_token_t(string name OR interned token, in one call), the attached Stage is anovstage_instance_t*, andovphysx_update_from_ovstagetakes anovstage_ordinal_range_t. This replaces the earlier ovphysx mirror structs (ovphysx_read_group_t/ovphysx_query_result_t/ovphysx_attribute_semantic_t) and the separateovphysx_read_tokens. ovphysx ships the ovstage headers in its SDK/wheel alongsideovphysx.h.Producer-owned group.
ovphysx_fetch_read_nexthands back a borrowedconst ovstage_read_group_t*(producer-owned) instead of filling a caller struct; ovstage owns its allocation and lifetime. Consumers must compile against the exact pinned ovstage headers when the public struct layout changes. The group’s borroweddata.tensors(tuple width indtype.lanes),prims.listinterned prim set,attributetoken, anddata.index_map/maskfeed the ovstage write path verbatim.Distinct EOF vs. error end to end. The lower ovstage-native
ovxFetchReadNextreturns a 3-valuedOvxReadStatus(ok / end-of-iteration / error) instead of a bool, so the publicOVPHYSX_API_END_OF_ITERATIONis forwarded faithfully and a genuine fetch error is no longer reported as EOF.Empty match is a valid query. A successful query with no matches returns a nonzero handle whose read reaches end-of-iteration immediately (
total_prim_count == 0); a zeroout_querymeans FAILURE only.Stable group lifetime. A fetched group’s borrowed storage (struct + tensors / index_map / prims.list) is valid until its
read_group_idis released viaovphysx_release_group— fetching further groups, or an interveningovphysx_step, does NOT invalidate earlier ones (the data path gathers into session-owned storage; the sidecar retains each live group and forwardsovxReleaseGroup).Point-instancer output forwards verbatim. Rigid-body point-instancer output always emits the instancer’s FULL instance array (by-index, no element-axis scatter) for both ALL and ACTIVE scope, so the group is directly assignable into the ovstage write path; ACTIVE scope only selects which instancers are emitted, not a sparse subset of instances.
Routes through the internal sidecar (which links the ovstage + runtime read symbols) via
g_sidecar*function pointers — it now passes the ovstage types straight through (no translation). Requires an attached ovstage Stage (ovstage-only). Documented in the ovstage Integration docs section (ordinal-coupling principle: app→physics edits flow throughovphysx_update_from_ovstage; physics→app output is written at ordinals drain ranges never cover). Python: context-managedPhysX.read(...)/PhysX.read_tokens(...)→ReadResult.groups(ReadGroupexposestensors/index_map/semantic/ordinal/layout_generation+ the internedprim_list/attribute), valid for thewithblock.
Closed-loop ovstage output-read samples (
output_read.py,output_read_c/). New Python and C samples driving the full ADR-0007 round trip on the falling-boxes scene: each frame authors aphysics:velocitycontrol edit on every rigid body into ovstage (reusing the read group’s internedprim_listdirectly — no path rebuild), drains only the control ordinal viaovphysx_update_from_ovstage, steps, then reads everyovphysx_sim_object_type_tback and writes each group into ovstage undersim:<attribute>at a separate output ordinal that is never drained. The C sample reuses the group’s borrowedtensorsverbatim as the write payload (true no-repack); both demonstrate the two-lane (control/output) ordinal scheme end to end.Codeless PhysX USD schemas exposed for external authoring/validation (OMPE-86833). ovphysx now ships the PhysX USD schemas as codeless schema artifacts (a
plugInfo.jsonwithType=resourceplusgeneratedSchema.usda, no compiled library) in both the wheel and the SDK package under a stable path:<ovphysx>/schemas/physx/<module>/resources/. The exposed set is derived at packaging time from the PhysX schema modules ovphysx actually ships (e.g.PhysxSchemaandOmniUsdPhysicsDeformableSchema), so it always matches the packaged schema revision and tracks module additions/removals automatically. They are intended for external tooling that drives a stockusd-corefrom PyPI; ovphysx still has no dependency on any external schema package. New pure-Python helpersovphysx.codeless_schema_paths()(per-moduleresources/directories, ready forpxr.Plug.Registry().RegisterPlugins()) andovphysx.codeless_schema_root()discover them without triggering native loading. A reference example + regression test (tests/python_samples_extra/codeless_schemas/) registers the schemas into a stockusd-core, appliesPhysxRigidBodyAPIby identifier, and assertsprim.HasAPI(...). Codeless schemas carry no compiled C++/Python bindings; use USD’s generic schema API and apply by schema identifier (e.g.prim.ApplyAPI("PhysxRigidBodyAPI")).PhysX debug-visualization C API (
ovphysx_debug_render_*). Drives the standard PhysX debug-visualization pipeline (omni::physx::IPhysxVisualization) from hosts that step PhysX through ovphysx:ovphysx_debug_render_enable()authors theeVISUALIZATIONflags + applies the selected params;ovphysx_debug_render_set_parameter()/_get_parameter()toggle/read oneovphysx_debug_render_parameter_t(a public enum mirroringomni::physx::PhysXVisualizationParameter, validated againstNONE/out-of-range);ovphysx_debug_render_set_scale()/_get_scale()set/read the master scale (rejects non-finite/negative);ovphysx_debug_render_set_culling_box()(rejects non-finite /min > max); andovphysx_debug_render_get_points()/_lines()/_triangles()hand back read-only buffers ofovphysx_debug_point_t/_line_t/_triangle_t(layoutstatic_assert-locked to the omni::physx structs), valid until the next step or any stage/scene change._get_parameter/_get_scalereturn the values last set through ovphysx (cached on the ovphysx side; no interface change). No high-level Python wrapper yet. Pairs with an omni.physx fix so a non-positive viewport gizmo scale no longer zeroeseSCALE(debug viz works in headless / minimal hosts without seeding any persistent setting).ovphysx_articulation_update_kinematic()(OMPE-94459, _KINEMATIC_UPDATE_NOOP fix). New C API that forces propagation of root + DOF state into every articulation link buffer in the binding by callingPxArticulationReducedCoordinate::updateKinematic. Flags map toPxArticulationKinematicFlag::ePOSITION/eVELOCITYviaOVPHYSX_ARTICULATION_KINEMATIC_POSITION/_VELOCITY(bitmask). Closes the umbrella’sOvPhysxSimulationView.update_articulations_kinematicno-op gap so dof-position / root-transform writes can be observed in link-pose reads without simulating. Routes through the internal sidecar (which links PhysX SDK headers); the main ovphysx target stays free of direct PhysX includes via a newg_sidecarUpdateKinematicfunction pointer. Python: callable directly via_bindings._lib.ovphysx_articulation_update_kinematic(handle, binding_handle, flags)with typed ctypes argtypes already registered; no high-level Python wrapper class method yet.OVPHYSX_TENSOR_ARTICULATION_DOF_DRIVE_MODEL_F32tensor type (OMPE-94459, JointPerformanceEnvelope fix). New read/write[N, D, 3]tensor wiringIArticulationView::set/getDofDriveModelProperties. Per-DOF triple is (speedEffortGradient,maxActuatorVelocity,velocityDependentResistance). Writes are applied only to DOFs withPhysxDrivePerformanceEnvelopeAPIapplied in USD; the engine silently drops writes on other DOFs and logs a warning. Python:TensorType.ARTICULATION_DOF_DRIVE_MODEL.ovphysx_get_object_type()(OMPE-94459 #13). New C API + PythonPhysX.get_object_type(prim_path)that classifies a USD prim by TensorAPI object type (RIGID_BODY,ARTICULATION,ARTICULATION_LINK,ARTICULATION_ROOT_LINK,ARTICULATION_JOINT, orINVALID). Mirrorsomni::physics::tensors::ObjectType. Closes the umbrella adapter’s missingsim.get_object_typesurface.GPU-mode shape-property and uint8-flag tensor writes (OMPE-94459 §B9). Fixed articulation and rigid-body tensor views rejecting GPU tensors for material, contact-offset, rest-offset, and uint8 flag writes. The GPU view implementations now stage GPU tensors to host via cudaMemcpy before delegating to the shared runtime logic, and matching reads copy host-side results back to GPU destination tensors. Both indexed and masked GPU writes on the three articulation shape-property bindings (
ARTICULATION_SHAPE_FRICTION_AND_RESTITUTION,ARTICULATION_CONTACT_OFFSET,ARTICULATION_REST_OFFSET) and the rigid-body uint8 bindings now succeed instead of returning “Incompatible device”.OVPHYSX_TENSOR_RIGID_BODY_DISABLE_SIMULATION_BOOLtensor type (OMPE-94459 §B5). New read/write[N]uint8/bool tensor that togglesPxActorFlag::eDISABLE_SIMULATIONon the underlyingPxRigidActorso disabled bodies stop participating in the next solver step. Writes apply at runtime (no stage detach required) – the underlying TensorAPIIRigidBodyView::set/getDisableSimulationswas already wired engine-side, this just exposes it through the ovphysx binding. Both indexed and masked-write paths are supported. Python:TensorType.RIGID_BODY_DISABLE_SIMULATION. The umbrella adapter’sRigidBodyView.set_disable_simulations()can now drive the engine directly instead of writing USDphysics:rigidBodyEnabled. The C ABI dtype validator (validateTensorShape) gained a per-type dtype expectation soDISABLE_SIMULATIONaccepts uint8/bool while every other binding continues to require float32;ovphysx_get_tensor_binding_spec()reports the matching dtype.Python
TensorBinding.dtypeandTensorBinding.spec. The high-level PythonTensorBindingnow preserves the nativeovphysx_get_tensor_binding_spec()dtype in addition toshapeandndim.binding.dtypereturns a Python-ownedDLDataTypecopy, andbinding.specreturnsTensorBindingSpec(dtype, ndim, shape). Downstream view builders can allocate buffers from the native DLPack spec and no longer need to assume everyTensorTypeis float32.ovphysx_read_raw_contact_data()andovphysx_contact_binding_get_other_actor_paths_from_ids()(OMPE-94459 #21). Filter-less variant ofovphysx_read_contact_data– returns every contact involving each sensor body plus a per-contact opaque actor id resolvable to a USD prim path. Shapes (C = max_contact_data_count, S = sensor_count): force/separation[C, 1], point/normal[C, 3], count/start_indices[S](1D, no filter dim), other_actor_ids[C]int64/uint64. Pair withovphysx_contact_binding_get_other_actor_paths_from_ids(ids_tensor, out_paths, max, &count)to translate ids to USD prim paths in bulk. Filter pattern length may be 0 when creating the binding (raw reads don’t use a filter dim). Python:ContactBinding.read_raw_contact_data(force, point, normal, separation, count, start_indices, ids)+get_other_actor_paths_from_ids(ids_array) -> list[str]. Also extends the DLPack->TensorDesc converter (DLPackConvert.h) to accept int64/uint64 in addition to the previous float32/int32/uint32/uint8/bool set, required by the actor-id tensors.Three new articulation tensor types for centroidal dynamics (OMPE-94459, §B0):
OVPHYSX_TENSOR_ARTICULATION_MASS_CENTER_WORLD_F32(12),OVPHYSX_TENSOR_ARTICULATION_MASS_CENTER_LOCAL_F32(13), andOVPHYSX_TENSOR_ARTICULATION_CENTROIDAL_MOMENTUM_F32(14). Both mass-center variants return[N, 3](per-articulation COM in world or root-local frame); the centroidal-momentum tensor returns[N, 6, D+7]packing the 6x(D+6) centroidal-momentum matrix plus a 6x1 bias column (D =getMaxDofs()). All three are read-only – writes returnOVPHYSX_API_INVALID_ARGUMENT. Centroidal momentum requires floating-base articulations; the tensor backend returns an error on fixed-base. Python mirror:TensorType.ARTICULATION_MASS_CENTER_WORLD,..._MASS_CENTER_LOCAL,..._CENTROIDAL_MOMENTUM.ovphysx_rigid_body_view_wake_up()/ovphysx_rigid_body_view_sleep()(OMPE-94459 follow-up). New C APIs + PythonTensorBinding.wake_up(indices=None)/TensorBinding.sleep(indices=None)that wake or put to sleep rigid bodies in a binding, mirroringPxRigidDynamic::wakeUp/putToSleep. Optional int32 indices tensor for a subset; pass NULL/None to act on every body in the binding. Bodies withRIGID_BODY_DISABLE_SIMULATIONset are silently skipped.GpuRigidBodyViewoverrides added so both calls work on the GPU pipeline (stages GPU indices to host then delegates to base impl).ovphysx_destroy_instanceresets the tensor SimulationBackend (OMPE-94459 follow-up). The backend cachesmGpuSimData/mCpuSimDataas plugin-scope singletons and clears them only on stop events that ovphysx does not emit. With plugins resident across destroy/create cycles, the next instance + stage attach would inherit stale GPU data causing “Internal error: Unresolved rigid dynamic index!” and heap corruption. Fix: the destroy path now resets the tensor backend after per-binding cleanup so the next instance starts with fresh backend state.GPU
link-incoming-joint-force/dof-projected-joint-forcesreturn zeros pre-simulate, matching CPU.GpuArticulationView::{getDofProjectedJointForces, getLinkIncomingJointForce}now zero the destination tensor whenSimulationBackend::getStepCount() == 0, mirroring CPU’s existingPxMemZeroondt == 0.0f. Previously the GPU readback streamed uninitialised PhysX SDK buffer bytes to the projection kernel.Disable/enable is now correct across multiple GPU rigid-body views over the same scene (OMPE-94459).
GpuRigidBodyView’s rd-GPU-index refresh was gated on a per-view dirty flag set only by that view’s ownsetDisableSimulations. With two or more views covering the same body, a disable/enable issued through one view freed/reallocated the shared GPU island index without dirtying the sibling views, so those siblings kept reading the stale/invalid index. Fix:setDisableSimulationsnow bumps a scene-widemRdDisableEpochon the sharedGpuSimulationData;refreshRdGpuIndices()compares it against a per-view last-seen value and rebuilds when a sibling toggled a body. The fast path stays a true no-op for the common single-view, disable-free case.ovphysx_set_cpu_mode(bool)process-level CPU-only API. Call before creating any instances to force the entire process into CPU-only mode: no CUDA driver is touched, all PhysX scenes use CPU dynamics regardless of their USDphysxScene:enableGPUDynamicssetting, and Fabric stages use CPU compute. ReturnsOVPHYSX_API_ERRORif any instances are currently active, or if attempting to revert after CPU mode has been applied (sticky for the process lifetime). C++:PhysX::setCpuMode(bool)(static). Python:PhysX.set_cpu_mode(bool)(static). For per-scene CPU control without this flag, author scenes explicitly withphysxScene:enableGPUDynamics = falseandphysxScene:broadphaseType = "MBP".
Changed#
omni.physics.tensorsplugin removed;TensorApinow ships in the static PhysX runtime (OMPE-96492). The standaloneomni.physics.tensors.plugin(TensorApi+BackendRegistry) is deleted. Because PhysX is the only simulation backend and it already lives insideOvruntimePhysX, the backend registry was pure indirection: the static PhysX runtime now owns theTensorApifunction table directly andcreateSimulationView/reset/resetStagecall the in-runtime backend directly.BackendRegistryandISimulationBackendare removed, and the now-meaningless per-callbackendNameselector is dropped fromcreateSimulationView/reset/resetStage. Behavior-neutral for tensor API consumers; theISimulationView/ typed-view contract is unchanged. The Pythonomni.physics.tensorsmodule and frontends were already retired upstream; this completes the removal.omni.physx.tensorsbackend folded into the static PhysX runtime (OMPE-96492). The standaloneomni.physx.tensorsCarbonite plugin is no longer built or packaged; the PhysX tensor backend now ships in the staticOvruntimePhysXruntime linked intolibovphysx.Non-Fabric ovruntime interfaces no longer use Carbonite plugin metadata or publication. Their existing function-table shapes are preserved, while ovphysx links the core
OvruntimePhysXruntime statically and starts it directly after Carbonite has loaded the remaining dependency plugins. This removes the old Carbonite interface-acquisition and lifecycle path without changing public ovphysx C, C++, or Python APIs.IPhysxFabricis excluded because Fabric is being removed separately.Tensor API path matcher no longer depends on Fabric/usdrt (OMPE-96492). The tensor wildcard matcher no longer attaches a usdrt/Fabric stage. On fabric-replicated stages it resolves against the Omni PhysX internal path<->object database and restores numeric clone order via
ObjectId; on USD-authored stages it uses the plain-USD matcher unchanged. The tensor backend is now Fabric-include-free, so the tensor API works in headless ovphysx without a populated Fabric stage. Behavior-preserving – tensor-view row/env ordering is unchanged on both paths (validated bit-identical against trunk on the IsaacLab parity matrix).Empty tensor bindings are quieter and better documented. Rigid-body and articulation tensor bindings created from optional broad patterns that match zero prims remain valid zero-count bindings, but no longer emit error/warning-level native logs through ovphysx. Explicit
prim_pathsstill surface no-match diagnostics for typos. Pythoncreate_tensor_binding(..., raise_if_empty=True)now keeps its opt-inValueErrorwhile explaining that optional or broad readback should use the defaultraise_if_empty=Falseand checkbinding.count. The tensor-binding tutorial now calls out empty optional bindings explicitly.Remaining top-level thread-safety docs now match the singleton runtime contract. The C header, module-level Python docs, and overview no longer claim that multiple
PhysXinstances are fully thread-safe; they now match the developer guide’s process-global runtime guidance.active_cuda_gpusGPU ordinal selection applied when simulation first binds to a stage. The ordinal specified inovphysx_create_args::active_cuda_gpusis applied lazily, not atcreate_instancetime. All stage bindings serialize through an internal mutex so concurrent instances with different ordinals do not stomp each other’s setting.Tensor backend per-stage view invalidation.
SimulationBackend::resetStage(stageId)now finds and invalidates allISimulationViewobjects for that stage before releasing sim data, preventing stale-view use-after-free.ovphysx_destroy_instancecallsresetStageautomatically on teardown.
Removed#
Dead pre-ovstage USD-from-URI loader removed (internal). The unused internal
omni_sdk_physx_load_usdand its sidecar open path (ovphysx_open_usd_stage+ovphysx_open_usd_stage_wrapper+g_sidecarOpenUsdStage) — a leftover from when ovphysx opened USD itself — had no caller or public entrypoint and are gone. ovphysx never resolves a remote/local USD asset itself in the ovstage model (the application populates the Stage and attaches it). The sidecar’sovphysx_close_usd_stageis retained (the instance-destroy/unload path still erases the attached USD stage fromUsdUtilsStageCache). No public-API change.ovphysx_configure_s3()/ovphysx_configure_azure_sas()removed from the public interface. Also gone: the C++PhysX::configureS3/PhysX::configureAzureSasand the Pythonconfigure_s3/configure_azure_sas. They were thin forwarders to OmniClient’s process-globalomniClientSetS3Configuration2/omniClientSetAzureSASToken— a pre-ovstage vestige from when ovphysx loaded USD itself. In the ovstage model the application owns population (ovstage.population.open_usd(...)) and ovphysx only consumes an already-populated Stage, so remote-asset credentials belong on the asset layer (OmniClient), configured by the app before population — not on ovphysx. Migration: configure credentials directly on OmniClient (omni.clientS3/Azure config in Python, oromniClientSetS3Configuration2/omniClientSetAzureSASTokenin C/C++) before callingopen_usd. See the Remote USD Loading developer-guide section.ovphysx_get_stage_id()removed from the public interface. The C APIovphysx_get_stage_id, the C++ wrapperPhysX::getStageId, and the PythonPhysX.get_stage_id()are gone. The attached USD stage id was an internal correlation detail that leaked the runtime’s stage-cache identity into the public surface; applications own their ovstage Stage and ordinals directly and have no need for it. Migration: there is no replacement — drop the call. Internal callers that still need the attached stage id read it from instance state directly.Init-time joint-limit snap removed (OMPE-94459). Articulation joints authored resting outside their limits are no longer snapped back into range at load (
addArticulationToScene). The runtime no longer silently corrects authoring errors – an out-of-limit joint now follows normal solver behavior. Assets must author joint state within limits; authoring-time validation will flag violations (tracked in a follow-up validation ticket).cpu_onlyfield removed fromovphysx_create_args. Replaced by the process-levelovphysx_set_cpu_mode()API. The previous per-instance flag had process-wide effect on PhysX dynamics mode and was removed to make that scope explicit. Migration: replaceargs.cpu_only = true/PhysX(cpu_only=True)with a call toovphysx_set_cpu_mode(true)/PhysX.set_cpu_mode(True)before creating any instances.DeviceTypePython enum andPhysXDeviceErrorexception removed. Neither was raised or checked by any current code path.
Fixed#
Use-after-free in
BaseDeformableMaterialView::release().release()nulledmSimbeforedelete this, so the destructor skippedmSim->_onChildRelease(this)and the freed view was never removed from the parentBaseSimulationView::mDeformableMaterialViews. The dangling pointer was later dereferenced by~BaseSimulationView’s_onParentRelease()sweep,check(), andrelease(recursive), corrupting the heap (intermittent SIGABRT,unlink_chunkhang, or segfault) after repeated deformable-material-view create/release cycles over a persistent per-stage_idSimulationView. Fix: dropmSim = nullptr;sorelease()matches the six sibling views and lets the destructor deregister normally.
[0.5.0] - Date TBD#
Changed or Fixed#
Breaking: stepping no longer takes an explicit simulation-time argument; the scene-teardown
reset()is renamed toreset_stage()(mirrors ovrtx).step(),step_sync(), andstep_n_sync()(Python) andovphysx_step(),ovphysx_step_sync(),ovphysx_step_n_sync()(C, plus the experimentalPhysX::step()C++ wrapper) drop theircurrent_time/sim_timeparameter. Simulation time is now a private implementation detail: the library tracks it internally (starting at0.0, advancing bydtper step, byn*dtforstep_n_sync) purely to feed engine/Fabric timestamps. This removes the per-callsim_time += n*dtboilerplate from RL training loops. The clock is owned by the application/ovstage, so it is not exposed through the ovphysx public API.The previous scene-teardown
reset()is renamed toreset_stage()/ovphysx_reset_stage()(mirrorsovrtx_reset_stage()); it clears the stage.
Migration: callers of the old
reset()(scene teardown) must switch toreset_stage(). The C++ wrapper’sreset(handle)(RAII handle reset) is unchanged.
Added#
UJITSO cooked-collider cache is now enabled (local, in-process). The kitless loader previously never loaded the UJITSO plugins, so collision cooking always ran uncached — every convex / triangle-mesh / SDF / convex-decomposition collider was re-cooked on every launch (the dominant cost in IsaacLab’s repeated-launch workflow). ovphysx now loads
omni.blobkey,carb.datastore,carb.ujitsoagent, andcarb.ujitso.defaultbefore the runtime cooking service and persists cooked colliders to a content-addressed cache, so they are reused on the next launch. The cache key includes the mesh geometry, cooking parameters, cooking type, and a cooker version token, so a changed mesh/param or a different PhysX build is never served a stale collider. Cache location is application-provided: setPhysXConfig(cooked_collider_cache_dir="<dir>")(C:OVPHYSX_CONFIG_COOKED_COLLIDER_CACHE_DIRECTORY) to the directory where cooked colliders should persist. ovphysx does not read environment variables and does not choose a location on the app’s behalf; if no directory is configured, cooking still works but does not persist across runs. The cache grows without automatic eviction — delete the configured directory to reclaim space. Scope: strictly local and in-process — no Omni Hub, Nucleus, or GRPC backends (those datastore backends default to off; an app may opt in viacarbonite_overrides). Disable cooking with/physics/cooking/ujitsoCollisionCooking=false. If collision cooking is enabled but the UJITSO plugins fail to load, initialization fails fast with a clear error rather than silently cooking uncached; a non-writable configured cache dir logs a warning and continues (no persistence). Addresses NVBugs 6262606.Process-global lifecycle API. New C entrypoints
ovphysx_initialize()andovphysx_shutdown()manage one active process lifecycle state. A second initialize before shutdown returnsOVPHYSX_API_ERROR. C callers must initialize beforeovphysx_create_instance().ovphysx_shutdown()clears this lifecycle state but does not unload Carbonite, OmniClient, or the static PhysX runtime; those remain resident until process exit. CPU/GPU selection remains onovphysx_create_instance().Release runtime-dependency selection for public Debug builds. New CMake option
OVPHYSX_USE_RELEASE_RUNTIME_DEPSselects release Packman runtime dependencies while preserving the requested compile build type. Internal checkouts default this option toOFF, so Debug builds keep matching Debug runtime dependencies unless explicitly opted in. Generated public source packages default it toON, because public debug dependency packages are not shipped. The selected runtime-deps config is propagated through fetch, configure, install, and package steps;cmake -P scripts/test_runtime_deps_config.cmakevalidates the default matrix.PhysX object-change notifications via
ovphysx_subscribe_object_changes()/ovphysx_unsubscribe_object_changes()(C) andovphysx::subscribeObjectChanges()returning an RAIIObjectChangeSubscription(experimental C++). Subscribers receive optionalon_object_created,on_object_destroyed, andon_all_objects_destroyedcallbacks; the per-object callbacks deliver the prim path andovphysx_physx_type_t, whileon_all_objects_destroyedis a no-payload bulk-invalidation signal. Pair withovphysx_get_physx_ptr()to refresh or invalidate cached pointers. Subscriptions are process-global; callbacks fire for events on every ovphysx instance in the process. Limitations:ovphysx_clone()does not currently emit creation notifications. After clone returns, refresh pointer caches explicitly.ovphysx_reset_stage()reliably emitson_all_objects_destroyedfor bulk teardown. Not exposed in Python (the Python layer no longer surfaces raw PhysX pointers).PhysX.get_contact_report(copy=True)returns Python-owned data safe to retain across steps. The defaultcopy=Falsekeeps existing zero-copy ctypes-array semantics for back-compat.copy=Truematerializesheaders,points, andanchorsintolist[dict]so callers (RL training loops, anything that holds contact data across a step) are no longer exposed to the silent-corruption hazard when internal C buffers are reallocated by the nextstep()/step_sync(). Docstring now carries a prominent lifetime warning. Closes NVBug 6172700.ResourceWarningfor forgottenPhysX()releases (Python). Usewith PhysX():or an explicitrelease()for deterministic cleanup. For instances that are garbage-collected mid-run,__del__emits aResourceWarning(silent by default, surfaced underpython -W default::ResourceWarningor in pytest) and then callsrelease()itself. The Python API no longer registers a process-exitatexitcleanup hook, so applications must not rely on interpreter shutdown to release live instances.PhysX.release()is unchanged (still idempotent). Closes NVBug 6172756.Opt-in benchmark suite under
tests/benchmarks/(C++ harness, framework files copied near-verbatim fromomni.physx/tests/test.benchmarks/so future updates merge cleanly) andtests/python_benchmarks/(pytest-benchmark-based). Both surfaces are off by default. Build the C++ binary withcmake -DOVPHYSX_BUILD_BENCHMARKS=ON -P scripts/build.cmake(or./build.sh --benchmarks); run the suite viacmake -P scripts/test_benchmarks_cpp.cmake(or..._python.cmake). Coverage: USD scene load, per-step simulation cost (CPU + GPU),clone()scaling at N = 64/256/1024, and tensor-binding read/write throughput. The C++ binary supports--regenerateto refresh baselines and--slop=<pct>for the regression bound. The CTest label isbenchmarksand is excluded fromvalidate_all. Heavy fixtures are generated and stored next to their generator scripts undertests/benchmarks/data/:kapla_tower_sphere_collapse.usda(1728 starting-asleep Kapla blocks + a sphere that fires in to collapse them) replaces the priorboxes_falling-based step benchmarks;articulation_pileup.usda(16 free-falling articulations of increasing link count over a dense scatter of dynamic obstacles, adapted fromomni.physxdemos.scenes.ArticulationDemo) replaces the priorlinks_chainstep benchmark. All benchmark fixtures now authorphysxScene:timeStepsPerSecond = 240so step-timing comparisons across fixtures are consistent.
Changed or Fixed#
ovphysx SDK and wheel builds now use static Carbonite as the only supported Carbonite mode. The package no longer ships the core
libcarb/carb.dllruntime library;libovphysxowns the single static Carbonite framework and packages only the remaining no-libcarb Carbonite bootstrap shims needed at runtime. Build and install RPATH handling was tightened so shipped binaries resolve bundled plugins without depending on a dynamic Carbonite SDK layout. TensorBindingsAPI Python acquisition now routes through the ovphysx-owned static framework instead of a module-localCARB_BINDINGSCarbonite entrypoint. The package verifier now hard-fails install and wheel builds if core Carbonite leaks back in aslibcarb.so,libcarb.so.*,carb.dll, ELFDT_NEEDED/DT_SONAME, or a PE import; the per-plugin no-libcarb shims (libcarb.<name>.plugin.so/carb.<name>.plugin.dll) remain allowed. No caller migration is required for normal wheel or dynamically linked C/C++ SDK usage.clone()afterwarmup_gpu()/ first step now raisesRuntimeErrorinstead of silently corrupting state. Previouslyclone()after GPU warmup logged a nativeCARB_LOG_WARNand proceeded, reallocating DirectGPU buffers and silently corrupting solver state — a documented hazard with no safe-fallback semantics. The C runtime (ovphysx_clone) now returnsOVPHYSX_API_INVALID_ARGUMENTwhengpu_warmup_doneis set, with the message “clone() must be called before warmup_gpu() and before the first step()…”; PythonPhysX.clone()propagates this asRuntimeError. Callreset_stage()first if you need to clone after warmup. Closes NVBug 6172717.ovphysx_subscribe_object_changes()no longer retries per-call when invoked before instance creation; it returnsOVPHYSX_API_ERRORimmediately if the internal sidecar isn’t loaded yet. Previously the call retried symbol resolution on every attempt to accommodate subscribers that beat instance creation. The sidecar is now resolved eagerly duringovphysx_create_instance, so subscribe must be called after at least one instance exists. Migration: reorder callers toovphysx_create_instancebefore anyovphysx_subscribe_object_changes; the error message names the missing sidecar load as the cause.Multi-instance and threading documentation corrected to reflect the process-global runtime. The developer guide previously implied that multiple
ovphysxinstances are safe to use concurrently across threads. They are not: every instance shares one attached scene process-wide. Callers must serialize across instances and treat the per-instance handle as bookkeeping only. The implementation has not changed; this is a spec correction. See docs/developer_guide.md Multi-Instance Support + Threading sections for the corrected guarantees.
Removed#
The legacy USD-handle load/unload API is removed, superseded by the ovstage attach/update flow.
ovphysx_add_usd()/ovphysx_remove_usd()(C),PhysX::removeUsd()(experimental C++), andPhysX.remove_usd()(Python) are gone, along with the per-instanceovphysx_usd_handle_t-keyed tracking. Load USD by populating an ovstage Stage and attaching it withovphysx_attach_ovstage(); useovphysx_update_from_ovstage()for later edits and clear the stage withovphysx_reset_stage(). Migration: replaceadd_usd/remove_usdwith the ovstage attach/update/reset flow.Raw PhysX SDK pointers are no longer exposed through the Python API.
PhysX.get_physx_ptr()and theovphysx.PhysXTypeenum are removed. The Python wrapper returned the pointer as a plainintaddress, so the only realistic use was passing it to a C/C++ extension and casting it back to a PhysX SDK type — a heavy migration for a Python-stable API to keep guaranteeing, with no in-Python use case that warranted it. The C (ovphysx_get_physx_ptr(),ovphysx_physx_type_t) and experimental C++ (PhysX::getPhysXPtr()) entrypoints are unchanged. Migration: this use case is no longer supported in Python. Callers that need raw PhysX SDK objects should callovphysx_get_physx_ptr()from a native (C/C++) extension; the C and C++ surfaces continue to ship and remain the canonical interop path.
[0.4.1] - 2026-05-12#
Breaking Changes#
gpu_indexreplaced byactive_cuda_gpusonovphysx_create_args. Theint32_t gpu_indexfield is removed. Useovphysx_string_t active_cuda_gpusinstead: 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 oldgpu_index=0default. 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 bysetActiveCudaGpus(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 Carbonite-published PhysX runtime stack. If another Carbonite user already registered incompatible PhysX plugins before
ovphysx_create_instance(), creation fails with a clear error. Conflicting pre-set/physics/cudaDeviceor/physics/suppressReadbackvalues 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=trueand/physics/suppressFabricUpdate=true(via two paths: a startup-timeCarboniteLoader::setStartupSuppressReadbackwrite before PhysX plugins loaded, and a per-instance write at GPU bootstrap), which routes PhysX intoeENABLE_DIRECT_GPU_APImode. DirectGPU is incompatible with contact modification (per PhysX SDK guidance: contact-modify requires a CPU roundtrip), so any scene relying oneMODIFY_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/suppressReadbackor/physics/suppressFabricUpdateat any point in its lifecycle. The previously-publicCarboniteLoader::setStartupSuppressReadbackstatic 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=truevia Carbonite settings beforeovphysx_create_instanceif they have direct ISettings access, or (b) passingovphysx_config_entry_carbonite("/physics/suppressReadback", "true")increate_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. Seeovphysx_create_argsininclude/ovphysx/ovphysx_types.hfor 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 Pythonimportnow auto-registers ovphysx’s namespaced USD schema/plugin path by appending it toOV_PXR_PLUGINPATH_2511once at module load. Eliminates a class of silent-schema-drop bugs in mixed-process apps (import ovphysx; import ovrtxor vice versa) where USD’sPlugRegistryis 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 inCarboniteLoadercontinues 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 – USD and_bindingsremain deferred to first native-attribute access (PhysX,ContactBinding, …). Failures (e.g. partially-installed checkout with noplugins/usddirectory) are surfaced as a logged warning rather than raised soimport ovphysxdoes not hard-fail. The existing publicregister_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 useovphysx_register_schema_paths()explicitly. Migration: none required. Apps that need an env-var-pure import canos.environ.pop("OV_PXR_PLUGINPATH_2511")afterimport ovphysx.
Added#
add_subdirectory()support for source-link builds. Users can now include ovphysx in their own CMake project viaadd_subdirectory()instead offind_package(), enabling an edit-rebuild workflow where changing ovphysx sources automatically recompiles them. Dependencies are auto-fetched at configure time (OVPHYSX_FETCH_DEPS=ON). See the source-link tutorial andtests/c_samples/hello_world_source_link/for a complete example.ovphysx_attach_ovstage()/ovphysx_update_from_ovstage()/ovphysx_detach_ovstage()for ovstage consumer integration. Pair anovstage_instance_t*with an ovphysx instance at the initial read ordinal, then explicitly drain subsequent committed edits by passing the producer-owned ordinal range toovphysx_update_from_ovstage(handle, from_ordinal, to_ordinal). Init-only attach rejects re-attach withOVPHYSX_API_ERROR; detach is idempotent. Python:PhysX.attach_ovstage(stage)accepts anovstage.Stageor raw handle,PhysX.update_from_ovstage(from_ordinal, to_ordinal)drains later changes, andPhysX.detach_ovstage()detaches the caller-owned stage.Cross-device staged reads AND writes in
ovphysx_read_tensor_binding/ovphysx_write_tensor_binding/ovphysx_write_tensor_binding_masked. A CPU-declaredsrc/dst/mask/indextensor 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 throughIOptionalCuda::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 returnsOVPHYSX_API_DEVICE_MISMATCH.Schema path pre-registration API for multi-subsystem USD processes. New C API
ovphysx_register_schema_paths()and Python helperovphysx.register_schema_paths()append ovphysx’s namespaced USD schema/plugin root toOV_PXR_PLUGINPATH_2511before ovphysx initialization. Use this with peer subsystem equivalents such asovrtx_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. The USD-population availability guard is now narrowed to fire only in full-host mode rather than any-usdrt-user, so partial hosts that provide population utilities but not the physics-schema plugins now trigger ovphysx’s own physics-schema 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_DIAGNOSTICSandconfig.toml build_packagebelow, plus the refuse-branch inCarboniteLoader::initialize()under Changed or Fixed) convert the failure into a named, actionable error instead of a silent plugin-resolution cascade.OVPHYSX_COEXIST_DIAGNOSTICS=1diagnostic env var. Set to enable stderr-only diagnostic logging of the plugin-load and Fabric-acquire paths insideovphysx_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.pluginregistration state, and the Fabric interface-version list as seen by ovphysx. Unset: no behavior change.OVPHYSX_COEXIST_REFUSE=1opt-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=1is 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 static registration /
loadPlugins().CarboniteLoader::initialize()now runstryAcquireInterface<>against the primary interface of each core plugin loaded during static registration (carb.dictionary,carb.settings,carb.tokens,carb.tasking,carb.filesystem). 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 opaquenullptrcascade two layers down into a named, actionable error at load time, mirroring the existingUsdVersionCheckpattern for USD.USD build-package drift diagnostic in
config.toml.config.tomlnow 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/releasesymlink). At runtime,UsdVersionCheckresolves the loaded USD’s canonical path, extracts its packman id, and compares. If the two differ – e.g. ovphysx built against0.25.11.kit.2but another library preloaded0.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
--devschemaflag forwards to ovruntime for local physics schema builds.Multi-GPU scene distribution. Pass
active_cuda_gpus="0,1,2"(all N GPUs) oractive_cuda_gpus="1,2"(all except GPU 0) to distribute scenes round-robin across the specified devices. Internally maps to/physics/sceneMultiGPUMode = eAlloreSkipFirst. Other patterns (e.g."0,2"on a 4-GPU machine) returnOVPHYSX_API_INVALID_ARGUMENTwith 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]), andRIGID_BODY_INV_INERTIA([N,9]). The enum values use previously unused slots afterRIGID_BODY_WRENCH; existing tensor enum values were not reordered.Articulation kinematic update API. New C
ovphysx_update_articulations_kinematic(), PythonPhysX.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_pathsand Covphysx_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.TensorBindingsAPI deformable support. Added volume and surface deformable body tensors (simulation positions, velocities, rest positions, simulation element indices) plus volume collision element indices (
DEFORMABLE_COLLISION_ELEMENT_INDICES_S32,[N,F,4], int32, read-only; K=4 matches the tetrahedral element connectivity exposed byGpuVolumeDeformableBodyView). Surface deformable tensors usecreateSurfaceDeformableBodyViewat bind time and share the same read/write/masked-write paths as volume. All body tensors bind to runtime deformable body views and require DirectGPU mode. Added deformable material tensors for dynamic friction, Young’s modulus, and Poisson’s ratio; material tensors are CPU-backed. Fixed masked write for deformable material tensors (setDynamicFrictionMasked,setYoungsModulusMasked,setPoissonsRatioMasked). Simulation and collision element index tensors report signed int32 DLPack metadata.Python tensor-binding empty-match guard.
PhysX.create_tensor_binding(..., raise_if_empty=True)now raisesValueErrorwhen the binding would match zero prims. The default remainsFalse, preserving zero-count bindings for optional scene elements.Python binding lifetime warnings.
TensorBindingandContactBindingnow emitResourceWarningif garbage collection cleans them up without an explicitdestroy()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, Covphysx_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++
CreateArgstype andPhysX::create(out, CreateArgs).CreateArgsis a safe C++ wrapper forovphysx_create_argsthat default-constructs toOVPHYSX_CREATE_ARGS_DEFAULTand exposes setters fordevice,active_cuda_gpus,bundled_deps_path, and config entries. Replaces the previouscreate(PhysX&, config_entries, count)overload.OmniPVD recording support via typed config. Two new config entries enable
.ovdrecording:omnipvd_output_enabled(bool) andomnipvd_ovd_recording_directory(string). Both must be set at instance creation viaPhysXConfig(Python) orconfig_entriesonovphysx_create_args(C/C++). These entries were previously available only viacarbonite_overrides; they now have first-class typed fields. See OmniPVD Recording tutorial.Source builds now use namespaced USD. ovphysx builds against namespaced monolithic USD (
PXR_NS=ov) so it can coexist with host applications that already load another USD runtime. Migration for source builds: update build scripts to fetch namespaced USD packages; classic modular USD is no longer supported.
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=truebefore instance creation. Behavior is unchanged.ovphysx startup now appends its namespaced USD schema path when
OV_PXR_PLUGINPATH_2511is already set. PreviouslyCarboniteLoaderonly 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 asovphysx_register_schema_paths()and does not modifyPXR_PLUGINPATH_NAME. Startup and explicit pre-registration fail fast if ovphysx cannot find an existingplugins/usddirectory, 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 preservesxformOp:scalefrom source prims. Previously, clones of prims with non-unit scale appeared at incorrect world-space size. The fix reads the source’s scale (supporting bothdouble3andfloat3attributes), writes it to each target, includes it inxformOpOrder, and incorporates it into thelocalMatrix.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 strictPhysxContactReportAPIrequirement 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 callsensure_physics_attached()before stepping, matching the asyncovphysx_step()path. Previously, the firststep_syncafter stage ingestion could run against an unattached stage, so 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 ofsys.prefixin child processes that spawn venv interpreters (Windows; e.g. rerun.io).Unified build flags.
build.sh/build.batnow 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);-tis now--target <name>(use--test-venvfor the old test-venv behavior).create_articulation_view()andcreate_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/recursiveLeafPatternMatchtofalseto 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 tofalseor 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_DIRremoved. All runtime paths are now derived fromOVPHYSX_LIB(the single dev-mode override for the shared library path). Migration: replace any of the removed env vars withOVPHYSX_LIBpointing at the shared library.ovphysx_set_shutting_down()removed. Full teardown now happens automatically when the last instance is destroyed viaovphysx_destroy_instance()(C/C++) orrelease()(Python). Migration: remove anyovphysx_set_shutting_down()calls.Legacy Python TensorAPI compatibility removed.
ovphysx.tensorsis no longer shipped or exported. Migration: replaceovphysx.tensorsimports with TensorBindingsAPI: usephysx.create_tensor_binding()to create bindings, thenbinding.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_countonovphysx_create_args;ovphysx_set_global_setting()/ovphysx_get_global_setting(); PythonPhysX(settings=...),set_setting(),get_setting(); C++PhysX::setSetting()/getSetting()) are all removed. Migration: C — useovphysx_config_entry_tarray onconfig_entries/config_entry_countwith builders fromovphysx_config.h, runtimeovphysx_set_global_config()and typed gettersovphysx_get_global_config_bool/int32/float/string(). Python — usePhysX(config=PhysXConfig(num_threads=4)), runtimeset_config_bool()/set_config_int32()and matching getters. Arbitrary Carbonite paths remain accessible viaovphysx_config_entry_carbonite()(C/C++) orPhysXConfig(carbonite_overrides={...})(Python).OVPHYSX_CONFIG_CUDA_DEVICEremoved fromovphysx_config_int32_t. Migration: useactive_cuda_gpusonovphysx_create_args(C) orPhysX(active_cuda_gpus=...)(Python). Setting/physics/cudaDeviceviacarbonite_overridesnow 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 viacarbonite_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_tandovphysx_enqueue_result_tno longer have anerrorfield. Migration: on failure, callovphysx_get_last_error()on the same thread to retrieve the error message (valid until the next ovphysx API call on that thread). Forwait_op, iterateerror_op_indicesand callovphysx_get_last_op_error()per failed index, thenovphysx_destroy_wait_result(&result). Removed:ovphysx_destroy_error(),ovphysx_destroy_errors(). Python API is unaffected (errors are raised as exceptions).ContactEventHeader.stageIdtype changed fromlongtoint64_tfor 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 ofconst void**, and gained 2 new parameters (out_friction_anchors,out_num_friction_anchors). Migration: update pointer types and append, NULL, NULLfor friction anchor parameters. C++ callers are unaffected (new params default tonullptr).Removed
ovphysx_articulation_get_dof_count,_body_count,_joint_count,_is_fixed_base,_fixed_tendon_count,_spatial_tendon_count. Migration: useovphysx_get_articulation_metadata(handle, binding, &meta). PythonTensorBindingproperties are unchanged.ovphysx_clone()parameterparent_positions_xyzreplaced withparent_transforms(7 floats per target: px, py, pz, qx, qy, qz, qw). Migration: replaceparent_positions_xyz=[x, y, z]withparent_transforms=[px, py, pz, 0, 0, 0, 1](append identity quaternion).Removed
ovphysx_set_clone_env_root()(C) andset_clone_env_root()(Python). Migration: no replacement needed — the firstclone(),simulate(), orwarmup_gpu()call triggers stage attach lazily.ovphysx_device_tenum 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_F32renamed toARTICULATION_CORIOLIS_AND_CENTRIFUGAL_FORCE(enum value 72 unchanged). Migration: update references to the old name.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). Migration: update to the new constant names.Python enum cleanup. All
OVPHYSX_TENSOR_*_F32bare-int constants removed (useTensorType.*members);OVPHYSX_API_*,OVPHYSX_LOG_*,OVPHYSX_DEVICE_*constants removed (useApiStatus.*,LogLevel.*,DeviceType.*);OVPHYSX_OP_INDEX_ALLrenamed toOP_INDEX_ALL;kDLCPU,kDLCUDA,kDLInt,kDLUInt,kDLFloatremoved (useDLDeviceType.kDLCPU/DLDataTypeCode.kDLFloat).Removed
ovphysx_finalize(). Useovphysx_shutdown()for process lifecycle shutdown andovphysx_destroy_instance()for handles. Migration: remove anyovphysx_finalize()calls.
Added#
ovphysx_articulation_metadata_tstruct andovphysx_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,ConfigStringIntEnums in Python for typed config key access.PhysXConfigdataclass in Python for typed initialization config.Remote USD loading through ovstage population accepts remote URIs (
omniverse://, S3, Azure Blob). Use HTTPS virtual-hosted S3 URLs. Useovphysx_configure_s3()/configure_s3()andovphysx_configure_azure_sas()/configure_azure_sas()for credential setup before population.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. PythonContactBindingclass 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 underinclude/physx/. See PhysX Interop tutorial.Contact report API:
ovphysx_get_contact_report()exposes per-step contact data as typed C struct pointers. PythonPhysX.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 = 6status code.CMake package config:
find_package(ovphysx)providesovphysx::ovphysxtarget and Windowsovphysx_copy_runtime_dlls()helper.Visual sample using Rerun for rigid body visualization (Rendering Handoff).
Linux aarch64 support.
Changed or Fixed#
Physics scene parsing is deferred until the first simulation attach path. This avoids GPU buffer corruption when
clone()adds environments after the initial scene update. Correct sequence: ovstage population +attach_ovstage()->clone()->warmup_gpu()/step(); callupdate_from_ovstage()only for later authored edits.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_bindingnow validatessensor_patternsis non-empty andfilter_patternslength.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 validatesusd_handleand returnsOVPHYSX_API_NOT_FOUNDfor 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.