-
Notifications
You must be signed in to change notification settings - Fork 3.8k
[Task Clean-up][OVPhysX] Dexterous Part 2/8: Fix articulation and manager runtime #6412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1ae1e00
1f7a433
9f4cb14
b3e59bc
3dd8718
eb23c8e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| Fixed | ||
| ^^^^^ | ||
|
|
||
| * Fixed OVPhysX actuator joint indices to follow the common actuator indexing contract. | ||
| * Fixed OVPhysX initialization alongside Kit by reusing Kit's registered PhysX schema provider. | ||
| * Fixed the OVPhysX manager to support both the declared public runtime API and the current runtime API. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ | |
| from __future__ import annotations | ||
|
|
||
| import atexit | ||
| import inspect | ||
| import logging | ||
| import os | ||
| import re | ||
|
|
@@ -272,8 +273,10 @@ def _ensure_physx_schemas_registered(cls) -> None: | |
| runs it must be registered manually before the wheel can match | ||
| ``PhysxContactReportAPI`` and friends on the stage. The wheel | ||
| bundles the plugin under ``ovphysx/plugins/usd/PhysxSchema``. This | ||
| method is idempotent — :meth:`pxr.Plug.Registry.RegisterPlugins` | ||
| is a no-op once the plugin is registered. | ||
| method is idempotent and leaves an existing Kit ``physxSchema`` | ||
| provider authoritative. Registering the wheel's provider after Kit's | ||
| provider raises duplicate-type errors even though both plugins share | ||
| the same name. | ||
| """ | ||
| if cls._physx_schemas_registered: | ||
| return | ||
|
|
@@ -285,11 +288,15 @@ def _ensure_physx_schemas_registered(cls) -> None: | |
| from pxr import Plug # noqa: PLC0415 | ||
| except Exception: | ||
| return | ||
| registry = Plug.Registry() | ||
| if any(plugin.name == "physxSchema" for plugin in registry.GetAllPlugins()): | ||
| cls._physx_schemas_registered = True | ||
| return | ||
| plugin_root = os.path.join(os.path.dirname(ovphysx.__file__), "plugins", "usd") | ||
| for sub in ("PhysxSchema/resources", "PhysxSchemaAddition/resources"): | ||
| path = os.path.join(plugin_root, sub) | ||
| if os.path.isdir(path): | ||
| Plug.Registry().RegisterPlugins(path) | ||
| registry.RegisterPlugins(path) | ||
| cls._physx_schemas_registered = True | ||
|
|
||
| @classmethod | ||
|
|
@@ -350,10 +357,25 @@ def step(cls) -> None: | |
| if cls._physx is None: | ||
| return | ||
| dt = cls.get_physics_dt() | ||
| cls._physx.step_sync(dt=dt) | ||
| cls._step_physx(cls._physx, dt=dt, sim_time=PhysicsManager._sim_time) | ||
| cls._physx.update_articulations_kinematic() | ||
| PhysicsManager._sim_time += dt | ||
|
|
||
| @staticmethod | ||
| def _step_physx(physx: Any, dt: float, sim_time: float) -> None: | ||
| """Step either the declared legacy runtime or the trusted current runtime.""" | ||
| if hasattr(physx, "reset_stage"): | ||
| physx.step_sync(dt=dt) | ||
| else: | ||
| physx.step_sync(dt=dt, sim_time=sim_time) | ||
|
|
||
| @staticmethod | ||
| def _reset_physx_stage(physx: Any) -> None: | ||
| """Clear the loaded stage through the runtime's available reset API.""" | ||
| reset = physx.reset_stage if hasattr(physx, "reset_stage") else physx.reset | ||
| operation = reset() | ||
| physx.wait_op(operation) | ||
|
|
||
| @classmethod | ||
| def close(cls) -> None: | ||
| """Release ovphysx resources and clean up.""" | ||
|
|
@@ -378,7 +400,7 @@ def close(cls) -> None: | |
| def _release_physx(cls) -> None: | ||
| """Soft-reset the ovphysx runtime stage; keep the C++ instance alive. | ||
|
|
||
| Calls ``physx.reset_stage()`` to clear the loaded scene, but does **not** | ||
| Clears the loaded scene through the runtime's reset API, but does **not** | ||
| drop the Python reference. The cached :class:`ovphysx.PhysX` is reused by | ||
| the next :class:`~isaaclab.sim.SimulationContext` via the reuse path in | ||
| :meth:`_warmup_and_load`. Safe to call multiple times. | ||
|
|
@@ -395,8 +417,7 @@ def _release_physx(cls) -> None: | |
| namespace-isolated Carbonite (different soname / hidden visibility). | ||
| """ | ||
| if cls._physx is not None: | ||
| op = cls._physx.reset_stage() | ||
| cls._physx.wait_op(op) | ||
| cls._reset_physx_stage(cls._physx) | ||
|
|
||
| @classmethod | ||
| def get_physx_instance(cls) -> Any: | ||
|
|
@@ -583,13 +604,12 @@ def _warmup_and_load(cls) -> None: | |
| cls._locked_device = ovphysx_device | ||
| else: | ||
| # Reuse path: the cached PhysX may still hold the prior stage (the | ||
| # wheel allows only one loaded USD at a time). ``physx.reset_stage()`` | ||
| # is idempotent on an already-cleared stage and required when this is | ||
| # wheel allows only one loaded USD at a time). Clearing the stage is | ||
| # idempotent on an already-cleared stage and required when this is | ||
| # a second :meth:`_warmup_and_load` within the same SimulationContext | ||
| # (e.g. when a caller manually clears ``_warmup_done`` to force a | ||
| # re-warmup). | ||
| op = cls._physx.reset_stage() | ||
| cls._physx.wait_op(op) | ||
| cls._reset_physx_stage(cls._physx) | ||
|
|
||
| usd_handle, op_idx = cls._physx.add_usd(stage_file) | ||
| cls._physx.wait_op(op_idx) | ||
|
|
@@ -661,28 +681,7 @@ def _construct_physx(cls, ovphysx_device: str, gpu_index: int) -> None: | |
| _sys.modules.update(_hidden_pxr) | ||
|
|
||
| ovphysx = import_ovphysx() | ||
| ovphysx.PhysX.set_cpu_mode(ovphysx_device == "cpu") | ||
|
|
||
| carbonite_overrides = { | ||
| "/physics/physxDispatcher": True, | ||
| "/physics/updateToUsd": False, | ||
| "/physics/updateVelocitiesToUsd": False, | ||
| "/physics/updateParticlesToUsd": False, | ||
| } | ||
| if ovphysx_device == "gpu": | ||
| carbonite_overrides.update( | ||
| { | ||
| "/physics/suppressReadback": True, | ||
| "/physics/suppressFabricUpdate": True, | ||
| } | ||
| ) | ||
| physx_kwargs = { | ||
| "config": ovphysx.PhysXConfig(num_threads=8, carbonite_overrides=carbonite_overrides), | ||
| } | ||
| if ovphysx_device == "gpu": | ||
| physx_kwargs["active_cuda_gpus"] = str(gpu_index) | ||
|
|
||
| cls._physx = ovphysx.PhysX(**physx_kwargs) | ||
| cls._physx = cls._create_physx_instance(ovphysx, ovphysx_device, gpu_index) | ||
|
|
||
| # FIXME(malesiani): re-evaluate this when carbonite ships an isolated copy. | ||
| # At process exit, two Carbonite instances are in memory: | ||
|
|
@@ -717,6 +716,71 @@ def _atexit_release_and_exit(): | |
| atexit.register(_atexit_release_and_exit) | ||
| cls._atexit_registered = True | ||
|
|
||
| @staticmethod | ||
| def _create_physx_instance(ovphysx: Any, ovphysx_device: str, gpu_index: int) -> Any: | ||
| """Create a PhysX instance for the declared or current OVPhysX runtime API. | ||
|
|
||
| Args: | ||
| ovphysx: Imported OVPhysX runtime module. | ||
| ovphysx_device: Physics device, either ``"cpu"`` or ``"gpu"``. | ||
| gpu_index: CUDA device ordinal selected for GPU physics. | ||
|
|
||
| Returns: | ||
| The configured ``ovphysx.PhysX`` instance. | ||
| """ | ||
|
|
||
| carbonite_overrides = { | ||
| "/physics/physxDispatcher": True, | ||
| "/physics/updateToUsd": False, | ||
| "/physics/updateVelocitiesToUsd": False, | ||
| "/physics/updateParticlesToUsd": False, | ||
| } | ||
| if ovphysx_device == "gpu": | ||
| carbonite_overrides.update( | ||
| { | ||
| "/physics/suppressReadback": True, | ||
| "/physics/suppressFabricUpdate": True, | ||
| } | ||
| ) | ||
| if hasattr(ovphysx.PhysX, "set_cpu_mode"): | ||
| ovphysx.PhysX.set_cpu_mode(ovphysx_device == "cpu") | ||
| physx_kwargs = { | ||
| "config": ovphysx.PhysXConfig(num_threads=8, carbonite_overrides=carbonite_overrides), | ||
| } | ||
| if ovphysx_device == "gpu": | ||
| physx_kwargs["active_cuda_gpus"] = str(gpu_index) | ||
| return ovphysx.PhysX(**physx_kwargs) | ||
|
|
||
| physx_kwargs = {"device": ovphysx_device} | ||
| try: | ||
| physx_parameters = inspect.signature(ovphysx.PhysX).parameters | ||
| except (TypeError, ValueError): | ||
| # C-extension constructors may not expose a Python-visible signature | ||
| physx_parameters = {} | ||
| if "active_cuda_gpus" in physx_parameters and ovphysx_device == "gpu": | ||
| physx_kwargs["active_cuda_gpus"] = str(gpu_index) | ||
| physx_kwargs["config"] = ovphysx.PhysXConfig( | ||
| carbonite_overrides={ | ||
| "/physics/suppressReadback": True, | ||
| "/physics/suppressFabricUpdate": True, | ||
| } | ||
| ) | ||
| elif "gpu_index" in physx_parameters: | ||
| physx_kwargs["gpu_index"] = gpu_index | ||
|
|
||
| physx = ovphysx.PhysX(**physx_kwargs) | ||
| if hasattr(physx, "set_setting"): | ||
| physx.set_setting("/persistent/physics/numThreads", "8") | ||
| physx.set_setting("/physics/physxDispatcher", "true") | ||
| physx.set_setting("/physics/updateToUsd", "false") | ||
| physx.set_setting("/physics/updateVelocitiesToUsd", "false") | ||
| physx.set_setting("/physics/updateParticlesToUsd", "false") | ||
| else: | ||
| # the declared legacy runtime exposes no generic settings API, so | ||
| # only the thread count can be applied post-construction | ||
| physx.set_config_int32(ovphysx.ConfigInt32.NUM_THREADS, 8) | ||
|
Comment on lines
+772
to
+781
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The If the declared legacy wheel defaults |
||
| return physx | ||
|
|
||
| @staticmethod | ||
| def _configure_physx_scene_prim(scene_prim, cfg, device: str) -> None: | ||
| """Apply PhysxSceneAPI schema and device-specific scene attributes to the | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
torch.int32index tensor may not be accepted by all PyTorch advanced-indexing pathsactuator_joint_idsis created withdtype=torch.int32and is immediately used to index several.torchtensors at lines 3943–3950. Standard PyTorch advanced indexing expectsint64(LongTensor); older bundled PyTorch versions raiseRuntimeError: expected scalar type Long but found Intforint32index tensors. Usingdtype=torch.int64(or inserting.long()casts at the indexing sites) would eliminate this version dependency.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!