Add CloudXR VR streaming support for PICO 4 Ultra (Early Access)
Replaces manual H264/TCP stereo streaming with NVIDIA CloudXR for
higher-quality stereoscopic rendering and lower latency.
Changes:
- teleop_xr_agent.py: add --cloudxr flag (enables Isaac Sim XR mode,
disables manual StreamingManager)
- deps/cloudxr/: NVIDIA CloudXR.js SDK (Early Access) with Isaac Lab
teleop React web client
- deps/cloudxr/Dockerfile.wss.proxy: HAProxy WSS proxy for PICO 4 Ultra
HTTPS mode (routes wss://48322 → ws://49100)
- deps/cloudxr/isaac/webpack.dev.js: disable file watching to avoid
EMFILE errors with large node_modules
- deps/cloudxr/INSTALL.md: full setup guide
Usage:
# Start CloudXR Runtime + Isaac Lab
cd ~/IsaacLab && ./docker/container.py start \
--files docker-compose.cloudxr-runtime.patch.yaml \
--env-file .env.cloudxr-runtime
# Run teleop with CloudXR
~/IsaacLab/isaaclab.sh -p teleop_xr_agent.py \
--task Isaac-MindRobot-2i-DualArm-IK-Abs-v0 --cloudxr
# Serve web client
cd deps/cloudxr/isaac && npm run dev-server:https
This commit is contained in:
109
deps/cloudxr/react/webpack-plugins/DownloadAssetsPlugin.js
vendored
Normal file
109
deps/cloudxr/react/webpack-plugins/DownloadAssetsPlugin.js
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
|
||||
class DownloadAssetsPlugin {
|
||||
constructor(assets) {
|
||||
this.assets = assets;
|
||||
this.hasRun = false;
|
||||
this.createdDirs = new Set();
|
||||
}
|
||||
|
||||
safeUnlink(filePath) {
|
||||
try {
|
||||
fs.unlinkSync(filePath);
|
||||
} catch (err) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
apply(compiler) {
|
||||
compiler.hooks.beforeCompile.tapAsync('DownloadAssetsPlugin', (params, callback) => {
|
||||
// Only run once per webpack process
|
||||
if (this.hasRun) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('📦 Checking and downloading required assets...');
|
||||
|
||||
const downloadPromises = this.assets.map(asset => this.downloadFile(asset));
|
||||
|
||||
Promise.allSettled(downloadPromises)
|
||||
.then((results) => {
|
||||
const failed = results.filter(r => r.status === 'rejected');
|
||||
if (failed.length > 0) {
|
||||
console.warn(`⚠️ ${failed.length} asset(s) failed to download, continuing anyway...`);
|
||||
}
|
||||
console.log('✅ Asset check complete!');
|
||||
this.hasRun = true;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
downloadFile({ url, output }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Ensure directory exists (only once per unique path)
|
||||
if (!this.createdDirs.has(output)) {
|
||||
fs.mkdirSync(output, { recursive: true });
|
||||
this.createdDirs.add(output);
|
||||
}
|
||||
|
||||
const filename = path.basename(url);
|
||||
const filePath = path.join(output, filename);
|
||||
|
||||
// Skip if file already exists
|
||||
if (fs.existsSync(filePath)) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(` Downloading ${filename}...`);
|
||||
|
||||
const file = fs.createWriteStream(filePath);
|
||||
|
||||
const downloadFromUrl = (downloadUrl) => {
|
||||
https.get(downloadUrl, (response) => {
|
||||
// Handle redirects
|
||||
if (response.statusCode === 302 || response.statusCode === 301) {
|
||||
file.close();
|
||||
this.safeUnlink(filePath);
|
||||
downloadFromUrl(response.headers.location);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
file.close();
|
||||
this.safeUnlink(filePath);
|
||||
reject(new Error(`Failed to download ${filename}: HTTP ${response.statusCode}`));
|
||||
return;
|
||||
}
|
||||
|
||||
response.pipe(file);
|
||||
|
||||
file.on('finish', () => {
|
||||
file.close();
|
||||
console.log(` ✓ Downloaded ${filename}`);
|
||||
resolve();
|
||||
});
|
||||
|
||||
file.on('error', (err) => {
|
||||
this.safeUnlink(filePath);
|
||||
reject(err);
|
||||
});
|
||||
}).on('error', (err) => {
|
||||
if (fs.existsSync(filePath)) {
|
||||
this.safeUnlink(filePath);
|
||||
}
|
||||
reject(err);
|
||||
});
|
||||
};
|
||||
|
||||
downloadFromUrl(url);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DownloadAssetsPlugin;
|
||||
|
||||
Reference in New Issue
Block a user