Skip to content

Jakob/port examples to cloudxrlauncher#703

Open
nv-jakob wants to merge 2 commits into
NVIDIA:mainfrom
nv-jakob:jakob/port-examples-to-cloudxrlauncher
Open

Jakob/port examples to cloudxrlauncher#703
nv-jakob wants to merge 2 commits into
NVIDIA:mainfrom
nv-jakob:jakob/port-examples-to-cloudxrlauncher

Conversation

@nv-jakob

@nv-jakob nv-jakob commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes #(issue)

Type of change

  • New feature (non-breaking change which adds functionality)

Testing

Checklist

  • I have read and understood the contribution guidelines
  • I have run the linter and formatter with SKIP=check-copyright-year pre-commit run --all-files
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix/feature works (or explained why not)
  • I have signed off all my commits (git commit -s) per the DCO

Summary by CodeRabbit

  • New Features

    • Added CloudXR launcher support across multiple examples, so live teleoperation and tracking flows can start and stop the required runtime automatically.
    • Added CLI options to control CloudXR startup behavior, including choosing an install directory and disabling automatic runtime launch.
    • Improved Windows support for shutting down CloudXR runtime processes cleanly.
  • Documentation

    • Updated example guidance to reflect the new automatic CloudXR startup flow.

nv-jakob added 2 commits June 24, 2026 16:34
Expose argparse helpers so embedding apps and examples can default to
launching the CloudXR runtime and WSS proxy, with --no-launch-cloudxr-runtime
for environments where the runtime is already running.

Signed-off-by: Jakob Bornecrantz <tbornecrantz@nvidia.com>
Wrap examples that need a running CloudXR runtime with CloudXRLauncher,
using the new --launch-cloudxr-runtime / --no-launch-cloudxr-runtime flags
where argparse is already present. Skips synthetic-plugin and mcap replay
examples that do not require a live runtime.

Signed-off-by: Jakob Bornecrantz <tbornecrantz@nvidia.com>
@github-actions

Copy link
Copy Markdown
Contributor

📝 Docs preview is not auto-deployed for fork PRs.

A maintainer with write access to NVIDIA/IsaacTeleop can deploy a preview by
commenting /preview-docs on this PR. Once deployed, the preview
will live at:

https://nvidia.github.io/IsaacTeleop/preview/pr-703/

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

CloudXRLauncher now exposes CLI helper methods and a launch_context(args) helper, and its shutdown path branches on Windows with a new direct-process terminator. Multiple example scripts now import the launcher and run their existing OpenXR or teleoperation flows inside with CloudXRLauncher(): or CloudXRLauncher.launch_context(args), and one example updates its runtime docs and timeout message. Tests cover the new argument helpers and Windows stop behavior.

Sequence Diagram(s)

sequenceDiagram
  participant ExampleMain as "main()"
  participant CloudXRLauncher
  participant CloudXRRuntime as "CloudXR runtime"
  participant TeleopSession
  participant OpenXRSession as "oxr.OpenXRSession"

  ExampleMain->>CloudXRLauncher: launch_context(args)
  CloudXRLauncher->>CloudXRRuntime: start runtime
  alt teleop example
    ExampleMain->>TeleopSession: run session loop
  else OpenXR example
    ExampleMain->>OpenXRSession: create and run session
  end
  ExampleMain->>CloudXRLauncher: exit context
  CloudXRLauncher->>CloudXRRuntime: stop runtime
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: porting examples to CloudXRLauncher.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
examples/teleop/python/joint_space_device_example.py (1)

217-229: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Start CloudXR before spawning the synthetic plugin.

The default flow now launches so101_leader_plugin before entering CloudXRLauncher.launch_context(args), so the plugin is asked to create/push against an OpenXR runtime that is not running yet. That makes --launch-plugin race or time out unless the user manually disables launcher startup and pre-starts CloudXR.

Suggested fix
     plugin_proc = None
-    if args.launch_plugin:
-        if not Path(args.plugin_bin).exists():
-            raise SystemExit(
-                f"plugin binary not found: {args.plugin_bin} (build it first)"
-            )
-        print(f"launching plugin: {args.plugin_bin}")
-        # Empty device_path -> synthetic backend; collection id must match the source.
-        plugin_proc = subprocess.Popen([args.plugin_bin, "", _COLLECTION_ID])
-        time.sleep(1.5)  # let it create its OpenXR session and start pushing
     try:
         with CloudXRLauncher.launch_context(args):
