Connection Manager [omni.kit.widget.connection_manager]#

Unified server connection management for Nucleus, storage discovery, and HTTP servers.

Purpose#

This extension is the single authority for managing server connections across the Kit ecosystem. It replaces the previous omni.kit.widget.nucleus_connector (now a deprecated shim) and consolidates connection handling for:

  • Nucleus servers (omniverse://) — authentication, sign-in, reconnect

  • Discovery servers — automatic storage service discovery via omni.client.register_storage_from_discovery_with_callback

  • HTTP/HTTPS servers — manual S3, Azure, GCS, or custom HTTP storage connections

Architecture#

omni.kit.widget.connection_manager
├── extension.py         Singleton lifecycle + module-level convenience functions
├── manager.py           ConnectionManager: ensure_connection, auth, add-server dialogs
├── registry.py          ConnectionRegistry: discovery + manual server state, events, persistence
├── toml_store.py        All TOML I/O (omniverse.toml), file watcher, migration
├── url_utils.py         URL normalization and validation
└── ui/
    ├── add_server_dialog.py   Unified add-server dialog (input -> connecting -> success/error)
    ├── auth_dialog.py         Standalone Nucleus browser sign-in prompt
    ├── alert_pane.py          Reusable alert widget
    ├── progress_bar.py        Reusable progress bar widget
    └── style.py               UI styles

Key Design Decisions#

  • ConnectionRegistry owns all state (bookmarks, discovery servers, manual servers) and emits events on changes. It is the source of truth — UI components read from it, never the reverse.

  • ConnectionManager wraps the registry with auth handling, add-server dialogs, and the ensure_connection() coroutine.

  • Module-level functions (ensure_connection, disconnect, reconnect, show_add_*_dialog) delegate to the singleton so callers never need to import the manager class directly.

  • Events use the omni.kit.widget.connection_manager.* namespace and are registered as aliases for integer event IDs via omni.kit.app.register_event_alias.

Data Flow#

                  ┌──────────────────────┐
                  │  External Callers    │
                  │  (filepicker, usd,   │
                  │   file_window, etc.) │
                  └────────┬─────────────┘
                           │
                  ensure_connection(url)
                           │
                  ┌────────▼─────────────┐
                  │  ConnectionManager   │
                  │  (manager.py)        │
                  │                      │
                  │  - auth callbacks    │
                  │  - add-server UX     │
                  │  - stat_with_retry   │
                  └────────┬─────────────┘
                           │
                  ┌────────▼─────────────┐
                  │  ConnectionRegistry  │
                  │  (registry.py)       │
                  │                      │
                  │  - bookmarks         │
                  │  - discovery servers │──── omni.client discovery API
                  │  - manual servers    │
                  │  - events            │──── event bus (carb.eventdispatcher)
                  │  - persistence       │──── omniverse.toml (via toml_store)
                  └──────────────────────┘

Threading Model#

The omni.client discovery callback runs on a background thread. To avoid races with UI-thread reads, the callback uses asyncio.get_event_loop().call_soon_threadsafe() to schedule all state mutations on the main event loop. This means:

  • All DiscoveryServerState mutations happen on the main thread.

  • The asyncio.Event used for ensure_connection awaiting is set on the main thread.

  • UI code can safely read registry state without locking.

Usage#

Ensuring a Server is Reachable#

The primary entry point. Call this before accessing a URL to guarantee connectivity:

from omni.kit.widget.connection_manager import ensure_connection

# Returns True if reachable, False on failure
success = await ensure_connection("omniverse://my-server/path/to/file.usd")

# With a progress dialog for callers without their own UI
success = await ensure_connection(url, show_connecting_dialog=True)

ensure_connection handles all server types:

  1. Discovery — if the URL matches a known discovery server’s cached addresses, ensures discovery is complete.

  2. Nucleus — for omniverse:// URLs, performs stat with auth/reconnect handling.

  3. HTTP/HTTPS — considered always reachable, returns True immediately.

Adding Servers Programmatically#

from omni.kit.widget.connection_manager import get_instance

cm = get_instance()

# Discovery server (triggers omni.client storage discovery)
sid = cm.registry.add_discovery_server(
    name="My Storage",
    discovery_url="https://discovery.example.com/",
)

# Manual Nucleus server
sid = cm.registry.add_manual_server(
    name="my-server",
    url="omniverse://my-server/",
)

# Manual HTTP server
sid = cm.registry.add_manual_server(
    name="S3 Bucket",
    url="https://mybucket.s3.amazonaws.com/",
)

Showing Add-Server Dialogs#

from omni.kit.widget.connection_manager import (
    show_add_discovery_server_dialog,
    show_add_nucleus_server_dialog,
    show_add_http_server_dialog,
)

# Each accepts optional on_success and on_failed callbacks
show_add_discovery_server_dialog(
    on_success=lambda name, url, sid: print(f"Added {name}"),
    on_failed=lambda: print("Cancelled"),
)

Only one dialog can be active at a time. A second call while a dialog is open will invoke on_failed and log a warning.

Disconnecting / Reconnecting#

from omni.kit.widget.connection_manager import disconnect, reconnect

disconnect("omniverse://my-server/")   # Signs out from Nucleus
reconnect("omniverse://my-server/")    # Reconnects asynchronously

Both are no-ops for non-Nucleus URLs.

Managing Bookmarks#

from omni.kit.widget.connection_manager import (
    get_bookmarks,
    add_bookmark,
    remove_bookmark,
    rename_bookmark,
)

# Get all bookmarks (name -> url)
bookmarks = get_bookmarks()

# Add a bookmark
add_bookmark("My Project", "omniverse://my-server/Projects/MyProject/")

# Remove by name
remove_bookmark("My Project")

# Rename (old_name, new_name, url)
rename_bookmark("Old Name", "New Name", "omniverse://my-server/Projects/")

Listening for Events#

from carb import eventdispatcher
from omni.kit.widget.connection_manager import (
    BOOKMARK_ADDED_EVENT,
    BOOKMARK_DELETED_EVENT,
    BOOKMARK_RENAMED_EVENT,
    CONNECTION_SUCCEEDED_EVENT,
    DISCOVERY_SERVER_ADDED_EVENT,
    DISCOVERY_SERVER_REMOVED_EVENT,
    DISCOVERY_SERVER_UPDATED_EVENT,
    MANUAL_SERVER_ADDED_EVENT,
    MANUAL_SERVER_REMOVED_EVENT,
)

sub = eventdispatcher.get_eventdispatcher().observe_event(
    event_name=DISCOVERY_SERVER_UPDATED_EVENT,
    on_event=lambda event: print(f"Discovery updated: {event['url']}"),
)

Event Payloads#

All events include at minimum a "url" key. Full payload details:

Event

Payload Keys

BOOKMARK_ADDED_EVENT

name, url

BOOKMARK_DELETED_EVENT

name, url

BOOKMARK_RENAMED_EVENT

old_name, new_name, url

CONNECTION_SUCCEEDED_EVENT

url

DISCOVERY_SERVER_ADDED_EVENT

name, url, service_id

DISCOVERY_SERVER_REMOVED_EVENT

name, url

DISCOVERY_SERVER_RENAMED_EVENT

url, old_name, new_name

DISCOVERY_SERVER_UPDATED_EVENT

url, cached_addresses_json, status, error_message

MANUAL_SERVER_ADDED_EVENT

name, url

MANUAL_SERVER_REMOVED_EVENT

name, url

MANUAL_SERVER_RENAMED_EVENT

url, old_name, new_name

Persistence (omniverse.toml)#

All data is persisted in omniverse.toml (resolved via ${omni_global_config}/omniverse.toml). This file is global and survives app updates.

[bookmarks]
Astronaut = "https://...amazonaws.com/Samples/Astronaut/"
"user@example.com" = "omniverse://content.ov.nvidia.com/Users/user@example.com/"

[servers]
"omniverse://my-server" = "my-server"
"https://mybucket.s3.amazonaws.com" = "S3 Bucket"

[[discovery_servers]]
name = "My Storage"
url = "https://discovery.example.com/"
cached_addresses = ["omniverse://storage.example.com/", "https://bucket.s3.amazonaws.com/"]
  • [bookmarks] is name = url.

  • [servers] is url = name (URL is the key; each server listed once).

  • [[discovery_servers]] uses TOML array-of-tables for cached_addresses.

  • Foreign sections (e.g. [paths]) are preserved across read-modify-write cycles.

  • _persist() writes all three sections via a single save_sections() call to toml_store.

File Watching#

TomlFileWatcher uses omni.client.stat_subscribe_with_callback with a 1-second debounce to detect external edits to omniverse.toml. On change, _apply_toml_reload() diffs against in-memory state and emits add/remove events for changes. Self-triggered writes are naturally a no-op (diff finds nothing to change).

Migration#

On first startup with old-style TOML, if [bookmarks] exists but both [servers] and [[discovery_servers]] are absent, servers are derived from [bookmarks] by extracting unique scheme://host roots (Azure: scheme://host/container). Bookmarks that point exactly at a server root are removed from [bookmarks] after their server entry is created, matching the legacy behavior where root bookmarks appeared in the server list instead of the bookmark list.

Migration from nucleus_connector#

omni.kit.widget.nucleus_connector is now a thin compatibility shim. All calls delegate to this extension with deprecation warnings. See the migration guide in the nucleus_connector extension (docs/MIGRATION.md) for details.