Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -850,16 +850,14 @@ jobs:
container-name: "isaac-lab-rendering-correctness-kitless-legacy-test"
omni-github-test-type: "rendering-correctness-kitless-legacy"

# ovstage is post-merge only; it never ran on pull requests.
# Validation branch: run OVStage on pull requests to exercise the combined OV runtime pins.
test-rendering-correctness-kitless-ovstage:
name: "rendering-correctness-kitless (ovstage)"
runs-on: [self-hosted, gpu]
timeout-minutes: 120
continue-on-error: ${{ github.event_name == 'workflow_dispatch' }}
needs: [build, config]
if: >-
github.event_name != 'pull_request' &&
needs.build.result == 'success'
if: needs.build.result == 'success'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Warning · Implementation — OVStage rendering job now runs on every PR

The removed github.event_name != 'pull_request' guard kept this 120-minute self-hosted GPU job post-merge only, and the added comment labels it a "Validation branch" change to exercise the new pins. As written, every pull request permanently gains a second full kitless rendering matrix, and the test-node-ids-file/test-node-ids-key inputs still apply only on push, so PR runs execute the uncurated matrix. Restore the post-merge condition before merging.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the intention - test pr

env:
USE_OVPHYSX_WHEELHOUSE: ${{ needs.config.outputs.ovphysx_wheelhouse_image != '' && ((github.event_name == 'pull_request' && github.base_ref == 'develop' && github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name != 'pull_request' && github.ref_name == 'develop')) }}
steps:
Expand Down
16 changes: 7 additions & 9 deletions .github/workflows/license-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,13 @@ jobs:

# If the package is in the exceptions list
if [ -n "$EXCEPTION" ]; then
# If the license is provided in the exceptions list, check the license
EXCEPTION_LICENSE=$(echo "$EXCEPTION" | jq -r '.license')

# echo "Comparing licenses for $pkg:"
# echo " EXCEPTION_LICENSE='${EXCEPTION_LICENSE}' (len=${#EXCEPTION_LICENSE})"
# echo " LICENSE='${LICENSE}' (len=${#LICENSE})"

# If the exceptions list has a license and doesn't match the current license
if [ "$EXCEPTION_LICENSE" != "null" ] && [ "$EXCEPTION_LICENSE" != "$LICENSE" ]; then
# If licenses are provided in the exception, require the primary value or an alias.
LICENSE_MATCH=$(echo "$EXCEPTION" | jq -r --arg license "$LICENSE" '
(.license == null)
or (.license == $license)
or any(.license_aliases[]?; . == $license)
')
if [ "$LICENSE_MATCH" != "true" ]; then
echo "ERROR: $pkg has license: $LICENSE"
FAILED_PACKAGES=$((FAILED_PACKAGES + 1)) # Increment the counter
fi
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/license-exceptions.json
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,9 @@
"package": "ovrtx",
"package_type": "Python",
"license": "NVIDIA Proprietary Software",
"license_aliases": [
"LicenseRef-NvidiaProprietary"
],
"usage": "runtime_dependency",
"interaction": "same_process_dynamic",
"linkage": "dynamic",
Expand Down
2 changes: 1 addition & 1 deletion docs/_extensions/isaaclab_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ def run(self) -> list[nodes.Node]:
content = f"""\
.. code-block:: bash

pip install --extra-index-url https://pypi.nvidia.com "ovrtx{spec}"
pip install "ovrtx{spec}"
"""
return _parse_rst(self, content)

Expand Down
3 changes: 2 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ def _read_pinned_versions() -> dict:
isaacsim_version = _pinned_versions["isaacsim"]
torch_version = _pinned_versions["torch"]
torchvision_version = _pinned_versions["torchvision"]
ovrtx_spec = _pinned_versions["ovrtx"]
ovrtx_version = _pinned_versions["ovrtx"]
ovrtx_spec = f"=={ovrtx_version}" if ovrtx_version[0].isdigit() else ovrtx_version
ovphysx_version = _pinned_versions["ovphysx"]

# Short version strings used in external documentation URLs and badges.
Expand Down
6 changes: 3 additions & 3 deletions docs/source/overview/core-concepts/renderers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ Installing the OVRTX renderer

The OVRTX renderer is provided by the ``isaaclab_ov`` extension. The extension's
source package ships with the core install, but the renderer's ``ovrtx`` runtime
wheel (the `ovrtx <https://github.com/NVIDIA-Omniverse/ovrtx>`_ package, hosted on
``pypi.nvidia.com``) is **not** installed by default. You must request it
wheel (the `ovrtx <https://github.com/NVIDIA-Omniverse/ovrtx>`_ package, published
on public PyPI) is **not** installed by default. You must request it
explicitly — OVRTX does **not** require Isaac Sim.

Install via the Isaac Lab CLI using the ``ov[ovrtx]`` token:
Expand All @@ -133,7 +133,7 @@ Install via the Isaac Lab CLI using the ``ov[ovrtx]`` token:
packages are already part of the core install). Use ``ov[ovrtx]`` (or ``ov[all]``)
to pull in the ``ovrtx`` dependency.

Or install the ``ovrtx`` runtime wheel directly with pip (note the extra index URL):
Or install the public ``ovrtx`` package directly from PyPI:

.. isaaclab-ovrtx-install::

Expand Down
13 changes: 7 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,9 @@ rerun = [

isaacsim = ["isaacsim[all,extscache]==6.0.1.0"]

ov = ["ovphysx==0.5.9", "ovrtx>0.4.0,<0.4.1", "ovstage==0.1.0.346039"]
ovphysx = ["ovphysx==0.5.9", "ovstage==0.1.0.346039"]
ovrtx = ["ovrtx>0.4.0,<0.4.1", "ovstage==0.1.0.346039"]
ov = ["ovphysx==0.5.10", "ovrtx==0.4.1.364340", "ovstage==0.1.1.355824"]
ovphysx = ["ovphysx==0.5.10", "ovstage==0.1.1.355824"]
ovrtx = ["ovrtx==0.4.1.364340", "ovstage==0.1.1.355824"]

mimic = [
"isaaclab-mimic",
Expand Down Expand Up @@ -211,9 +211,9 @@ usd_exchange = "2.3.0"
torch = "2.11.0"
torchvision = "0.26.0"
torchaudio = "2.11.0"
ovphysx = "0.5.9"
ovrtx = ">0.4.0,<0.4.1"
ovstage = "0.1.0.346039"
ovphysx = "0.5.10"
ovrtx = "0.4.1.364340"
ovstage = "0.1.1.355824"
newton = "release-1.5"
warp = "1.16.0"

Expand Down Expand Up @@ -417,6 +417,7 @@ package = false
[tool.uv.sources]
omniverseclient = { index = "pypi-public" }
ovphysx = { index = "pypi-public" }
ovrtx = { index = "pypi-public" }
ovstage = { index = "pypi-public" }
isaaclab = { path = "source/isaaclab", editable = true }
"isaaclab-assets" = { path = "source/isaaclab_assets", editable = true }
Expand Down
1 change: 1 addition & 0 deletions source/isaaclab/changelog.d/esekkin-ovrtx-041.skip
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test-only: verified that the public OVRTX package resolves through the public PyPI index.
2 changes: 2 additions & 0 deletions source/isaaclab/changelog.d/ovphysx-0510-pins.skip
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Only the pinned OvPhysX version asserted by ``test_version_single_source_matches_literal_pins``
changed; there is no user-visible behaviour change in ``isaaclab``.
8 changes: 3 additions & 5 deletions source/isaaclab/test/cli/test_uv_run_pyproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def test_version_single_source_matches_literal_pins():
optional = pyproject["project"]["optional-dependencies"]
overrides = pyproject["tool"]["uv"]["override-dependencies"]

assert versions["ovphysx"] == "0.5.9"
assert versions["ovphysx"] == "0.5.10"
assert "omniverseclient==2.72.3" in dependencies

# Isaac Sim extra mirrors the table, and the teleop extra repeats the same pin.
Expand Down Expand Up @@ -167,9 +167,7 @@ def spec(package: str) -> str:
line.strip() for line in build_workflow.splitlines() if "extra-pip-packages:" in line and "ovrtx" in line
]
assert ovrtx_install_lines
assert all(
f"ovrtx{versions['ovrtx']}" in line or "steps.ov_pins.outputs.ovrtx" in line for line in ovrtx_install_lines
)
assert all(spec("ovrtx") in line or "steps.ov_pins.outputs.ovrtx" in line for line in ovrtx_install_lines)

# uv torch-stack overrides mirror the table.
for package in ("torch", "torchvision", "torchaudio"):
Expand All @@ -195,7 +193,7 @@ def test_public_ov_packages_use_public_pypi_index():
"url": "https://pypi.org/simple",
"explicit": True,
}
for package in ("omniverseclient", "ovphysx", "ovstage"):
for package in ("omniverseclient", "ovphysx", "ovrtx", "ovstage"):
assert sources[package] == {"index": "pypi-public"}


Expand Down
6 changes: 6 additions & 0 deletions source/isaaclab_ov/changelog.d/esekkin-ovrtx-041.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Changed
^^^^^^^

* Updated the optional OVRTX runtime dependency to the public ``ovrtx==0.4.1.364340`` package and
enabled synchronous texture streaming for deterministic material readiness. Reinstall the OVRTX
extra with ``uv sync --extra ovrtx`` to use the supported runtime.
6 changes: 6 additions & 0 deletions source/isaaclab_ov/changelog.d/ovphysx-0510-pins.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Changed
^^^^^^^

* Updated OvPhysX to 0.5.10 and the OpenUSD stage bindings to ovstage 0.1.1.355824.
The two move together: OvPhysX 0.5.10 requires that exact ovstage build, so bumping
it alone leaves the environment unresolvable.
63 changes: 13 additions & 50 deletions source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@
Renderer,
RendererConfig,
Semantic,
TextureStreamingMode,
)
except ModuleNotFoundError as exc:
if exc.name != "ovrtx":
raise
raise ModuleNotFoundError(
"The OVRTX renderer requires the optional 'ovrtx' runtime wheel, which is not installed. "
"Run your command with: uv run --extra ovrtx <command> "
"(or, manually: python -m pip install --extra-index-url https://pypi.nvidia.com "
"'ovrtx>0.4.0,<0.4.1')."
"(or, manually: python -m pip install 'ovrtx==0.4.1.364340')."
) from exc

from isaaclab.cloner.clone_plan import ClonePlan
Expand Down Expand Up @@ -376,14 +376,9 @@ def __init__(self, cfg: OVRTXRendererCfg):
log_level=self.cfg.log_level,
read_gpu_transforms=_read_gpu_transforms_enabled(),
keep_system_alive=True,
suppress_deprecation_warnings=True,
texture_streaming_mode=TextureStreamingMode.SYNCHRONOUS,
)
# Isaac Lab still drives ovrtx's legacy stage API until the ovstage path is the default, so
# its deprecation warnings are noise no user of this renderer can act on. Set after
# construction because ``RendererConfig`` is a plain dataclass and wheels predating the
# option would reject it as an unexpected keyword argument.
# TODO: Remove this once the ovstage path is the default and the legacy stage API is removed.
if hasattr(OVRTX_CONFIG, "suppress_deprecation_warnings"):
OVRTX_CONFIG.suppress_deprecation_warnings = True

self._renderer = Renderer(OVRTX_CONFIG)
if not self._renderer:
Expand Down Expand Up @@ -450,15 +445,14 @@ def _init_fields_legacy(self) -> None:
"""Initialize the legacy-path instance fields.

Counterpart to :meth:`_init_fields_ovstage`. Only fields the ovstage path never touches live
here: the four ``bind_attribute``/``bind_array_attribute`` handles and the
``UsdGeom.Points`` seeding flag. State shared by both paths (``_object_newton_indices``, the
particle offset/count lists) stays in :meth:`__init__`.
here: the four ``bind_attribute``/``bind_array_attribute`` handles. State shared by both
paths (``_object_newton_indices``, the particle offset/count lists) stays in
:meth:`__init__`.
"""
self._camera_xform_binding = None
self._object_xform_binding = None
self._deformable_points_binding = None
self._particle_points_binding = None
self._particle_workaround_applied = False

def _initialize_from_spec_legacy(self, spec: CameraRenderSpec):
"""Initialize the OVRTX renderer with internal environment cloning.
Expand Down Expand Up @@ -799,8 +793,6 @@ def _setup_particle_bindings_legacy(self) -> None:
flags=BindingFlag.OPTIMIZE,
)

self._particle_workaround_applied = False

def create_render_data(self, spec: CameraRenderSpec) -> OVRTXRenderData:
"""Create OVRTX-specific RenderData with GPU buffers.

Expand Down Expand Up @@ -897,16 +889,12 @@ def _update_geometries_legacy(self) -> None:
)

if self._particle_points_binding is not None:
if not self._particle_workaround_applied:
self._apply_particle_workaround(particle_q)
self._particle_workaround_applied = True
else:
self._write_particle_q_slices(
self._particle_points_binding,
particle_q,
self._particle_visual_offsets,
self._particle_visual_counts,
)
self._write_particle_q_slices(
self._particle_points_binding,
particle_q,
self._particle_visual_offsets,
self._particle_visual_counts,
)

def _write_particle_q_slices(
self,
Expand Down Expand Up @@ -942,24 +930,6 @@ def _write_particle_q_slices(
cuda_stream=cuda_stream,
)

def _apply_particle_workaround(self, particle_q: wp.array) -> None:
"""Host-SYNC seed ``UsdGeom.Points`` so later GPU ASYNC writes can work correctly.

OVRTX does not initialize UsdGeom.Points prims from a GPU ASYNC ``points`` write as expected.
A host SYNC write + a renderer step call are needed to finish initialization; later frames
can then use zero-copy GPU ASYNC write with the same binding.

TODO: The workaround will be removed when OVRTX fixes the issue (OMPE-102610).
"""
particle_q_host = particle_q.numpy()
host_slices = [
particle_q_host[particle_offset : particle_offset + particle_count]
for particle_offset, particle_count in zip(
self._particle_visual_offsets, self._particle_visual_counts, strict=True
)
]
self._particle_points_binding.write(cast(Any, host_slices), data_access=DataAccess.SYNC)

def _update_camera_legacy(
self,
render_data: OVRTXRenderData,
Expand Down Expand Up @@ -1425,7 +1395,6 @@ def _safe_unbind(binding, name: str) -> None:
self._deformable_particle_counts = []
self._particle_visual_offsets = []
self._particle_visual_counts = []
self._particle_workaround_applied = False

if self._renderer:
try:
Expand Down Expand Up @@ -1942,12 +1911,6 @@ def _setup_particle_bindings_ovstage(self) -> None:
if self._particle_points_query is None:
raise RuntimeError("Failed to create OVRTX particle point bindings")

# Note: no ``UsdGeom.Points`` seeding workaround is needed here. The legacy path's
# ``_apply_particle_workaround`` (OMPE-102610) exists because OVRTX fails to initialize
# Points prims from a zero-copy GPU ASYNC ``points`` write through ``bind_array_attribute``.
# The ovstage path instead writes host numpy DLTensors through ``Stage.write_attribute``,
# which populates the column synchronously, so the prims are valid from the first frame.

def _update_transforms_ovstage(self) -> None:
if self._object_xform_query is None or self._object_newton_indices is None:
return
Expand Down
2 changes: 1 addition & 1 deletion source/isaaclab_ov/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ requires-python = ">=3.12"
# Only interpackage dependencies should go here.
dependencies = [
"isaaclab",
"ovstage==0.1.0.346039",
"ovstage==0.1.1.355824",
]

[tool.uv.sources]
Expand Down
Loading
Loading