+            if args.launch_plugin:
+                if not Path(args.plugin_bin).exists():
+                    raise SystemExit(
+                        f"plugin binary not found: {args.plugin_bin} (build it first)"
+                    )
+                print(f"launching plugin: {args.plugin_bin}")
+                plugin_proc = subprocess.Popen([args.plugin_bin, "", _COLLECTION_ID])
+                time.sleep(1.5)
             run_live(args.mode, args.frames, args.urdf, args.ee_link, args.timeout)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/teleop/python/joint_space_device_example.py` around lines 217 - 229,
Move the CloudXR startup in joint_space_device_example.py so
CloudXRLauncher.launch_context(args) is entered before spawning the synthetic
plugin process. The current launch order in the main flow starts plugin_proc via
subprocess.Popen before the OpenXR runtime is available, which can make
--launch-plugin fail or race. Keep the existing plugin launch logic and cleanup,
but wrap the plugin startup and subsequent run_live call inside the
CloudXRLauncher.launch_context(args) block so the plugin sees a running
CloudXR/OpenXR session.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/oxr/python/modular_example.py`:
- Around line 80-108: The hand and head pose printing in modular_example.py only
checks data is not None, which still leaves possible missing joints or invalid
poses; update the left_tracked, right_tracked, and head_tracker handling to
verify the relevant pose/joints validity before dereferencing. Use the existing
tracker access points and the schema.HeadPoseTrackedT / hand tracking objects to
add the same stricter guard pattern used elsewhere, then only read pose.position
and print coordinates when the pose is valid.

---

Outside diff comments:
In `@examples/teleop/python/joint_space_device_example.py`:
- Around line 217-229: Move the CloudXR startup in joint_space_device_example.py
so CloudXRLauncher.launch_context(args) is entered before spawning the synthetic
plugin process. The current launch order in the main flow starts plugin_proc via
subprocess.Popen before the OpenXR runtime is available, which can make
--launch-plugin fail or race. Keep the existing plugin launch logic and cleanup,
but wrap the plugin startup and subsequent run_live call inside the
CloudXRLauncher.launch_context(args) block so the plugin sees a running
CloudXR/OpenXR session.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 72b405bd-aa93-41ce-9e26-419f3684510e

📥 Commits

Reviewing files that changed from the base of the PR and between 3276b5c and 84970ea.

📒 Files selected for processing (16)
  • examples/haptic_feedback/python/controller_haptic_example.py
  • examples/lerobot/record.py
  • examples/oxr/python/modular_example.py
  • examples/oxr/python/modular_example_with_mcap.py
  • examples/retargeting/python/dual_source_teleop_example.py
  • examples/retargeting/python/sharpa_hand_retargeter_demo.py
  • examples/retargeting/python/sources_example.py
  • examples/teleop/python/dex_bimanual_example.py
  • examples/teleop/python/full_bimanual_reordering_example.py
  • examples/teleop/python/isaac_lab_gripper_example.py
  • examples/teleop/python/joint_space_device_example.py
  • examples/teleop/python/se3_retargeting_example.py
  • examples/teleop_session_manager/python/message_channel_example.py
  • examples/teleop_session_manager/python/teleop_controls_simple_example.py
  • src/core/cloudxr/python/launcher.py
  • src/core/cloudxr_tests/python/test_launcher.py

Comment on lines +80 to +108
if left_tracked.data is not None:
pos = left_tracked.data.joints.poses(
deviceio.JOINT_WRIST
).pose.position
print(
f" Left wrist: [{pos.x:6.3f}, {pos.y:6.3f}, {pos.z:6.3f}]"
)
else:
print(" Left hand: inactive")

if right_tracked.data is not None:
pos = right_tracked.data.joints.poses(
deviceio.JOINT_WRIST
).pose.position
print(
f" Right wrist: [{pos.x:6.3f}, {pos.y:6.3f}, {pos.z:6.3f}]"
)
else:
print(" Right hand: inactive")

# Get head data
head_tracked: schema.HeadPoseTrackedT = head_tracker.get_head(
session
)
if head_tracked.data is not None:
pos = head_tracked.data.pose.position
print(
f" Head pos: [{pos.x:6.3f}, {pos.y:6.3f}, {pos.z:6.3f}]"
)

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.

🩺 Stability & Availability | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the reviewed file and the comparison file, then inspect relevant ranges.
git ls-files 'examples/oxr/python/modular_example.py' 'examples/lerobot/record.py' | cat

echo '--- modular_example.py ---'
wc -l examples/oxr/python/modular_example.py
sed -n '1,220p' examples/oxr/python/modular_example.py | cat -n | sed -n '1,180p'

echo '--- record.py ---'
wc -l examples/lerobot/record.py
sed -n '1,260p' examples/lerobot/record.py | cat -n | sed -n '1,220p'

Repository: NVIDIA/IsaacTeleop

Length of output: 15884


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for the tracker and pose API definitions / usage to understand validity semantics.
rg -n "class .*HeadPoseTracked|class .*HandPoseTracked|def poses\(|is_valid|joints" \
  examples src . -g '!**/.git/**' | sed -n '1,220p'

Repository: NVIDIA/IsaacTeleop

Length of output: 25636


Guard invalid hand/head poses before reading them. data is not None still allows missing joints or invalid wrist/head poses, so these dereferences can raise or print bogus coordinates. Check joints/is_valid first, matching the stricter tracker handling used elsewhere.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/oxr/python/modular_example.py` around lines 80 - 108, The hand and
head pose printing in modular_example.py only checks data is not None, which
still leaves possible missing joints or invalid poses; update the left_tracked,
right_tracked, and head_tracker handling to verify the relevant pose/joints
validity before dereferencing. Use the existing tracker access points and the
schema.HeadPoseTrackedT / hand tracking objects to add the same stricter guard
pattern used elsewhere, then only read pose.position and print coordinates when
the pose is valid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant