Files
DobidopandClaude c2b3bb5a50 Merge ComfyStereoViewer and StereoscopeGenerator into unified ComfyStereo
Combines three powerful stereoscopic tools into one comprehensive package:

1. Core Stereo Generation
   - Depth-based stereo conversion with GPU acceleration
   - Advanced fill techniques (Polylines, GPU Warp, Hybrid Edge, etc.)
   - Edge-aware depth blurring for artifact reduction
   - Side-by-Side, Top-Bottom, and Red-Cyan Anaglyph output formats

2. Native VR Viewing (Optional - requires PyOpenXR)
   - Auto-launch directly into VR headset without browser
   - Support for all VR headsets (Quest, Vive, Index, WMR)
   - Multiple projection types (Flat, Curved, 180° Dome, 360° Sphere)
   - Image and video playback support

3. StereoDiffusion AI (Optional - requires diffusers)
   - AI-powered stereo generation using diffusion models
   - DDIM inversion with null-text optimization
   - Bilateral Neighbor Attention for stereo consistency
   - Works with ComfyUI MODEL/CLIP/VAE or HuggingFace models

Changes:
- Added native_viewer/ module with PyOpenXR implementation
- Added native_nodes.py with 3 VR viewer nodes
- Added stereodiffusion_nodes.py with AI stereo generation
- Added supporting modules: model_wrappers, model_loader, inversion,
  stereo_utils, diffusion_utils
- Updated __init__.py with graceful optional dependency handling
- Consolidated all requirements into single requirements.txt
- Comprehensive README with setup guides and troubleshooting
- Added NOTICE and licenses/ for proper attribution

The package now offers 6+ nodes total with modular installation options.
Users can install just the base, or add VR viewing and/or AI generation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 22:10:56 +01:00

148 lines
5.2 KiB
Python

"""
Utility functions for viewer management and OpenXR checking
"""
import threading
import time
import gc
from .constants import PYOPENXR_AVAILABLE
if PYOPENXR_AVAILABLE:
import xr
# Global persistent viewer instance
_global_viewer = None
_viewer_thread = None
_viewer_lock = threading.Lock()
def check_openxr_available():
"""
Check if OpenXR runtime is available
Returns:
tuple: (is_available: bool, message: str)
"""
if not PYOPENXR_AVAILABLE:
return False, "PyOpenXR not installed"
try:
# Try to enumerate available runtimes
xr.enumerate_instance_extension_properties()
return True, "OpenXR runtime available"
except Exception as e:
return False, f"OpenXR runtime not available: {str(e)}"
def get_or_create_viewer():
"""Get existing viewer or create new one (singleton pattern)"""
global _global_viewer, _viewer_thread
# Import here to avoid circular dependency
from .core import PersistentNativeViewer
# print("🔍 [Utils] === get_or_create_viewer() called ===")
with _viewer_lock:
# If there's a viewer that's still running, return it
if _global_viewer is not None and _global_viewer.running:
# print(f"🔍 [Utils] Existing viewer is running, returning it (id: {id(_global_viewer)})")
return _global_viewer
# print(f"🔍 [Utils] Global viewer state: {_global_viewer}")
# print(f"🔍 [Utils] Thread state: {_viewer_thread}")
# If there's a thread that's still alive, wait for it to finish
if _viewer_thread is not None and _viewer_thread.is_alive():
# print(f"🔍 [Utils] Previous thread is alive (id: {_viewer_thread.ident})")
print("⏳ Waiting for previous viewer instance to terminate...")
_viewer_thread.join(timeout=10.0)
if _viewer_thread.is_alive():
print("⚠️ Previous viewer did not terminate cleanly after 10s")
return None
else:
print("✓ Previous viewer terminated")
# Give OpenXR a moment to clean up
print("⏳ Waiting for OpenXR to clean up (3 seconds)...")
time.sleep(3.0)
# print("🔍 [Utils] Wait complete")
# Create new viewer instance
print("🔨 Creating new VR viewer instance...")
_global_viewer = PersistentNativeViewer()
# print(f"🔍 [Utils] New viewer created (id: {id(_global_viewer)})")
# print(f"🔍 [Utils] Starting viewer thread...")
_viewer_thread = threading.Thread(target=_global_viewer.run, daemon=True)
_viewer_thread.start()
# print(f"🔍 [Utils] Thread started (id: {_viewer_thread.ident})")
# Give it a moment to initialize
# print("🔍 [Utils] Sleeping 1.5s for initialization...")
time.sleep(1.5)
# print(f"🔍 [Utils] Returning viewer (running={_global_viewer.running})")
return _global_viewer
def stop_global_viewer():
"""Stop the global viewer"""
global _global_viewer
if _global_viewer:
_global_viewer.stop()
def launch_native_viewer(media_path, stereo_format="sbs", swap_eyes=False,
projection_type="flat", screen_size=3.0, screen_distance=3.0,
background_color=(0.0, 0.0, 0.0), is_video=False, loop_video=True):
"""
Launch or update the native viewer with a new image or video.
If viewer is already running, updates it with the new media.
Otherwise, starts a new viewer.
Args:
media_path: Path to stereo image or video
stereo_format: Stereo format (sbs, ou, mono)
swap_eyes: Whether to swap eyes
projection_type: Projection type (flat, curved, dome180, sphere360)
screen_size: Screen size in meters (for flat/curved/dome)
screen_distance: Distance from viewer in meters
background_color: Background color as RGB tuple (0.0-1.0 range)
is_video: Whether the media is a video file
loop_video: Whether to loop video playback
Returns:
bool: True if successful, False if error
"""
available, message = check_openxr_available()
if not available:
print(f"ERROR: {message}")
print("\nTo use native VR viewer:")
print("1. Install PyOpenXR: pip install pyopenxr PyOpenGL pillow opencv-python")
print("2. Install SteamVR or Oculus runtime")
print("3. Make sure your VR headset is connected")
return False
try:
viewer = get_or_create_viewer()
if viewer is None:
print("❌ Failed to create or get viewer instance")
print("💡 The previous viewer may still be cleaning up.")
print(" Please wait a few seconds and try again.")
return False
# Queue the media update - it will be processed by the render loop
viewer.update_media(media_path, stereo_format, swap_eyes, projection_type,
screen_size, screen_distance, background_color, is_video, loop_video)
return True
except Exception as e:
print(f"Error launching native viewer: {e}")
import traceback
traceback.print_exc()
return False