diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..5e58249
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,86 @@
+# Changelog
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+
+## [106.1.0] - 2024-09-17
+
+### Added
+- Support for containerization of streaming applications and services via `repo package --container`
+- Support extension only builds via `repo build`
+- Support the ability to launch created containers via `repo launch --container`
+- repo_usd tooling dependency
+- Support for USD Viewer Template to send scene loading state to client via messaging
+
+### Changed
+- Aligned default testing for applications and extensions
+- Update and align code formatting/style across templates
+
+### Fixed
+- Extra setup extensions appear in standard extension template menu
+- "Could not find cgroup memory limit" error during build
+- Fixed default manipulator pivot back to "bounding box base" in USD Explorer Template
+
+
+## [106.0.2] - 2024-07-29
+
+### Added
+- Support for local streaming configurations for UI based Applications
+- Support for multiple setup extensions per application
+- Ability to pass arguments to Kit via the 'repo launch` tool.
+- USD Composer Application Template and Documentation
+- USD Viewer Application Template and Documentation
+- USD Composer Setup Extension and Documentation
+- USD Viewer Setup Extension and Documentation
+- Repository Issue Templates Bug/Question/Feature Request
+- Omniverse Product-Specific Terms (PRODUCT_TERMS_OMNIVERSE)
+- Support for type ordering in templates.toml
+- Metrics Assembler to Kit Base Editor Template to support unit correct assets
+- Support for automatic launch if only single `.kit` file is present in `source/apps`
+
+### Changed
+- Updated all relevant application templates READMEs to reflect the addition of local streaming configurations
+- Updated .gitattributes to ensure LFS is used for all relevant file types
+- Updated .gitignore to exclude streaming app event traces
+- Updated .vscode/launch.json to better support debugging behavior
+- Updated LICENSE to separate NVIDIA License from Omniverse Product-Specific Terms
+- Updated top level README.md to reflect additional templates and improve documentation clarity
+- Updated Developer Bundle extension availability and corresponding documentation
+- Updated public extension registry to reflect current Kit 106 registry location
+- Updated templates.toml to support multiple setup extensions and new templates
+
+
+## [106.0.0] - 2024-06-07
+
+### Added
+- Kit Base Editor Application Template and Documentation
+- USD Explorer Application Template and Documentation
+- USD Explorer Setup Extension and Documentation
+- Kit Service Template and Documentation
+- Simple Python Extension Template and Documentation
+- Simple C++ Extension Template and Documentation
+- Python UI Extension Template and Documentation
+- Template configuration file (templates.toml)
+- Added local `repo launch` tool for launching applications and fat packages directly
+- Added local `repo package` functionality to improve package naming
+- Omniverse EULA acceptance to Kit App Template via tooling
+- tasks.json for better VSCode support
+- SECURITY.md for security policy
+- Notice for data collection and use
+- Early access Developer Bundle extensions
+- Kit App Template related Developer Bundle documentation (developer_bundle_extensions.md)
+- Kit App Template related repo tools documentation (kit_app_template_tooling_guide.md)
+- Usage and troubleshooting documentation for Kit App Template (usage_and_troubleshooting.md)
+- repo_tools.toml to configure local repo tools
+
+### Changed
+- Updated repo_kit_template tooling to support Applications and Extensions
+- Updated repo_kit_template tooling to allow for application setup extensions
+- Updated top level README.md to reflect updated tooling and templates
+- Updated LICENSE.md to reflect updated tooling and templates
+- Updated .gitattributes to reflect use of templates rather directly from source
+- Added configuration to repo.toml to support new tools and templates
+
+### Removed
+- Top level build .bat/.sh scripts in favor of using `repo build` directly
+- Predefined `define_app` declarations from `premake5.lua` in favor of developer defined applications
+- Predefined source/apps in favor of templates for developers to build from
\ No newline at end of file
diff --git a/README.md b/README.md
index 48d6c16..d7aecfa 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,8 @@
-**This branch branch is based on Omniverse Kit SDK 106.0**
+## :warning: EA Release Information
+**This branch is based on Omniverse Kit 106.1 EA. It includes EA versions of the Kit SDK, associated development tools, and templates. For the latest stable release, see the `106` branch.**
## Overview
@@ -59,21 +60,30 @@ These resources empower developers at all experience levels to fully utilize the
Ensure your system is set up with the following to work with Omniverse Applications and Extensions:
- **Operating System**: Windows 10/11 or Linux (Ubuntu 20.04/22.04 recommended)
+
- **GPU**: NVIDIA RTX capable GPU (Turing or newer recommended)
+
- **Driver**: Latest NVIDIA driver compatible with your GPU
+
- **Internet Access**: Required for downloading the Omniverse Kit SDK, extensions, and tools.
### Required Software Dependencies
-- **Git**: For version control and repository management
-- **Git LFS**: For managing large files within the repository
+- [**Git**](https://git-scm.com/downloads): For version control and repository management
+
+- [**Git LFS**](https://git-lfs.com/): For managing large files within the repository
+
- **(Windows) Microsoft Visual C++ Redistributable**: Many Windows systems will already have this, but if not, it can be obtained from [latest-supported-vc-redist](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#latest-microsoft-visual-c-redistributable-version)
+
- **(Linux) build-essentials**: A package that includes `make` and other essential tools for building applications. For Ubuntu, install with `sudo apt-get install build-essential`
### Recommended Software
-- **(Linux) Docker**: For containerized development and deployment.
-- **VSCode (or your preferred IDE)**: For code editing and development
+- [**(Linux) Docker**](https://docs.docker.com/engine/install/ubuntu/): For containerized development and deployment. **Ensure non-root users have Docker permissions.**
+
+- [**(Linux) NVIDIA Container Toolkit**](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html): For GPU-accelerated containerized development and deployment. **Installation and Configuring Docker steps are required.**
+
+- [**VSCode**](https://code.visualstudio.com/download) (or your preferred IDE): For code editing and development
## Repository Structure
@@ -129,7 +139,7 @@ Run the following command to initiate the configuration wizard:
.\repo.bat template new
```
-> **Note:** If this is your first time running the `template new` tool, you'll be prompted to accept the Omniverse Licensing Terms.
+> **NOTE:** If this is your first time running the `template new` tool, you'll be prompted to accept the Omniverse Licensing Terms.
Follow the prompt instructions:
- **? Select with arrow keys what you want to create:** Application
@@ -180,8 +190,23 @@ Initiate your newly created application using:
![Kit Base Editor Image](readme-assets/kit_base_editor.png)
-***NOTE:* The initial startup may take 5 to 8 minutes as shaders compile for the first time. After initial shader compilation, startup time will reduce dramatically**
+> **NOTE:** The initial startup may take 5 to 8 minutes as shaders compile for the first time. After initial shader compilation, startup time will reduce dramatically
+
+**Launch with Developer Bundle (Alternative):** Instead of running the default launch command, developers might prefer to include the developer bundle for access to developer-specific extensions, such as the Script Editor, Extension Manager, and more.
+
+To launch with the developer bundle, use the `--dev-bundle` or `-d` flag:
+
+**Linux:**
+```bash
+./repo.sh launch -d
+```
+
+**Windows:**
+```powershell
+.\repo.bat launch -d
+```
+For more information on the extensions available in the developer bundle, see the [Developer Bundle Extensions](readme-assets/additional-docs/developer_bundle_extensions.md) document.
## Templates
@@ -246,7 +271,7 @@ To learn more about what data is collected, how we use it and how you can change
- [Usage and Troubleshooting](readme-assets/additional-docs/usage_and_troubleshooting.md)
-- [BETA - Developer Bundle Extensions](readme-assets/additional-docs/developer_bundle_extensions.md)
+- [Developer Bundle Extensions](readme-assets/additional-docs/developer_bundle_extensions.md)
- [Omniverse Kit SDK Manual](https://docs.omniverse.nvidia.com/kit/docs/kit-manual/latest/index.html)
diff --git a/premake5.lua b/premake5.lua
index 0b643f5..7f81f94 100644
--- a/premake5.lua
+++ b/premake5.lua
@@ -12,4 +12,4 @@ repo_build.prebuild_copy {
{ "%{root}/tools/deps/user.toml", "%{root}/_build/deps/user.toml" },
}
--- Apps: for each app generate batch files and a project based on kit files (e.g. my_name.my_app.kit)
+-- Apps: for each app generate batch files and a project based on kit files (e.g. my_name.my_app.kit)
\ No newline at end of file
diff --git a/readme-assets/additional-docs/kit_app_template_tooling_guide.md b/readme-assets/additional-docs/kit_app_template_tooling_guide.md
index 6a24169..f64ff44 100644
--- a/readme-assets/additional-docs/kit_app_template_tooling_guide.md
+++ b/readme-assets/additional-docs/kit_app_template_tooling_guide.md
@@ -69,8 +69,8 @@ Run the build command before testing or packaging your application to ensure all
```
Other common build options:
-- `-c` or `--clean`: Cleans the build directory before building.
-- `x` or `--rebuild`: Rebuilds the project from scratch.
+- **`-c` or `--clean`:** Cleans the build directory before building.
+- **`x` or `--rebuild`:** Rebuilds the project from scratch.
## Launch Tool
@@ -91,37 +91,49 @@ Select and run a built .kit file from the `source/apps` directory:
.\repo.bat launch
```
-Additional launch options:
-- `d` or `--dev-bundle`: Launches with a suite of developer tools enabled in UI-based applications.
-- `-p` or `--package`: Launches a packaged application from a specified path.
+Additional package options:
+- **`-d` or `--dev-bundle`:** Launches with a suite of developer tools enabled in UI-based applications.
-**Linux:**
-```bash
-./repo.sh launch -p /path/to/package.zip
-```
-**Windows:**
-```powershell
-.\repo.bat launch -p C:\path\to\package.zip
-```
+- **`-p` or `--package`:** Launches a packaged application from a specified path.
-Passing args to launched Kit executable:
-You can pass through arguments to your targeted Kit executable by appending `--` to your launch command. Anything after `--` will be passed through to Kit. The following examples will pass the `--clear-cache` flag to Kit.
+ **Linux:**
+ ```bash
+ ./repo.sh launch -p
+ ```
+ **Windows:**
+ ```powershell
+ .\repo.bat launch -p
+ ```
-**Linux:**
-```bash
-./repo.sh launch -- --clear-cache
-```
-**Windows:**
-```powershell
-.\repo.bat launch -- --clear-cache
-```
+- **`--container`:** Launches a containerized application (Linux only).
+
+ **Linux:**
+ ```bash
+ ./repo.sh launch --container
+ ```
+ **Windows:**
+ ```powershell
+ .\repo.bat launch --container
+ ```
+
+- **Passing args to launched Kit executable:**
+You can pass through arguments to your targeted Kit executable by appending `--` to your launch command. Any flags added after `--` will be passed through to Kit directly. The following examples will pass the `--clear-cache` flag to Kit.
+
+ **Linux:**
+ ```bash
+ ./repo.sh launch -- --clear-cache
+ ```
+ **Windows:**
+ ```powershell
+ .\repo.bat launch -- --clear-cache
+ ```
## Test Tool
**Command:** `./repo.sh test` or `.\repo.bat test`
### Purpose
-The test tool facilitates the execution of automated tests on your applications and extensions to help ensure their functionality and stability.
+The test tooling facilitates the execution of automated tests on your applications and extensions to help ensure their functionality and stability. Applications configurations (`.kit` files) are tested to ensure they can startup and shutdown without issue. However, the tests written within the extensions will dictate a majority of application functionality testing. Extension templates provided by the Kit App Template repository include sample tests which can be expanded upon to increase test coverage as needed.
### Usage
Always run a build before testing:
@@ -154,21 +166,42 @@ Always run a build before packaging to ensure the application is up-to-date:
.\repo.bat package
```
-By default, the `package` tool creates a `.zip` file named `kit-app-template` in the `_build/packages` directory. The package name can be specified using the `-n` or `--name` flag:
-**Linux:**
-```bash
-./repo.sh package -n my_package
-```
-**Windows:**
-```powershell
-.\repo.bat package -n my_package
-```
+Additional launch options:
+- **`-n` or `--name`:** Specifies the package (or container image) name.
+
+ **Linux:**
+ ```bash
+ ./repo.sh package -n
+ ```
+ **Windows:**
+ ```powershell
+ .\repo.bat package -n
+ ```
+
+- **`--thin`:** Creates a thin package that includes only custom extensions and configurations for required registry extensions.
+
+ **Linux:**
+ ```bash
+ ./repo.sh package --thin
+ ```
+ **Windows:**
+ ```powershell
+ .\repo.bat package --thin
+ ```
+
+- **`--container`:** Packages the application as a container image (Linux only). When using the `--container` flag, the user will be asked to select a `.kit` file to use within the entry point script for the container. This can also be specified without user interaction by passing it appropriate `.kit` file name via the `--target-app` flag.
+
+ **Linux:**
+ ```bash
+ ./repo.sh package --container
+ ```
+ **Windows:**
+ ```powershell
+ .\repo.bat package --container
+ ```
:warning: **Important Note for Packaging:** Because the packaging operation will package everything within the `source/` directory the package version will need to be set independently of a given `kit` file. **The version is set within the `tools/VERSION.md` file.**
-#### Fat and Thin Packages
-Packages can be either 'fat' (including all dependencies) or 'thin' (including only custom extensions and configurations for required registry extensions). Thin packages are created using the `--thin` flag. A fat package is created by default to facilitate ease of use and testing, whereas **thin package distribution is required for broader dissemination.**
-
## Additional Resources
- [Kit App Template Companion Tutorial](https://docs.omniverse.nvidia.com/kit/docs/kit-app-template/latest/docs/intro.html)
\ No newline at end of file
diff --git a/readme-assets/additional-docs/usage_and_troubleshooting.md b/readme-assets/additional-docs/usage_and_troubleshooting.md
index 611a27d..a266e2a 100644
--- a/readme-assets/additional-docs/usage_and_troubleshooting.md
+++ b/readme-assets/additional-docs/usage_and_troubleshooting.md
@@ -48,4 +48,26 @@ The `template new` tooling ensures that any created application is properly conf
For a clean build, use the command `./repo.sh build -c` or `.\repo.bat build -c` to clean the build directory before building.
### Windows Long Path
-Due to path length limitations on Windows it is recommended to place repository artifacts in a location closer to the root of the drive. This will help avoid issues with the path lengths when building and packaging applications.
\ No newline at end of file
+Due to path length limitations on Windows it is recommended to place repository artifacts in a location closer to the root of the drive. This will help avoid issues with the path lengths when building and packaging applications.
+
+
+### Space Constraints Due to Docker Artifacts
+When performing extensive local testing of container images created via `repo package --container`, Docker artifacts can accumulate over time, consuming significant disk space.
+
+`docker system df` can be used to determine disk space utilized by Docker objects. To reclaim space, consider the following options:
+
+1. **Regular Safe Cleanup**:
+ - **Command**: `docker container prune`
+ - **Description**: This command removes all stopped containers, which is typically safe and helps manage disk space without affecting images, networks, or volumes.
+ - **Use**: Recommended for regular maintenance.
+
+2. **Extensive Cleanup (:warning:Use with Caution:warning:)**:
+ - **Command**: `docker system prune`
+ - **Description**: This command removes all unused containers, networks, images, and optionally, volumes. It is akin to running a `rm -rf` for Docker resources.
+ - **Warning**: Use this command carefully, as it will remove many resources indiscriminately. Ensure you review and understand what will be deleted.
+
+For image-specific cleanup, use `docker images` to list all images and `docker rmi ` to manually remove those that are no longer needed.
+
+
+### exFAT Drive Compatibility Limitations
+The Kit App Template repository and associated tooling are designed to work with drive formats that support junctions/symlinks. If you are using an exFAT-formatted drive, you may encounter errors during the build process. To resolve this issue, consider using a different drive format such as NTFS.
\ No newline at end of file
diff --git a/repo.sh b/repo.sh
index 2ca081f..236929b 100755
--- a/repo.sh
+++ b/repo.sh
@@ -2,9 +2,12 @@
set -e
+# Set OMNI_REPO_ROOT early so `repo` bootstrapping can target the repository
+# root when writing out Python dependencies.
+export OMNI_REPO_ROOT="$( cd "$(dirname "$0")" ; pwd -P )"
+
SCRIPT_DIR=$(dirname ${BASH_SOURCE})
cd "$SCRIPT_DIR"
-# Set OMNI_REPO_ROOT early so `repo` bootstrapping can target the repository
-# root when writing out Python dependencies.
-OMNI_REPO_ROOT="$( cd "$(dirname "$0")" ; pwd -P )" exec "tools/packman/python.sh" tools/repoman/repoman.py "$@"
+# Use "exec" to ensure that envrionment variables don't accidentally affect other processes.
+exec "tools/packman/python.sh" tools/repoman/repoman.py "$@"
diff --git a/repo.toml b/repo.toml
index 79a87a0..75d17c2 100644
--- a/repo.toml
+++ b/repo.toml
@@ -13,7 +13,18 @@ import_configs = [
# Repository Name
name = "kit-app-template"
-# Disable linbuild until we know what we want to do with this.
+[repo_build]
+# These are necessary to avoid a repo_build failure where the source/apps directory
+# is expected to always exist.
+fetch."platform:linux-x86_64".before_pull_commands = [
+ ["mkdir", "--parents", "${root}/source/apps"],
+]
+
+fetch."platform:windows-x86_64".before_pull_commands = [
+ ["powershell", "-Command", "New-Item -ItemType Directory -Path ${root}/source/apps -ErrorAction SilentlyContinue", ";", "Write-Host 'Done'"],
+]
+
+# Disable linbuild until we have a public image available.
[repo_build.docker]
enabled = false
diff --git a/templates/apps/kit_base_editor/README.md b/templates/apps/kit_base_editor/README.md
index b077af1..728fb19 100644
--- a/templates/apps/kit_base_editor/README.md
+++ b/templates/apps/kit_base_editor/README.md
@@ -49,7 +49,7 @@ cd kit-app-template
.\repo.bat template new
```
-> **Note:** If this is your first time running the `template new` tool, you'll be prompted to accept the Omniverse Licensing Terms.
+> **NOTE:** If this is your first time running the `template new` tool, you'll be prompted to accept the Omniverse Licensing Terms.
Follow the prompt instructions:
- **? Select with arrow keys what you want to create:** Application
@@ -182,17 +182,67 @@ Alternatively, you can specify a package name using the `--name` flag:
**Linux:**
```bash
-./repo.sh package --name [package_name]
+./repo.sh package --name
```
**Windows:**
```powershell
-.\repo.bat package --name [package_name]
+.\repo.bat package --name
```
This will bundle your application into a distributable format, ready for deployment on compatible platforms.
:warning: **Important Note for Packaging:** Because the packaging operation will package everything within the `source/` directory the package version will need to be set independently of a given `kit` file. **The version is set within the `tools/VERSION.md` file.**
+#### Launching a Package
+
+Applications packaged using the `package` command can be launched using the `launch` command:
+
+**Linux:**
+```bash
+./repo.sh launch --package
+```
+**Windows:**
+```powershell
+.\repo.bat launch --package
+```
+
+> **NOTE:** This behavior is not supported when packaging with the `--thin` flag.
+
+### Containerization (Linux Only)
+
+**Requires:** `Docker` and `NVIDIA Container Toolkit`
+
+The packaging tooling provided by the Kit App Template also supports containerization of applications. This is especially useful for deploying headless services and streaming applications in a containerized environment.
+
+To package your application as a container image, use the `--container` flag:
+
+**Linux:**
+```bash
+./repo.sh package --container
+```
+
+You will be prompted to select a `.kit` file to serve as the application to launch via the container entrypoint script. This will dictate the behavior of your containerized application.
+
+For example, if you are containerizing an application for streaming, select the `{your-app-name}_streaming.kit` file to ensure the correct application configuration is launched within the container.
+
+Similar to desktop packaging, the container option allows for specifying a package name using the `--name` flag to name the container image:
+
+**Linux:**
+```bash
+./repo.sh package --container --name [container_image_name]
+```
+
+#### Launching a Container
+
+Applications packaged as container images can be launched using the `launch` command:
+
+**Linux:**
+```bash
+./repo.sh launch --container
+```
+
+If only a single container image exists, it will launch automatically. If multiple container images exist, you will be prompted to select the desired container image to launch.
+
### Local Streaming
The UI-based template applications in this repository produce more than a single `.kit` file. For the Kit Base Editor template application, this includes `{your-app-name}_streaming.kit` which we will use for local streaming. This file inherits from the base application and adds necessary streaming components like `omni.kit.livestream.webrtc`. To try local streaming, you need a web client to connect to the streaming server.
@@ -229,6 +279,8 @@ import Window from './WindowNoUI';
:warning: **Important**: Launching the streaming application with `--no-window` passes an argument directly to Kit allowing it to run without the main application window to prevent conflicts with the streaming client.
+**Launch and stream a desktop application:**
+
**Linux:**
```bash
./repo.sh launch -- --no-window
@@ -240,6 +292,19 @@ import Window from './WindowNoUI';
Select the `{your-app-name}_streaming.kit` and wait for the application to start
+**Launch and stream a containerized application:**
+
+When streaming a containerized application, ensure that the containerized application was configured during packaging to launch a streaming application (e.g., `{your_app_name}_streaming.kit`).
+
+**Linux:**
+```bash
+./repo.sh launch --container
+```
+
+If only a single container image exists, it will launch automatically. If multiple container images exist, you will be prompted to select the desired container image to launch.
+
+> **NOTE:** The `--no-window` flag is not required for containerized applications as it is the default launch behavior.
+
#### 4. Start the Streaming Client
```bash
npm run dev
diff --git a/templates/apps/kit_service/README.md b/templates/apps/kit_service/README.md
index e7058b8..3c738a0 100644
--- a/templates/apps/kit_service/README.md
+++ b/templates/apps/kit_service/README.md
@@ -52,7 +52,7 @@ cd kit-app-template
.\repo.bat template new
```
-> **Note:** If this is your first time running the `template new` tool, you'll be prompted to accept the Omniverse Licensing Terms.
+> **NOTE:** If this is your first time running the `template new` tool, you'll be prompted to accept the Omniverse Licensing Terms.
Follow the prompt instructions:
- **? Select with arrow keys what you want to create:** Application
@@ -174,17 +174,67 @@ Alternatively, you can specify a package name using the `--name` flag:
**Linux:**
```bash
-./repo.sh package --name [package_name]
+./repo.sh package --name
```
**Windows:**
```powershell
-.\repo.bat package --name [package_name]
+.\repo.bat package --name
```
This will bundle your application into a distributable format, ready for deployment on compatible platforms.
:warning: **Important Note for Packaging:** Because the packaging operation will package everything within the `source/` directory the package version will need to be set independently of a given `kit` file. **The version is set within the `tools/VERSION.md` file.**
+#### Launching a Package
+
+Applications packaged using the `package` command can be launched using the `launch` command:
+
+**Linux:**
+```bash
+./repo.sh launch --package
+```
+**Windows:**
+```powershell
+.\repo.bat launch --package
+```
+
+> **NOTE:** This behavior is not supported when packaging with the `--thin` flag.
+
+### Containerization (Linux Only)
+
+**Requires:** `Docker` and `NVIDIA Container Toolkit`
+
+The packaging tooling provided by the Kit App Template also supports containerization of applications. This is especially useful for deploying headless services and streaming applications in a containerized environment.
+
+To package your application as a container image, use the `--container` flag:
+
+**Linux:**
+```bash
+./repo.sh package --container
+```
+
+You will be prompted to select a `.kit` file to serve as the application to launch via the container entrypoint script. This will dictate the behavior of your containerized application.
+
+For example, if you are containerizing a headless Kit Service, select the `{your-service-name}.kit` file to ensure the correct application configuration is launched within the container.
+
+Similar to desktop packaging, the container option allows for specifying a package name using the `--name` flag to name the container image:
+
+**Linux:**
+```bash
+./repo.sh package --container --name [container_image_name]
+```
+
+#### Launching a Container
+
+Applications packaged as container images can be launched using the `launch` command:
+
+**Linux:**
+```bash
+./repo.sh launch --container
+```
+
+If only a single container image exists, it will launch automatically. If multiple container images exist, you will be prompted to select the desired container image to launch.
+
## Additional Learning
- [Kit App Template Companion Tutorial](https://docs.omniverse.nvidia.com/kit/docs/kit-app-template/latest/docs/intro.html)
diff --git a/templates/apps/streaming_configs/default_stream.kit b/templates/apps/streaming_configs/default_stream.kit
index e87abef..683ef48 100644
--- a/templates/apps/streaming_configs/default_stream.kit
+++ b/templates/apps/streaming_configs/default_stream.kit
@@ -21,27 +21,22 @@ template_name = "omni.streaming_configuration"
[settings.app]
fastShutdown = true
-livestream.allowResize = false # Enable or Disable SDK Scaling
-livestream.skipCapture = 1 # livestream skipCapture ON for local streaming
-livestream.webrtcEtli = true # Only log error or critical level issues.
name = "{{ application_display_name }} Streaming" # Application Display Name
ovc_deployment = true
renderer.skipWhileMinimized = true
renderer.resolution.height = 1080
renderer.resolution.width = 1920
-runLoops.present.rateLimitEnabled = false
+vsync = false # Vsync disabled by default, can be set to true for L40 or similar
window.scaleToMonitor = true
window.showStartup = false
window.height = 1080
window.width = 1920
-
[settings]
rtx.post.aa.op = 3
rtx.verifyDriverVersion.enabled = false
rtx-transient.dlssg.enabled = false # Disable DLSS otherwise it can push the framerate above the locked limit
-
[settings.app.extensions]
registryEnabled = true
supportedTargets.platform = [] # Skip checking supported platform/config when building
@@ -52,3 +47,40 @@ folders.'++' = [ # Search paths for extensions.
"${app}/../apps",
"${app}/../extscache"
]
+
+[settings.app.file]
+ignoreUnsavedOnExit = true
+
+[settings.app.livestream]
+skipCapture = 1 # livestream skipCapture ON for local streaming
+webrtcEtli = true # Only log error or critical level issues.
+
+[settings.app.rendergraph]
+maxFramesInFlight = 2
+
+[settings.app.runloops]
+main.rateLimitEnabled = true # Enable rate limiting on the main thread
+main.rateLimitFrequency = 60 # Lock it to 60fps
+main.rateLimitUsePrecisionSleep = true # Use precise sleep values to ensure threads sync
+main.syncToPresent = true # Sync with the present thread, smooths UI updates
+present.rateLimitEnabled = true # Rate limit the present thread
+present.rateLimitFrequency = 60 # Lock it to 60fps
+present.rateLimitUsePrecisionSleep = true # Use precise sleep values to ensure threads sync
+rendering_0.rateLimitEnabled = true # Enable rate limiting for the rendering thread
+rendering_0.rateLimitFrequency = 60 # Lock it to 60fps
+rendering_0.rateLimitUsePrecisionSleep = true # Use precise sleep values to ensure threads sync
+rendering_0.syncToPresent = true # Sync with the present tread, smooths UI updates
+rendering_1.rateLimitEnabled = true # Enable rate limiting for the rendering thread
+rendering_1.rateLimitFrequency = 60 # Lock it to 60fps
+rendering_1.rateLimitUsePrecisionSleep = true # Use precise sleep values to ensure threads sync
+rendering_1.syncToPresent = true # Sync with the present tread, smooths UI updates
+
+[settings.app.runLoopsGlobal]
+syncToPresent = true # Sync everything with the present thread
+
+[settings.app.viewport]
+defaults.tickRate = 60 # Lock to 60fps[settings.app]
+
+[settings.exts."omni.kit.renderer.core"]
+present.enabled = true # Enable the present thread
+present.presentAfterRendering = true # Ensure the present thread waits for the rendering to complete[settings]
diff --git a/templates/apps/streaming_configs/gdn_stream.kit b/templates/apps/streaming_configs/gdn_stream.kit
index 685ad1c..40ca9e3 100644
--- a/templates/apps/streaming_configs/gdn_stream.kit
+++ b/templates/apps/streaming_configs/gdn_stream.kit
@@ -25,6 +25,7 @@ fastShutdown = true # Skip long full shutdown and exit quickly
name = "{{ application_display_name }} GDN Streaming" # Skip checking supported platform/config when building
titleVersion = "1.0.0" # this is used in our setup file to display some Version to the user in the title bar
useFabricSceneDelegate = true # Turn on the Fabric scene delegate by default
+vsync = false # Vsync disabled by default, can be set to true for L40 or similar
[settings.app.content]
@@ -38,6 +39,32 @@ emptyStageOnStart = false
ignoreUnsavedOnExit = true # enable quiting without confirmation
+[settings.app.rendergraph]
+maxFramesInFlight = 2
+
+
+[settings.app.runloops]
+main.rateLimitEnabled = true # Enable rate limiting on the main thread
+main.rateLimitFrequency = 60 # Lock it to 60fps
+main.rateLimitUsePrecisionSleep = true # Use precise sleep values to ensure threads sync
+main.syncToPresent = true # Sync with the present thread, smooths UI updates
+present.rateLimitEnabled = true # Rate limit the present thread
+present.rateLimitFrequency = 60 # Lock it to 60fps
+present.rateLimitUsePrecisionSleep = true # Use precise sleep values to ensure threads sync
+rendering_0.rateLimitEnabled = true # Enable rate limiting for the rendering thread
+rendering_0.rateLimitFrequency = 60 # Lock it to 60fps
+rendering_0.rateLimitUsePrecisionSleep = true # Use precise sleep values to ensure threads sync
+rendering_0.syncToPresent = true # Sync with the present tread, smooths UI updates
+rendering_1.rateLimitEnabled = true # Enable rate limiting for the rendering thread
+rendering_1.rateLimitFrequency = 60 # Lock it to 60fps
+rendering_1.rateLimitUsePrecisionSleep = true # Use precise sleep values to ensure threads sync
+rendering_1.syncToPresent = true # Sync with the present tread, smooths UI updates
+
+
+[settings.app.runLoopsGlobal]
+syncToPresent = true # Sync everything with the present thread
+
+
[settings.app.window]
title = "{{ application_display_name }} GDN Streaming"
fullscreen = true # fullscreen on startup
@@ -63,22 +90,19 @@ width = 1920 # width of main window
enabled = true
-[settings.app.usdrt.population.utils]
-# When populating Fabric from USD, merge meshes under scene
-# graph instances. Helps with e.g. Tesla
-# Needs to be tested more before enabling for Runway
-mergeInstances = false
-
[settings.app.viewport]
forceHideFps = true # control if performance data is shown in Viewport
createCameraModelRep = false
+defaults.tickRate = 60 # Lock to 60fps[settings.app]
[settings.exts."omni.appwindow"]
listenF11 = false
listenF7 = false
+
[settings.exts."omni.kit.gfn"]
+
auto_startup_gfn = true
# Tests
diff --git a/templates/apps/usd_composer/README.md b/templates/apps/usd_composer/README.md
index 3dbf95f..cb6ca1b 100644
--- a/templates/apps/usd_composer/README.md
+++ b/templates/apps/usd_composer/README.md
@@ -60,7 +60,7 @@ cd kit-app-template
.\repo.bat template new
```
-> **Note:** If this is your first time running the `template new` tool, you'll be prompted to accept the Omniverse Licensing Terms.
+> **NOTE:** If this is your first time running the `template new` tool, you'll be prompted to accept the Omniverse Licensing Terms.
Follow the prompt instructions:
- **? Select with arrow keys what you want to create:** Application
@@ -114,7 +114,7 @@ Select **Window > Browsers > Configurator Samples** - to open configuration samp
### Testing
Applications and their associated extensions can be tested using the `repo test` tooling provided. Each application template includes an initial test suite that can be run to verify the application's functionality.
-> **Note:** Testing will only be run on applications and extensions within the build directory. **A successful build is required before testing.**
+> **NOTE:** Testing will only be run on applications and extensions within the build directory. **A successful build is required before testing.**
**Linux:**
```bash
@@ -199,17 +199,67 @@ Alternatively, you can specify a package name using the `--name` flag:
**Linux:**
```bash
-./repo.sh package --name [package_name]
+./repo.sh package --name
```
**Windows:**
```powershell
-.\repo.bat package --name [package_name]
+.\repo.bat package --name
```
This will bundle your application into a distributable format, ready for deployment on compatible platforms.
:warning: **Important Note for Packaging:** Because the packaging operation will package everything within the `source/` directory the package version will need to be set independently of a given `kit` file. **The version is set within the `tools/VERSION.md` file.**
+#### Launching a Package
+
+Applications packaged using the `package` command can be launched using the `launch` command:
+
+**Linux:**
+```bash
+./repo.sh launch --package
+```
+**Windows:**
+```powershell
+.\repo.bat launch --package
+```
+
+> **NOTE:** This behavior is not supported when packaging with the `--thin` flag.
+
+### Containerization (Linux Only)
+
+**Requires:** `Docker` and `NVIDIA Container Toolkit`
+
+The packaging tooling provided by the Kit App Template also supports containerization of applications. This is especially useful for deploying headless services and streaming applications in a containerized environment.
+
+To package your application as a container image, use the `--container` flag:
+
+**Linux:**
+```bash
+./repo.sh package --container
+```
+
+You will be prompted to select a `.kit` file to serve as the application to launch via the container entrypoint script. This will dictate the behavior of your containerized application.
+
+For example, if you are containerizing an application for streaming, select the `{your-app-name}_streaming.kit` file to ensure the correct application configuration is launched within the container.
+
+Similar to desktop packaging, the container option allows for specifying a package name using the `--name` flag to name the container image:
+
+**Linux:**
+```bash
+./repo.sh package --container --name [container_image_name]
+```
+
+#### Launching a Container
+
+Applications packaged as container images can be launched using the `launch` command:
+
+**Linux:**
+```bash
+./repo.sh launch --container
+```
+
+If only a single container image exists, it will launch automatically. If multiple container images exist, you will be prompted to select the desired container image to launch.
+
### Local Streaming
The UI-based template applications in this repository produce more than a single `.kit` file. For the USD Composer template application, this includes `{your-app-name}_streaming.kit` which we will use for local streaming. This file inherits from the base application and adds necessary streaming components like `omni.kit.livestream.webrtc`. To try local streaming, you need a web client to connect to the streaming server.
@@ -246,6 +296,8 @@ import Window from './WindowNoUI';
:warning: **Important**: Launching the streaming application with `--no-window` passes an argument directly to Kit allowing it to run without the main application window to prevent conflicts with the streaming client.
+**Launch and stream a desktop application:**
+
**Linux:**
```bash
./repo.sh launch -- --no-window
@@ -257,6 +309,19 @@ import Window from './WindowNoUI';
Select the `{your-app-name}_streaming.kit` and wait for the application to start
+**Launch and stream a containerized application:**
+
+When streaming a containerized application, ensure that the containerized application was configured during packaging to launch a streaming application (e.g., `{your_app_name}_streaming.kit`).
+
+**Linux:**
+```bash
+./repo.sh launch --container
+```
+
+If only a single container image exists, it will launch automatically. If multiple container images exist, you will be prompted to select the desired container image to launch.
+
+> **NOTE:** The `--no-window` flag is not required for containerized applications as it is the default launch behavior.
+
#### 4. Start the Streaming Client
```bash
npm run dev
diff --git a/templates/apps/usd_composer/omni.usd_composer.kit b/templates/apps/usd_composer/omni.usd_composer.kit
index 7e20a71..002d9dd 100644
--- a/templates/apps/usd_composer/omni.usd_composer.kit
+++ b/templates/apps/usd_composer/omni.usd_composer.kit
@@ -203,6 +203,9 @@ skipWhileMinimized = true
dev_build = false
persistent = true
+[settings.app.usdrt.population.utils]
+mergeMaterials = false
+
[settings.app.window]
title = "{{ application_display_name }}"
iconPath = "${% raw %}{{% endraw %}{{ setup_extension_name }}{% raw %}}{% endraw %}/data/nvidia-omniverse-create.png"
diff --git a/templates/apps/usd_explorer/README.md b/templates/apps/usd_explorer/README.md
index 3c79e47..bfa6a52 100644
--- a/templates/apps/usd_explorer/README.md
+++ b/templates/apps/usd_explorer/README.md
@@ -62,7 +62,7 @@ cd kit-app-template
.\repo.bat template new
```
-> **Note:** If this is your first time running the `template new` tool, you'll be prompted to accept the Omniverse Licensing Terms.
+> **NOTE:** If this is your first time running the `template new` tool, you'll be prompted to accept the Omniverse Licensing Terms.
Follow the prompt instructions:
- **? Select with arrow keys what you want to create:** Application
@@ -201,17 +201,66 @@ Alternatively, you can specify a package name using the `--name` flag:
**Linux:**
```bash
-./repo.sh package --name [package_name]
+./repo.sh package --name
```
**Windows:**
```powershell
-.\repo.bat package --name [package_name]
+.\repo.bat package --name
```
This will bundle your application into a distributable format, ready for deployment on compatible platforms.
:warning: **Important Note for Packaging:** Because the packaging operation will package everything within the `source/` directory the package version will need to be set independently of a given `kit` file. **The version is set within the `tools/VERSION.md` file.**
+#### Launching a Package
+
+Applications packaged using the `package` command can be launched using the `launch` command:
+
+**Linux:**
+```bash
+./repo.sh launch --package
+```
+**Windows:**
+```powershell
+.\repo.bat launch --package
+```
+
+> **NOTE:** This behavior is not supported when packaging with the `--thin` flag.
+
+### Containerization (Linux Only)
+
+**Requires:** `Docker` and `NVIDIA Container Toolkit`
+
+The packaging tooling provided by the Kit App Template also supports containerization of applications. This is especially useful for deploying headless services and streaming applications in a containerized environment.
+
+To package your application as a container image, use the `--container` flag:
+
+**Linux:**
+```bash
+./repo.sh package --container
+```
+
+You will be prompted to select a `.kit` file to serve as the application to launch via the container entrypoint script. This will dictate the behavior of your containerized application.
+
+For example, if you are containerizing an application for streaming, select the `{your-app-name}_streaming.kit` file to ensure the correct application configuration is launched within the container.
+
+Similar to desktop packaging, the container option allows for specifying a package name using the `--name` flag to name the container image:
+
+**Linux:**
+```bash
+./repo.sh package --container --name [container_image_name]
+```
+
+#### Launching a Container
+
+Applications packaged as container images can be launched using the `launch` command:
+
+**Linux:**
+```bash
+./repo.sh launch --container
+```
+
+If only a single container image exists, it will launch automatically. If multiple container images exist, you will be prompted to select the desired container image to launch.
### Local Streaming
@@ -249,6 +298,8 @@ import Window from './WindowNoUI';
:warning: **Important**: Launching the streaming application with `--no-window` passes an argument directly to Kit allowing it to run without the main application window to prevent conflicts with the streaming client.
+**Launch and stream a desktop application:**
+
**Linux:**
```bash
./repo.sh launch -- --no-window
@@ -260,6 +311,19 @@ import Window from './WindowNoUI';
Select the `{your-app-name}_streaming.kit` and wait for the application to start
+**Launch and stream a containerized application:**
+
+When streaming a containerized application, ensure that the containerized application was configured during packaging to launch a streaming application (e.g., `{your_app_name}_streaming.kit`).
+
+**Linux:**
+```bash
+./repo.sh launch --container
+```
+
+If only a single container image exists, it will launch automatically. If multiple container images exist, you will be prompted to select the desired container image to launch.
+
+> **NOTE:** The `--no-window` flag is not required for containerized applications as it is the default launch behavior.
+
#### 4. Start the Streaming Client
```bash
npm run dev
diff --git a/templates/apps/usd_explorer/omni.usd_explorer.kit b/templates/apps/usd_explorer/omni.usd_explorer.kit
index afc17cc..7040a53 100644
--- a/templates/apps/usd_explorer/omni.usd_explorer.kit
+++ b/templates/apps/usd_explorer/omni.usd_explorer.kit
@@ -548,7 +548,7 @@ enable = false # Making a ground for whatever reason bewteen scene loads is cau
inertiaDecay = 3
tumbleSpeed = 720
-[setings.persistent.exts."omni.kit.manipulator.prim".manipulator]
+[settings.persistent.exts."omni.kit.manipulator.prim.core".manipulator]
placement = "Bounding Box Base"
[settings.persistent.exts."omni.kit.manipulator.tool.snap"]
@@ -613,7 +613,7 @@ ambientLightIntensity = 1.0
rendermode = 1
[settings.rtx-transient.hydra]
-conservativeMemoryLimits = true # Enable a more conservative approach to stop loading geometry when the amount of free GPU and system memory go below specified thresholds
+conservativeMemoryLimits = true # Stop loading geometry when memory goes below specified thresholds
maxInstanceCount = 12000000 # Limit the number of instances to 12 million to cap performance and memory impact of larger instance counts
[settings.rtx-transient.meshlights]
@@ -635,7 +635,6 @@ enableAnonymousAppName = true
samplingFactor = 1.0 # No test sampling for these tests
dependencies = [
- "omni.kit.test_suite.helpers",
"omni.kit.ui_test",
#"omni.hydra.pxr",
# NOTE: test_extensions.py (with imports from omni.kit.core.tests) need dependencies from some extensions
diff --git a/templates/apps/usd_viewer/README.md b/templates/apps/usd_viewer/README.md
index e784dc1..75678ec 100644
--- a/templates/apps/usd_viewer/README.md
+++ b/templates/apps/usd_viewer/README.md
@@ -51,7 +51,7 @@ cd kit-app-template
.\repo.bat template new
```
-> **Note:** If this is your first time running the `template new` tool, you'll be prompted to accept the Omniverse Licensing Terms.
+> **NOTE:** If this is your first time running the `template new` tool, you'll be prompted to accept the Omniverse Licensing Terms.
Follow the prompt instructions:
- **? Select with arrow keys what you want to create:** Application
@@ -185,17 +185,66 @@ Alternatively, you can specify a package name using the `--name` flag:
**Linux:**
```bash
-./repo.sh package --name [package_name]
+./repo.sh package --name
```
**Windows:**
```powershell
-.\repo.bat package --name [package_name]
+.\repo.bat package --name
```
This will bundle your application into a distributable format, ready for deployment on compatible platforms.
:warning: **Important Note for Packaging:** Because the packaging operation will package everything within the `source/` directory the package version will need to be set independently of a given `kit` file. **The version is set within the `tools/VERSION.md` file.**
+#### Launching a Package
+
+Applications packaged using the `package` command can be launched using the `launch` command:
+
+**Linux:**
+```bash
+./repo.sh launch --package
+```
+**Windows:**
+```powershell
+.\repo.bat launch --package
+```
+
+> **NOTE:** This behavior is not supported when packaging with the `--thin` flag.
+
+### Containerization (Linux Only)
+
+**Requires:** `Docker` and `NVIDIA Container Toolkit`
+
+The packaging tooling provided by the Kit App Template also supports containerization of applications. This is especially useful for deploying headless services and streaming applications in a containerized environment.
+
+To package your application as a container image, use the `--container` flag:
+
+**Linux:**
+```bash
+./repo.sh package --container
+```
+
+You will be prompted to select a `.kit` file to serve as the application to launch via the container entrypoint script. This will dictate the behavior of your containerized application.
+
+For example, if you are containerizing an application for streaming, select the `{your-app-name}_streaming.kit` file to ensure the correct application configuration is launched within the container.
+
+Similar to desktop packaging, the container option allows for specifying a package name using the `--name` flag to name the container image:
+
+**Linux:**
+```bash
+./repo.sh package --container --name [container_image_name]
+```
+
+#### Launching a Container
+
+Applications packaged as container images can be launched using the `launch` command:
+
+**Linux:**
+```bash
+./repo.sh launch --container
+```
+
+If only a single container image exists, it will launch automatically. If multiple container images exist, you will be prompted to select the desired container image to launch.
### Local Streaming
@@ -215,6 +264,8 @@ Follow the instructions in the README to install the necessary dependencies.
:warning: **Important**: Launching the streaming application with `--no-window` passes an argument directly to Kit allowing it to run without the main application window to prevent conflicts with the streaming client.
+**Launch and stream a desktop application:**
+
**Linux:**
```bash
./repo.sh launch -- --no-window
@@ -226,6 +277,19 @@ Follow the instructions in the README to install the necessary dependencies.
Select the `{your-app-name}_streaming.kit` and wait for the application to start
+**Launch and stream a containerized application:**
+
+When streaming a containerized application, ensure that the containerized application was configured during packaging to launch a streaming application (e.g., `{your_app_name}_streaming.kit`).
+
+**Linux:**
+```bash
+./repo.sh launch --container
+```
+
+If only a single container image exists, it will launch automatically. If multiple container images exist, you will be prompted to select the desired container image to launch.
+
+> **NOTE:** The `--no-window` flag is not required for containerized applications as it is the default launch behavior.
+
#### 3. Start the Streaming Client
```bash
npm run dev
diff --git a/templates/apps/usd_viewer/omni.usd_viewer.kit b/templates/apps/usd_viewer/omni.usd_viewer.kit
index e266a9e..6e3d587 100644
--- a/templates/apps/usd_viewer/omni.usd_viewer.kit
+++ b/templates/apps/usd_viewer/omni.usd_viewer.kit
@@ -23,6 +23,7 @@ template_name = "omni.usd_viewer"
"omni.graph.scriptnode" = {}
"omni.graph.ui_nodes" = {}
"omni.hydra.rtx" = {}
+"omni.hydra.usdrt_delegate" = {}
"omni.kit.manipulator.camera" = {}
"omni.kit.manipulator.selection" = {}
"omni.kit.renderer.core" = {}
@@ -31,18 +32,14 @@ template_name = "omni.usd_viewer"
"omni.kit.viewport.utility" = {}
"omni.kit.viewport.window" = {}
"omni.no_code_ui.bundle" = {}
+"omni.ujitso.client" = {}
"usdrt.scenegraph" = {}
"{{ setup_extension_name }}" = { order = 1000 }
[settings.persistent.app]
viewport.defaults.tickRate = 60 # Lock to 60fps
-viewport.displayOptions = 32255 # Bitmask that controls which performance counters appear in the HUD.
-viewport.grid.enabled = false
viewport.noPadding = true # Remove default frame around viewport
-viewport.show.lights.visible = false
-viewport.Viewport.Viewport0.guide.grid.visible = false
-viewport.Viewport.Viewport0.guide.axis.visible = false
[settings.persistent.exts]
"omni.kit.window.sequencer".useSequencerCamera = true # Sequencer Camera Sync ON
@@ -63,31 +60,19 @@ livestream.skipCapture = 1 # livestream skipCapture ON for local streaming
name = "{{ application_display_name }}"
renderer.resolution.width = 1920
renderer.resolution.height = 1080
-runLoops.main.rateLimitEnabled = true # Enable rate limiting on the main thread
-runLoops.main.rateLimitFrequency = 60 # Lock it to 60fps
-runLoops.main.rateLimitUsePrecisionSleep = true # Use precise sleep values to ensure threads sync
-runLoops.main.syncToPresent = true # Sync with the present thread, smooths UI updates
-runLoops.present.rateLimitEnabled = true # Rate limit the present thread
-runLoops.present.rateLimitFrequency = 60 # Lock it to 60fps
-runLoops.present.rateLimitUsePrecisionSleep = true # Use precise sleep values to ensure threads sync
-runLoops.rendering_0.rateLimitEnabled = true # Enable rate limiting for the rendering thread
-runLoops.rendering_0.rateLimitFrequency = 60 # Lock it to 60fps
-runLoops.rendering_0.rateLimitUsePrecisionSleep = true # Use precise sleep values to ensure threads sync
-runLoops.rendering_0.syncToPresent = true # Sync with the present tread, smooths UI updates
-runLoops.rendering_1.rateLimitEnabled = true # Enable rate limiting for the rendering thread
-runLoops.rendering_1.rateLimitFrequency = 60 # Lock it to 60fps
-runLoops.rendering_1.rateLimitUsePrecisionSleep = true # Use precise sleep values to ensure threads sync
-runLoops.rendering_1.syncToPresent = true # Sync with the present tread, smooths UI updates
-runLoopsGlobal.syncToPresent = true # Sync everything with the present thread
-# When populating Fabric from USD, merge meshes under scene
-# graph instances. Helps with e.g. Tesla
-# Needs to be tested more before enabling for Runway
-usdrt.population.utils.mergeInstances = false
-useFabricSceneDelegate = true # FSD Enabled by default
-viewport.createCameraModelRep = false
-viewport.forceHideFps = true # Show/Hide the performance counters in the upper right corner of the viewport.
-viewport.defaults.fillViewport = true # default to fill viewport
-vsync = true # Enable vsync
+useFabricSceneDelegate = true # Turn on the Fabric scene delegate by default
+
+[settings.app.usdrt.population.utils]
+mergeInstances = false
+mergeMaterials = false
+
+[settings.app.viewport.defaults]
+fillViewport = true
+guide.grid.visible = false
+guide.axis.visible = false
+hud.visible = false
+scene.cameras.visible = false
+scene.lights.visible = false
[settings.app.exts]
folders.'++' = [ # Search paths for extensions.
@@ -138,10 +123,26 @@ listenF7 = false
enableAnonymousData = true
enableAnonymousAppName = true
+[settings.UJITSO]
+# UJITSO supports loading cached representations of assets.
+# These settings controls what is loaded from cache and where from.
+enabled = true # Enable or disable the use of UJITSO cache.
+textures = true
+geometry = true
+materials = true
+datastore.localCachePath="" # The absolute path to the root directory containing cached assets.
+readCacheWithAssetRoot="" # The absolute path to the root directory containing the original non-cached assets.
+
+
# Tests
################################
[[test]]
+
+dependencies = [
+ "{{ setup_extension_name }}.tests"
+]
+
args = [
"--/app/window/width=480",
"--/app/window/height=480",
diff --git a/templates/extensions/basic_cpp/template/config/extension.toml b/templates/extensions/basic_cpp/template/config/extension.toml
index ef1ab60..a166b82 100644
--- a/templates/extensions/basic_cpp/template/config/extension.toml
+++ b/templates/extensions/basic_cpp/template/config/extension.toml
@@ -44,3 +44,7 @@ dependencies = [
args =[
]
+
+cppTests.libraries = [
+ # "bin/${lib_prefix}{{ extension_name }}.tests${lib_ext}",
+]
\ No newline at end of file
diff --git a/templates/extensions/basic_cpp/template/plugins/{{extension_name}}/CppExtension.cpp b/templates/extensions/basic_cpp/template/plugins/{{extension_name}}/CppExtension.cpp
index f612419..961e033 100644
--- a/templates/extensions/basic_cpp/template/plugins/{{extension_name}}/CppExtension.cpp
+++ b/templates/extensions/basic_cpp/template/plugins/{{extension_name}}/CppExtension.cpp
@@ -1,5 +1,6 @@
/*
- * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES.
+ * All rights reserved.
* SPDX-License-Identifier: LicenseRef-NvidiaProprietary
*
* NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
@@ -14,6 +15,7 @@
#include
#include
+#include
#include
#include
diff --git a/templates/extensions/basic_cpp/template/premake5.lua b/templates/extensions/basic_cpp/template/premake5.lua
index a3c86c0..84e5682 100644
--- a/templates/extensions/basic_cpp/template/premake5.lua
+++ b/templates/extensions/basic_cpp/template/premake5.lua
@@ -16,3 +16,9 @@ project_ext_plugin(ext, "{{ extension_name }}.plugin")
add_files("iface", "%{root}/include/omni/ext", "IExt.h")
add_files("impl", "plugins/"..plugin_name)
includedirs { "plugins/"..plugin_name }
+
+-- To write C++ tests, we have to create a shared library with tests to be loaded
+-- project_ext_tests(ext, ext.id..".tests")
+ -- dependson { ext.id..".plugin" }
+ -- add_files("impl", "tests.cpp")
+ -- includedirs { "%{target_deps}/doctest/include" }
diff --git a/templates/extensions/basic_cpp/template/tests.cpp/CppExampleTest.cpp b/templates/extensions/basic_cpp/template/tests.cpp/CppExampleTest.cpp
new file mode 100644
index 0000000..f10c97e
--- /dev/null
+++ b/templates/extensions/basic_cpp/template/tests.cpp/CppExampleTest.cpp
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: LicenseRef-NvidiaProprietary
+ *
+ * NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
+ * property and proprietary rights in and to this material, related
+ * documentation and any modifications thereto. Any use, reproduction,
+ * disclosure or distribution of this material and related documentation
+ * without an express license agreement from NVIDIA CORPORATION or
+ * its affiliates is strictly prohibited.
+ */
+
+#include
+#include
+
+CARB_BINDINGS("{{ extension_name }}.tests")
+
+TEST_SUITE("{{ extension_name }}.tests Test Suite") {
+ TEST_CASE("Sample Test Case") {
+ CHECK(5 == 5);
+ }
+}
\ No newline at end of file
diff --git a/templates/extensions/basic_python/template/{{python_module_path}}/extension.py b/templates/extensions/basic_python/template/{{python_module_path}}/extension.py
index fc98b11..53cc2ee 100644
--- a/templates/extensions/basic_python/template/{{python_module_path}}/extension.py
+++ b/templates/extensions/basic_python/template/{{python_module_path}}/extension.py
@@ -1,4 +1,5 @@
-# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES.
+# All rights reserved.
# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
#
# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
@@ -11,20 +12,28 @@
import omni.ext
-# Functions and vars are available to other extensions as usual in python: `{{python_module}}.some_public_function(x)`
+# Functions and vars are available to other extensions as usual in python:
+# `{{python_module}}.some_public_function(x)`
def some_public_function(x: int):
+ """This is a public function that can be called from other extensions."""
print(f"[{{ extension_name }}] some_public_function was called with {x}")
return x**x
-# Any class derived from `omni.ext.IExt` in the top level module (defined in `python.modules` of `extension.toml`) will
-# be instantiated when the extension gets enabled, and `on_startup(ext_id)` will be called.
-# Later when the extension gets disabled on_shutdown() is called.
+# Any class derived from `omni.ext.IExt` in the top level module (defined in
+# `python.modules` of `extension.toml`) will be instantiated when the extension
+# gets enabled, and `on_startup(ext_id)` will be called. Later when the
+# extension gets disabled on_shutdown() is called.
class MyExtension(omni.ext.IExt):
- # ext_id is the current extension id. It can be used with the extension manager to query additional information,
- # like where this extension is located on the filesystem.
- def on_startup(self, ext_id):
+ """This is a blank extension template."""
+ # ext_id is the current extension id. It can be used with the extension
+ # manager to query additional information, like where this extension is
+ # located on the filesystem.
+ def on_startup(self, _ext_id):
+ """This is called every time the extension is activated."""
print("[{{ extension_name }}] Extension startup")
def on_shutdown(self):
+ """This is called every time the extension is deactivated. It is used
+ to clean up the extension state."""
print("[{{ extension_name }}] Extension shutdown")
diff --git a/templates/extensions/basic_python/template/{{python_module_path}}/tests/test_benchmarks.py b/templates/extensions/basic_python/template/{{python_module_path}}/tests/test_benchmarks.py
index ada67c1..b80546b 100644
--- a/templates/extensions/basic_python/template/{{python_module_path}}/tests/test_benchmarks.py
+++ b/templates/extensions/basic_python/template/{{python_module_path}}/tests/test_benchmarks.py
@@ -1,4 +1,5 @@
-# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES.
+# All rights reserved.
# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
#
# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
@@ -37,18 +38,23 @@ async def benchmark_sleepy_with_custom_metrics(self):
async def benchmark_sleepy_with_custom_metrics_array(self):
"""
- Another benchmark method using custom metrics to demonstrate setting arrays for a metric, and to show that
- there's no crosstalk of metrics between benchmarks.
+ Another benchmark method using custom metrics to demonstrate setting
+ arrays for a metric, and to show that there's no crosstalk of metrics
+ between benchmarks.
"""
- # array of samples for custom metric 'my_other_metric' is set to [1.2ms, 0.9ms, 1.1ms]
- self.set_metric_sample_array(name="my_other_metric", values=[1.2, 0.9, 1.1], unit="ms")
+ # array of samples for custom metric 'my_other_metric' is set to
+ # [1.2ms, 0.9ms, 1.1ms]
+ self.set_metric_sample_array(
+ name="my_other_metric", values=[1.2, 0.9, 1.1], unit="ms"
+ )
await asyncio.sleep(0.01)
class TestBenchmarksNoCustomMetric(AsyncTestCase):
"""
Example Benchmark class without custom metrics.
- * If you are not planning to use custom metrics you can derive from `AsyncTestCase`.
+ * If you are not planning to use custom metrics you can derive from
+ `AsyncTestCase`.
* Benchmark methods have to start with the 'benchmark' prefix.
* The runtime of the benchmark methods and their skip state
belong to the default metrics which are always reported.
diff --git a/templates/extensions/python_ui/template/{{python_module_path}}/extension.py b/templates/extensions/python_ui/template/{{python_module_path}}/extension.py
index 571ede4..c5901ce 100644
--- a/templates/extensions/python_ui/template/{{python_module_path}}/extension.py
+++ b/templates/extensions/python_ui/template/{{python_module_path}}/extension.py
@@ -12,24 +12,31 @@
import omni.ui as ui
-# Functions and vars are available to other extensions as usual in python: `{{python_module}}.some_public_function(x)`
+# Functions and vars are available to other extensions as usual in python:
+# `{{python_module}}.some_public_function(x)`
def some_public_function(x: int):
+ """This is a public function that can be called from other extensions."""
print(f"[{{ extension_name }}] some_public_function was called with {x}")
return x ** x
-# Any class derived from `omni.ext.IExt` in the top level module (defined in `python.modules` of `extension.toml`) will
-# be instantiated when the extension gets enabled, and `on_startup(ext_id)` will be called.
-# Later when the extension gets disabled on_shutdown() is called.
+# Any class derived from `omni.ext.IExt` in the top level module (defined in
+# `python.modules` of `extension.toml`) will be instantiated when the extension
+# gets enabled, and `on_startup(ext_id)` will be called. Later when the
+# extension gets disabled on_shutdown() is called.
class MyExtension(omni.ext.IExt):
- # ext_id is the current extension id. It can be used with the extension manager to query additional information,
- # like where this extension is located on the filesystem.
- def on_startup(self, ext_id):
+ """This extension manages a simple counter UI."""
+ # ext_id is the current extension id. It can be used with the extension
+ # manager to query additional information, like where this extension is
+ # located on the filesystem.
+ def on_startup(self, _ext_id):
+ """This is called every time the extension is activated."""
print("[{{ extension_name }}] Extension startup")
self._count = 0
-
- self._window = ui.Window("{{ extension_display_name }}", width=300, height=300)
+ self._window = ui.Window(
+ "{{ extension_display_name }}", width=300, height=300
+ )
with self._window.frame:
with ui.VStack():
label = ui.Label("")
@@ -49,4 +56,6 @@ def on_reset():
ui.Button("Reset", clicked_fn=on_reset)
def on_shutdown(self):
+ """This is called every time the extension is deactivated. It is used
+ to clean up the extension state."""
print("[{{ extension_name }}] Extension shutdown")
diff --git a/templates/extensions/python_ui/template/{{python_module_path}}/tests/test_hello_world.py b/templates/extensions/python_ui/template/{{python_module_path}}/tests/test_hello_world.py
index 6fade0b..eaadc12 100644
--- a/templates/extensions/python_ui/template/{{python_module_path}}/tests/test_hello_world.py
+++ b/templates/extensions/python_ui/template/{{python_module_path}}/tests/test_hello_world.py
@@ -9,18 +9,22 @@
# its affiliates is strictly prohibited.
# NOTE:
-# omni.kit.test - std python's unittest module with additional wrapping to add suport for async/await tests
-# For most things refer to unittest docs: https://docs.python.org/3/library/unittest.html
+# omni.kit.test - std python's unittest module with additional wrapping to add
+# suport for async/await tests
+# For most things refer to unittest docs:
+# https://docs.python.org/3/library/unittest.html
import omni.kit.test
# Extension for writing UI tests (to simulate UI interaction)
import omni.kit.ui_test as ui_test
-# Import extension python module we are testing with absolute import path, as if we are external user (other extension)
+# Import extension python module we are testing with absolute import path,
+# as if we are external user (other extension)
import {{ python_module }}
-# Having a test class dervived from omni.kit.test.AsyncTestCase declared on the root of module will make it auto-discoverable by omni.kit.test
+# Having a test class dervived from omni.kit.test.AsyncTestCase declared on the
+# root of module will make it auto-discoverable by omni.kit.test
class Test(omni.kit.test.AsyncTestCase):
# Before running each test
async def setUp(self):
@@ -36,13 +40,16 @@ async def test_hello_public_function(self):
self.assertEqual(result, 256)
async def test_window_button(self):
-
# Find a label in our window
label = ui_test.find("{{ extension_display_name }}//Frame/**/Label[*]")
# Find buttons in our window
- add_button = ui_test.find("{{ extension_display_name }}//Frame/**/Button[*].text=='Add'")
- reset_button = ui_test.find("{{ extension_display_name }}//Frame/**/Button[*].text=='Reset'")
+ add_button = ui_test.find(
+ "{{ extension_display_name }}//Frame/**/Button[*].text=='Add'"
+ )
+ reset_button = ui_test.find(
+ "{{ extension_display_name }}//Frame/**/Button[*].text=='Reset'"
+ )
# Click reset button
await reset_button.click()
diff --git a/templates/extensions/service.setup/template/{{python_module_path}}/extension.py b/templates/extensions/service.setup/template/{{python_module_path}}/extension.py
index bbd6189..5b08b7d 100644
--- a/templates/extensions/service.setup/template/{{python_module_path}}/extension.py
+++ b/templates/extensions/service.setup/template/{{python_module_path}}/extension.py
@@ -13,18 +13,22 @@
from .service import router
-# Any class derived from `omni.ext.IExt` in the top level module (defined in `python.modules` of `extension.toml`) will
-# be instantiated when the extension gets enabled, and `on_startup(ext_id)` will be called.
-# Later when the extension gets disabled on_shutdown() is called.
+# Any class derived from `omni.ext.IExt` in the top level module (defined in
+# `python.modules` of `extension.toml`) will be instantiated when the extension
+# gets enabled, and `on_startup(ext_id)` will be called. Later when the
+# extension gets disabled on_shutdown() is called.
class MyExtension(omni.ext.IExt):
- # ext_id is the current extension id. It can be used with the extension manager to
- # query additional information, like where this extension is located on the filesystem.
- def on_startup(self, ext_id):
-
+ """This extension manages the service setup"""
+ # ext_id is the current extension id. It can be used with the extension
+ # manager to query additional information, like where this extension is
+ # located on the filesystem.
+ def on_startup(self, _ext_id):
+ """This is called every time the extension is activated."""
main.register_router(router)
-
print("[{{ extension_name }}] MyExtension startup : Local Docs - http://localhost:8011/docs")
def on_shutdown(self):
+ """This is called every time the extension is deactivated. It is used
+ to clean up the extension state."""
main.deregister_router(router)
- print("[{{ extension_name }}] MyExtension shutdown")
\ No newline at end of file
+ print("[{{ extension_name }}] MyExtension shutdown")
diff --git a/templates/extensions/service.setup/template/{{python_module_path}}/service.py b/templates/extensions/service.setup/template/{{python_module_path}}/service.py
index 0254406..ee470e7 100644
--- a/templates/extensions/service.setup/template/{{python_module_path}}/service.py
+++ b/templates/extensions/service.setup/template/{{python_module_path}}/service.py
@@ -9,11 +9,11 @@
# its affiliates is strictly prohibited.
from pathlib import Path
-import omni.usd
-import omni.kit.commands
from pydantic import BaseModel, Field
-from omni.services.core.routers import ServiceAPIRouter
+import omni.kit.commands
+import omni.usd
+from omni.services.core.routers import ServiceAPIRouter
router = ServiceAPIRouter(tags=["{{ extension_display_name }}"])
@@ -72,7 +72,9 @@ async def generate_cube(cube_data: CubeDataModel):
)
# save stage
- asset_file_path = str(Path(cube_data.asset_write_location).joinpath(f"{cube_data.asset_name}.usda"))
+ asset_file_path = str(Path(
+ cube_data.asset_write_location).joinpath(f"{cube_data.asset_name}.usda")
+ )
stage.GetRootLayer().Export(asset_file_path)
msg = f"[{{ extension_name }}] Wrote a cube to this path: {asset_file_path}"
print(msg)
diff --git a/templates/extensions/service.setup/template/{{python_module_path}}/tests/test_benchmarks.py b/templates/extensions/service.setup/template/{{python_module_path}}/tests/test_benchmarks.py
index ada67c1..1b2e2ee 100644
--- a/templates/extensions/service.setup/template/{{python_module_path}}/tests/test_benchmarks.py
+++ b/templates/extensions/service.setup/template/{{python_module_path}}/tests/test_benchmarks.py
@@ -37,18 +37,23 @@ async def benchmark_sleepy_with_custom_metrics(self):
async def benchmark_sleepy_with_custom_metrics_array(self):
"""
- Another benchmark method using custom metrics to demonstrate setting arrays for a metric, and to show that
- there's no crosstalk of metrics between benchmarks.
+ Another benchmark method using custom metrics to demonstrate setting
+ arrays for a metric, and to show that there's no crosstalk of metrics
+ between benchmarks.
"""
- # array of samples for custom metric 'my_other_metric' is set to [1.2ms, 0.9ms, 1.1ms]
- self.set_metric_sample_array(name="my_other_metric", values=[1.2, 0.9, 1.1], unit="ms")
+ # array of samples for custom metric 'my_other_metric' is set to
+ # [1.2ms, 0.9ms, 1.1ms]
+ self.set_metric_sample_array(
+ name="my_other_metric", values=[1.2, 0.9, 1.1], unit="ms"
+ )
await asyncio.sleep(0.01)
class TestBenchmarksNoCustomMetric(AsyncTestCase):
"""
Example Benchmark class without custom metrics.
- * If you are not planning to use custom metrics you can derive from `AsyncTestCase`.
+ * If you are not planning to use custom metrics you can derive
+ from `AsyncTestCase`.
* Benchmark methods have to start with the 'benchmark' prefix.
* The runtime of the benchmark methods and their skip state
belong to the default metrics which are always reported.
diff --git a/templates/extensions/usd_composer.setup/template/config/extension.toml b/templates/extensions/usd_composer.setup/template/config/extension.toml
index b5f3603..11bf6d5 100644
--- a/templates/extensions/usd_composer.setup/template/config/extension.toml
+++ b/templates/extensions/usd_composer.setup/template/config/extension.toml
@@ -56,7 +56,18 @@ pages = [
[[test]]
dependencies = [
+ "omni.kit.mainwindow",
+ "omni.kit.ui_test",
]
args = [
+ "--/app/fastShutdown=1",
+ "--/app/file/ignoreUnsavedOnExit=true",
+ "--/app/window/dpiScaleOverride=1.0",
+ "--/app/window/height=720",
+ "--/app/window/scaleToMonitor=false",
+ "--/app/window/width=1280",
+ "--/exts/omni.kit.viewport.window/startup/windowName=Viewport",
+ "--no-window",
+ "--reset-user"
]
diff --git a/templates/extensions/usd_composer.setup/template/{{python_module_path}}/extension.py b/templates/extensions/usd_composer.setup/template/{{python_module_path}}/extension.py
index fde9e18..c55fa5d 100644
--- a/templates/extensions/usd_composer.setup/template/{{python_module_path}}/extension.py
+++ b/templates/extensions/usd_composer.setup/template/{{python_module_path}}/extension.py
@@ -8,11 +8,17 @@
#
import asyncio
+import inspect
import logging
import os
+import platform
+import subprocess
import sys
+import webbrowser
from pathlib import Path
+
+
import carb.imgui as _imgui
import carb.input
import carb.settings
@@ -23,78 +29,98 @@
import omni.kit.menu.utils
import omni.kit.stage_templates as stage_templates
import omni.kit.ui
+import omni.kit.window.property as property_window_ext
import omni.ui as ui
import omni.usd
+from omni.kit.menu.utils import MenuLayout
+from omni.kit.property.usd import PrimPathWidget
+from omni.kit.quicklayout import QuickLayout
from omni.kit.window.title import get_main_window_title
-DATA_PATH = Path(carb.tokens.get_tokens_interface().resolve("${% raw %}{{% endraw %}{{ extension_name }}{% raw %}}{% endraw %}"))
+DATA_PATH = Path(carb.tokens.get_tokens_interface().resolve(
+ "${% raw %}{{% endraw %}{{ extension_name }}{% raw %}}{% endraw %}")
+)
async def _load_layout(layout_file: str, keep_windows_open=False):
+ """Loads a provided layout file and ensures the viewport is set to FILL."""
try:
- from omni.kit.quicklayout import QuickLayout
-
- # few frames delay to avoid the conflict with the layout of omni.kit.mainwindow
- for i in range(3):
+ # few frames delay to avoid the conflict with the
+ # layout of omni.kit.mainwindow
+ for _ in range(3):
await omni.kit.app.get_app().next_update_async()
QuickLayout.load_file(layout_file, keep_windows_open)
-
- except Exception as exc:
- pass
-
+ except:
QuickLayout.load_file(layout_file)
class CreateSetupExtension(omni.ext.IExt):
"""Create Final Configuration"""
-
- def on_startup(self, ext_id):
- """setup the window layout, menu, final configuration of the extensions etc"""
+ def on_startup(self, _ext_id):
+ """
+ setup the window layout, menu, final configuration
+ of the extensions etc
+ """
self._settings = carb.settings.get_settings()
self._menu_layout = []
telemetry_logger = logging.getLogger("idl.telemetry.opentelemetry")
telemetry_logger.setLevel(logging.ERROR)
- # this is a work around as some Extensions don't properly setup their default setting in time
+ # this is a work around as some Extensions don't properly setup their
+ # default setting in time
self._set_defaults()
# adjust couple of viewport settings
self._settings.set("/app/viewport/boundingBoxes/enabled", True)
# Force enable Axis, Grid, Outline and Lights
- forceEnable = self._settings.get("/app/create/forceViewportSettings")
- if forceEnable:
- display_options = self._settings.get("/persistent/app/viewport/displayOptions")
+ if self._settings.get("/app/create/forceViewportSettings"):
+ display_options = self._settings.get(
+ "/persistent/app/viewport/displayOptions"
+ )
# Note: flags are from omni/kit/ViewportTypes.h
- kShowFlagAxis = 1 << 1
- kShowFlagGrid = 1 << 6
- kShowFlagSelectionOutline = 1 << 7
- kShowFlagLight = 1 << 8
+ show_flag_axis = 1 << 1
+ show_flag_grid = 1 << 6
+ show_flag_selection_outline = 1 << 7
+ show_flag_light = 1 << 8
display_options = (
- display_options | (kShowFlagAxis) | (kShowFlagGrid) | (kShowFlagSelectionOutline) | (kShowFlagLight)
+ display_options | (show_flag_axis) | (show_flag_grid) |
+ (show_flag_selection_outline) | (show_flag_light)
+ )
+ self._settings.set(
+ "/persistent/app/viewport/displayOptions", display_options
)
- self._settings.set("/persistent/app/viewport/displayOptions", display_options)
# Make sure these are in sync from changes above
self._settings.set("/app/viewport/show/lights", True)
self._settings.set("/app/viewport/grid/enabled", True)
self._settings.set("/app/viewport/outline/enabled", True)
- # Make sure any action-graph setup locking out user from HUD does not persist across re-launch
- self._settings.set("/persistent/app/viewport/Viewport/Viewport0/hud/visible", True)
- self._settings.set("/persistent/app/viewport/Viewport 2/Viewport0/hud/visible", True)
+ # Make sure any action-graph setup locking out user from HUD does
+ # not persist across re-launch
+ self._settings.set(
+ "/persistent/app/viewport/Viewport/Viewport0/hud/visible",
+ True
+ )
+ self._settings.set(
+ "/persistent/app/viewport/Viewport 2/Viewport0/hud/visible",
+ True
+ )
# These two settings do not co-operate well on ADA cards, so for
# now simulate a toggle of the present thread on startup to work around
- if self._settings.get("/exts/omni.kit.renderer.core/present/enabled") and self._settings.get(
+ if self._settings.get("/exts/omni.kit.renderer.core/present/enabled") \
+ and self._settings.get(
"/exts/omni.kit.widget.viewport/autoAttach/mode"
):
-
async def _toggle_present(settings, n_waits: int = 1):
async def _toggle_setting(app, enabled: bool, n_waits: int):
for _ in range(n_waits):
await app.next_update_async()
- settings.set("/exts/omni.kit.renderer.core/present/enabled", enabled)
+ settings.set(
+ "/exts/omni.kit.renderer.core/present/enabled",
+ enabled
+ )
app = omni.kit.app.get_app()
await _toggle_setting(app, False, n_waits)
@@ -103,32 +129,40 @@ async def _toggle_setting(app, enabled: bool, n_waits: int):
asyncio.ensure_future(_toggle_present(self._settings))
# Setting and Saving FSD as a global change in preferences
- # Requires to listen for changes at the local path to update Composer's persistent path.
+ # Requires to listen for changes at the local path to update
+ # Composer's persistent path.
fabric_app_setting = self._settings.get("/app/useFabricSceneDelegate")
- fabric_persistent_setting = self._settings.get("/persistent/app/useFabricSceneDelegate")
- fabric_enabled: bool = fabric_app_setting if fabric_persistent_setting == None else fabric_persistent_setting
+ fabric_persistent_setting = self._settings.get(
+ "/persistent/app/useFabricSceneDelegate"
+ )
+ fabric_enabled: bool = fabric_app_setting if \
+ fabric_persistent_setting is None else fabric_persistent_setting
self._settings.set("/app/useFabricSceneDelegate", fabric_enabled)
- self._sub_fabric_delegate_changed = omni.kit.app.SettingChangeSubscription(
- "/app/useFabricSceneDelegate",
- self._on_fabric_delegate_changed
- )
+ self._sub_fabric_delegate_changed = \
+ omni.kit.app.SettingChangeSubscription(
+ "/app/useFabricSceneDelegate",
+ self._on_fabric_delegate_changed
+ )
# Adjust the Window Title to show the Create Version
window_title = get_main_window_title()
app_version = self._settings.get("/app/version")
if not app_version:
- app_version = open(carb.tokens.get_tokens_interface().resolve("${app}/../VERSION")).read()
+ with open(
+ carb.tokens.get_tokens_interface().resolve("${app}/../VERSION"),
+ encoding="utf-8"
+ ) as f:
+ app_version = f.read()
if app_version:
if "+" in app_version:
app_version, _ = app_version.split("+")
# for RC version we remove some details
- PRODUCTION = self._settings.get("/privacy/externalBuild")
- if PRODUCTION:
+ if self._settings.get("/privacy/externalBuild"):
if "-" in app_version:
app_version, _ = app_version.split("-")
window_title.set_app_version(app_version)
@@ -137,9 +171,18 @@ async def _toggle_setting(app, enabled: bool, n_waits: int):
# setup some imgui Style overide
imgui = _imgui.acquire_imgui()
- imgui.push_style_color(_imgui.StyleColor.ScrollbarGrab, carb.Float4(0.4, 0.4, 0.4, 1))
- imgui.push_style_color(_imgui.StyleColor.ScrollbarGrabHovered, carb.Float4(0.6, 0.6, 0.6, 1))
- imgui.push_style_color(_imgui.StyleColor.ScrollbarGrabActive, carb.Float4(0.8, 0.8, 0.8, 1))
+ imgui.push_style_color(
+ _imgui.StyleColor.ScrollbarGrab,
+ carb.Float4(0.4, 0.4, 0.4, 1)
+ )
+ imgui.push_style_color(
+ _imgui.StyleColor.ScrollbarGrabHovered,
+ carb.Float4(0.6, 0.6, 0.6, 1)
+ )
+ imgui.push_style_color(
+ _imgui.StyleColor.ScrollbarGrabActive,
+ carb.Float4(0.8, 0.8, 0.8, 1)
+ )
imgui.push_style_var_float(_imgui.StyleVar.DockSplitterSize, 2)
@@ -149,58 +192,98 @@ async def _toggle_setting(app, enabled: bool, n_waits: int):
test_mode = self._settings.get("/app/testMode")
if not test_mode:
- self.__setup_window_task = asyncio.ensure_future(_load_layout(layout_file, True))
+ asyncio.ensure_future(_load_layout(layout_file, True))
- self.__setup_property_window = asyncio.ensure_future(self.__property_window())
+ asyncio.ensure_future(self.__property_window())
self.__menu_update()
- if not test_mode and not self._settings.get("/app/content/emptyStageOnStart"):
- self.__await_new_scene = asyncio.ensure_future(self.__new_stage())
+ if not test_mode and not \
+ self._settings.get("/app/content/emptyStageOnStart"):
+ asyncio.ensure_future(self.__new_stage())
- startup_time = omni.kit.app.get_app_interface().get_time_since_start_s()
- self._settings.set("/crashreporter/data/startup_time", f"{startup_time}")
+ startup_time = \
+ omni.kit.app.get_app_interface().get_time_since_start_s()
+ self._settings.set(
+ "/crashreporter/data/startup_time", f"{startup_time}"
+ )
- def show_documentation(*x):
- import webbrowser
- webbrowser.open("https://docs.omniverse.nvidia.com/composer/latest/index.html")
+ def show_documentation(*args):
+ webbrowser.open(
+ "https://docs.omniverse.nvidia.com/composer/latest/index.html"
+ )
self._help_menu_items = [
- omni.kit.menu.utils.MenuItemDescription(name="Documentation",
- onclick_fn=show_documentation,
- appear_after=[omni.kit.menu.utils.MenuItemOrder.FIRST])
+ omni.kit.menu.utils.MenuItemDescription(
+ name="Documentation",
+ onclick_fn=show_documentation,
+ appear_after=[omni.kit.menu.utils.MenuItemOrder.FIRST]
+ )
]
omni.kit.menu.utils.add_menu_items(self._help_menu_items, name="Help")
def _set_defaults(self):
- """this is trying to setup some defaults for extensions to avoid warning"""
+ """
+ This is trying to setup some defaults for extensions to avoid warnings.
+ """
self._settings.set_default("/persistent/app/omniverse/bookmarks", {})
- self._settings.set_default("/persistent/app/stage/timeCodeRange", [0, 100])
+ self._settings.set_default(
+ "/persistent/app/stage/timeCodeRange", [0, 100]
+ )
- self._settings.set_default("/persistent/audio/context/closeAudioPlayerOnStop", False)
+ self._settings.set_default(
+ "/persistent/audio/context/closeAudioPlayerOnStop",
+ False
+ )
- self._settings.set_default("/persistent/app/primCreation/PrimCreationWithDefaultXformOps", True)
- self._settings.set_default("/persistent/app/primCreation/DefaultXformOpType", "Scale, Rotate, Translate")
- self._settings.set_default("/persistent/app/primCreation/DefaultRotationOrder", "ZYX")
- self._settings.set_default("/persistent/app/primCreation/DefaultXformOpPrecision", "Double")
+ self._settings.set_default(
+ "/persistent/app/primCreation/PrimCreationWithDefaultXformOps",
+ True
+ )
+ self._settings.set_default(
+ "/persistent/app/primCreation/DefaultXformOpType",
+ "Scale, Rotate, Translate"
+ )
+ self._settings.set_default(
+ "/persistent/app/primCreation/DefaultRotationOrder",
+ "ZYX"
+ )
+ self._settings.set_default(
+ "/persistent/app/primCreation/DefaultXformOpPrecision",
+ "Double"
+ )
# omni.kit.property.tagging
- self._settings.set_default("/persistent/exts/omni.kit.property.tagging/showAdvancedTagView", False)
- self._settings.set_default("/persistent/exts/omni.kit.property.tagging/showHiddenTags", False)
- self._settings.set_default("/persistent/exts/omni.kit.property.tagging/modifyHiddenTags", False)
+ self._settings.set_default(
+ "/persistent/exts/omni.kit.property.tagging/showAdvancedTagView",
+ False
+ )
+ self._settings.set_default(
+ "/persistent/exts/omni.kit.property.tagging/showHiddenTags",
+ False
+ )
+ self._settings.set_default(
+ "/persistent/exts/omni.kit.property.tagging/modifyHiddenTags",
+ False
+ )
self._settings.set_default(
"/rtx/sceneDb/ambientLightIntensity", 0.0
) # set default ambientLight intensity to Zero
- def _on_fabric_delegate_changed(self, value: str, event_type: carb.settings.ChangeEventType):
+ def _on_fabric_delegate_changed(
+ self, _v: str, event_type: carb.settings.ChangeEventType):
if event_type == carb.settings.ChangeEventType.CHANGED:
- enabled: bool = self._settings.get_as_bool("/app/useFabricSceneDelegate")
- self._settings.set("/persistent/app/useFabricSceneDelegate", enabled)
+ enabled: bool = self._settings.get_as_bool(
+ "/app/useFabricSceneDelegate"
+ )
+ self._settings.set(
+ "/persistent/app/useFabricSceneDelegate", enabled
+ )
async def __new_stage(self):
-
- # 10 frame delay to allow Layout
- for i in range(5):
+ """Create a new stage """
+ # 5 frame delay to allow Layout
+ for _ in range(5):
await omni.kit.app.get_app().next_update_async()
if omni.usd.get_context().can_open_stage():
@@ -208,10 +291,6 @@ async def __new_stage(self):
def _launch_app(self, app_id, console=True, custom_args=None):
"""launch another Kit app with the same settings"""
- import platform
- import subprocess
- import sys
-
app_path = carb.tokens.get_tokens_interface().resolve("${app}")
kit_file_path = os.path.join(app_path, app_id)
@@ -236,7 +315,8 @@ def _launch_app(self, app_id, console=True, custom_args=None):
kwargs = {"close_fds": False}
if platform.system().lower() == "windows":
if console:
- kwargs["creationflags"] = subprocess.CREATE_NEW_CONSOLE | subprocess.CREATE_NEW_PROCESS_GROUP
+ kwargs["creationflags"] = subprocess.CREATE_NEW_CONSOLE | \
+ subprocess.CREATE_NEW_PROCESS_GROUP
else:
kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
@@ -248,25 +328,28 @@ def _show_ui_docs(self):
def _show_launcher(self):
"""show the omniverse ui documentation as an external Application"""
- self._launch_app("omni.create.launcher.kit", console=False, custom_args={"--/app/auto_launch=false"})
+ self._launch_app(
+ "omni.create.launcher.kit",
+ console=False,
+ custom_args={"--/app/auto_launch=false"}
+ )
async def __property_window(self):
+ """Creates a propety window and sets column sizes."""
await omni.kit.app.get_app().next_update_async()
- import omni.kit.window.property as property_window_ext
- from omni.kit.property.usd import PrimPathWidget
property_window = property_window_ext.get_window()
property_window.set_scheme_delegate_layout(
"Create Layout",
- ["basis_curves_prim", "path_prim", "material_prim", "xformable_prim", "shade_prim", "camera_prim"],
+ ["basis_curves_prim", "path_prim", "material_prim",
+ "xformable_prim", "shade_prim", "camera_prim"],
)
- # expand width of path_items to "Instancable" doesn't get wrapped
+ # expand width of path_items so "Instancable" doesn't get wrapped
PrimPathWidget.set_path_item_padding(3.5)
def __menu_update(self):
- from omni.kit.menu.utils import MenuLayout
-
+ """Update the menu"""
editor_menu = omni.kit.ui.get_editor_menu()
self._menu_layout = [
@@ -282,8 +365,6 @@ def __menu_update(self):
MenuLayout.Item("Retargeting"),
MenuLayout.Item("Animation Graph"),
MenuLayout.Item("Animation Graph Samples"),
- # MenuLayout.Item("Keyframer"),
- # MenuLayout.Item("Recorder"),
],
),
MenuLayout.SubMenu(
@@ -314,11 +395,23 @@ def __menu_update(self):
"Simulation",
[
MenuLayout.Group("Flow", source="Window/Flow"),
- MenuLayout.Group("Blast Destruction", source="Window/Blast"),
- MenuLayout.Group("Blast Destruction", source="Window/Blast Destruction"),
- MenuLayout.Group("Boom Collision Audio", source="Window/Boom"),
- MenuLayout.Group("Boom Collision Audio", source="Window/Boom Collision Audio"),
- MenuLayout.Group("Physics", source="Window/Physics"),
+ MenuLayout.Group(
+ "Blast Destruction", source="Window/Blast"
+ ),
+ MenuLayout.Group(
+ "Blast Destruction",
+ source="Window/Blast Destruction"
+ ),
+ MenuLayout.Group(
+ "Boom Collision Audio", source="Window/Boom"
+ ),
+ MenuLayout.Group(
+ "Boom Collision Audio",
+ source="Window/Boom Collision Audio"
+ ),
+ MenuLayout.Group(
+ "Physics", source="Window/Physics"
+ ),
],
),
MenuLayout.SubMenu(
@@ -333,9 +426,10 @@ def __menu_update(self):
MenuLayout.Item("Asset Validator"),
],
),
- MenuLayout.Sort(exclude_items=["Extensions"], sort_submenus=True),
+ MenuLayout.Sort(
+ exclude_items=["Extensions"], sort_submenus=True
+ ),
MenuLayout.Item("New Viewport Window", remove=True),
- # MenuLayout.Item("Material Preview", remove=True),
],
),
MenuLayout.Menu(
@@ -343,32 +437,40 @@ def __menu_update(self):
[
MenuLayout.Item("Default", source="Reset Layout"),
MenuLayout.Seperator(),
- MenuLayout.Item("UI Toggle Visibility", source="Window/UI Toggle Visibility"),
- MenuLayout.Item("Fullscreen Mode", source="Window/Fullscreen Mode"),
+ MenuLayout.Item(
+ "UI Toggle Visibility",
+ source="Window/UI Toggle Visibility"
+ ),
+ MenuLayout.Item(
+ "Fullscreen Mode", source="Window/Fullscreen Mode"
+ ),
MenuLayout.Seperator(),
- MenuLayout.Item("Save Layout", source="Window/Layout/Save Layout..."),
- MenuLayout.Item("Load Layout", source="Window/Layout/Load Layout..."),
+ MenuLayout.Item(
+ "Save Layout", source="Window/Layout/Save Layout..."
+ ),
+ MenuLayout.Item(
+ "Load Layout", source="Window/Layout/Load Layout..."
+ ),
MenuLayout.Seperator(),
- MenuLayout.Item("Quick Save", source="Window/Layout/Quick Save"),
- MenuLayout.Item("Quick Load", source="Window/Layout/Quick Load"),
+ MenuLayout.Item(
+ "Quick Save", source="Window/Layout/Quick Save"
+ ),
+ MenuLayout.Item(
+ "Quick Load", source="Window/Layout/Quick Load"
+ ),
],
),
]
omni.kit.menu.utils.add_layout(self._menu_layout)
- # set omnu.ui Help Menu
- # self._ui_doc_menu_path = "Help/Omni UI Docs"
- # self._ui_doc_menu_item = editor_menu.add_item(self._ui_doc_menu_path, lambda *_: self._show_ui_docs())
- # editor_menu.set_priority(self._ui_doc_menu_path, -10)
-
self._layout_menu_items = []
self._current_layout_priority = 20
def add_layout_menu_entry(name, parameter, key):
- import inspect
-
menu_path = f"Layout/{name}"
- menu = editor_menu.add_item(menu_path, None, False, self._current_layout_priority)
+ menu = editor_menu.add_item(
+ menu_path, None, False, self._current_layout_priority
+ )
self._current_layout_priority = self._current_layout_priority + 1
if inspect.isfunction(parameter):
@@ -379,7 +481,6 @@ def add_layout_menu_entry(name, parameter, key):
(carb.input.KEYBOARD_MODIFIER_FLAG_CONTROL, key),
)
else:
-
async def _active_layout(layout):
await _load_layout(layout)
# load layout file again to make sure layout correct
@@ -387,36 +488,42 @@ async def _active_layout(layout):
menu_action = omni.kit.menu.utils.add_action_to_menu(
menu_path,
- lambda *_: asyncio.ensure_future(_active_layout(f"{DATA_PATH}/layouts/{parameter}.json")),
+ lambda *_: asyncio.ensure_future(
+ _active_layout(f"{DATA_PATH}/layouts/{parameter}.json")
+ ),
name,
(carb.input.KEYBOARD_MODIFIER_FLAG_CONTROL, key),
)
self._layout_menu_items.append((menu, menu_action))
- add_layout_menu_entry("Reset Layout", "default", carb.input.KeyboardInput.KEY_1)
+ add_layout_menu_entry(
+ "Reset Layout", "default", carb.input.KeyboardInput.KEY_1
+ )
# create Quick Load & Quick Save
- from omni.kit.quicklayout import QuickLayout
-
async def quick_save():
QuickLayout.quick_save(None, None)
async def quick_load():
QuickLayout.quick_load(None, None)
- add_layout_menu_entry("Quick Save", quick_save, carb.input.KeyboardInput.KEY_7)
- add_layout_menu_entry("Quick Load", quick_load, carb.input.KeyboardInput.KEY_8)
+ add_layout_menu_entry(
+ "Quick Save", quick_save, carb.input.KeyboardInput.KEY_7
+ )
+ add_layout_menu_entry(
+ "Quick Load", quick_load, carb.input.KeyboardInput.KEY_8
+ )
# open "Asset Stores" window
ui.Workspace.show_window("Asset Stores")
def on_shutdown(self):
+ """Clean up the extension"""
self._sub_fabric_delegate_changed = None
omni.kit.menu.utils.remove_layout(self._menu_layout)
self._menu_layout = None
self._layout_menu_items = None
- # self._ui_doc_menu_item = None
self._launcher_menu = None
self._reset_menu = None
diff --git a/templates/extensions/usd_composer.setup/template/{{python_module_path}}/tests/__init__.py b/templates/extensions/usd_composer.setup/template/{{python_module_path}}/tests/__init__.py
new file mode 100644
index 0000000..e866cdc
--- /dev/null
+++ b/templates/extensions/usd_composer.setup/template/{{python_module_path}}/tests/__init__.py
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
+#
+# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
+# property and proprietary rights in and to this material, related
+# documentation and any modifications thereto. Any use, reproduction,
+# disclosure or distribution of this material and related documentation
+# without an express license agreement from NVIDIA CORPORATION or
+# its affiliates is strictly prohibited.
+
+# run startup tests first
+from .test_app_startup import *
+from .test_app_extensions import *
diff --git a/templates/extensions/usd_composer.setup/template/{{python_module_path}}/tests/test_app_extensions.py b/templates/extensions/usd_composer.setup/template/{{python_module_path}}/tests/test_app_extensions.py
new file mode 100644
index 0000000..eb85bd2
--- /dev/null
+++ b/templates/extensions/usd_composer.setup/template/{{python_module_path}}/tests/test_app_extensions.py
@@ -0,0 +1,45 @@
+# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
+#
+# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
+# property and proprietary rights in and to this material, related
+# documentation and any modifications thereto. Any use, reproduction,
+# disclosure or distribution of this material and related documentation
+# without an express license agreement from NVIDIA CORPORATION or
+# its affiliates is strictly prohibited.
+
+import omni.kit.app
+from omni.kit.test import AsyncTestCase
+
+
+class TestUSDComposerExtensions(AsyncTestCase):
+ # NOTE: Function pulled to remove dependency from omni.kit.core.tests
+ def _validate_extensions_load(self):
+ failures = []
+ manager = omni.kit.app.get_app().get_extension_manager()
+ for ext in manager.get_extensions():
+ ext_id = ext["id"]
+ ext_name = ext["name"]
+ info = manager.get_extension_dict(ext_id)
+
+ enabled = ext.get("enabled", False)
+ if not enabled:
+ continue
+
+ failed = info.get("state/failed", False)
+ if failed:
+ failures.append(ext_name)
+
+ if len(failures) == 0:
+ print("\n[success] All extensions loaded successfully!\n")
+ else:
+ print("")
+ print(f"[error] Found {len(failures)} extensions that could not load:")
+ for count, ext in enumerate(failures):
+ print(f" {count+1}: {ext}")
+ print("")
+ return len(failures)
+
+ async def test_l1_extensions_load(self):
+ """Loop all enabled extensions to see if they loaded correctly"""
+ self.assertEqual(self._validate_extensions_load(), 0)
\ No newline at end of file
diff --git a/templates/extensions/usd_composer.setup/template/{{python_module_path}}/tests/test_app_startup.py b/templates/extensions/usd_composer.setup/template/{{python_module_path}}/tests/test_app_startup.py
new file mode 100644
index 0000000..ef69ade
--- /dev/null
+++ b/templates/extensions/usd_composer.setup/template/{{python_module_path}}/tests/test_app_startup.py
@@ -0,0 +1,71 @@
+# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
+#
+# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
+# property and proprietary rights in and to this material, related
+# documentation and any modifications thereto. Any use, reproduction,
+# disclosure or distribution of this material and related documentation
+# without an express license agreement from NVIDIA CORPORATION or
+# its affiliates is strictly prohibited.
+
+# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
+#
+# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
+# property and proprietary rights in and to this material, related
+# documentation and any modifications thereto. Any use, reproduction,
+# disclosure or distribution of this material and related documentation
+# without an express license agreement from NVIDIA CORPORATION or
+# its affiliates is strictly prohibited.
+
+import json
+import time
+from typing import Tuple
+
+import carb.settings
+import omni.kit.app
+from omni.kit.test import AsyncTestCase
+
+
+class TestAppStartup(AsyncTestCase):
+ def app_startup_time(self, test_id: str) -> float:
+ """Get startup time - send to nvdf"""
+ test_start_time = time.monotonic()
+ startup_time = omni.kit.app.get_app().get_time_since_start_s()
+ test_result = {"startup_time_s": startup_time}
+ print(f"App Startup time: {startup_time}")
+ return startup_time
+
+ def app_startup_warning_count(self, test_id: str) -> Tuple[int, int]:
+ """Get the count of warnings during startup - send to nvdf"""
+ test_start_time = time.monotonic()
+ warning_count = 0
+ error_count = 0
+ log_file_path = carb.settings.get_settings().get("/log/file")
+ with open(log_file_path, "r") as file:
+ for line in file:
+ if "[Warning]" in line:
+ warning_count += 1
+ elif "[Error]" in line:
+ error_count += 1
+
+ test_result = {"startup_warning_count": warning_count, "startup_error_count": error_count}
+ print(f"App Startup Warning count: {warning_count}")
+ print(f"App Startup Error count: {error_count}")
+ return warning_count, error_count
+
+ async def test_l1_app_startup_time(self):
+ """Get startup time - send to nvdf"""
+ for _ in range(60):
+ await omni.kit.app.get_app().next_update_async()
+
+ self.app_startup_time(self.id())
+ self.assertTrue(True)
+
+ async def test_l1_app_startup_warning_count(self):
+ """Get the count of warnings during startup - send to nvdf"""
+ for _ in range(60):
+ await omni.kit.app.get_app().next_update_async()
+
+ self.app_startup_warning_count(self.id())
+ self.assertTrue(True)
diff --git a/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/menu_helper.py b/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/menu_helper.py
index 83c7863..5d150ec 100644
--- a/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/menu_helper.py
+++ b/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/menu_helper.py
@@ -1,4 +1,5 @@
-# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES.
+# All rights reserved.
# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
#
# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
@@ -21,14 +22,16 @@
class MenuHelper:
- def __init__(self) -> None:
+ """Helper class to manage the main menu layout based on the
+ application mode."""
+ def __init__(self):
self._settings = carb.settings.get_settings()
self._current_layout = None
self._pending_layout = None
self._changing_layout_task: asyncio.Task = None
-
self._menu_layout_empty = []
self._menu_layout_modify = []
+ self._app_ready_sub = None
omni.kit.menu.utils.add_hook(self._menu_hook)
@@ -37,7 +40,8 @@ def __init__(self) -> None:
)
self._menu_hook()
- def destroy(self) -> None:
+ def destroy(self):
+ """Tear down the menu helper."""
omni.kit.menu.utils.remove_hook(self._menu_hook)
if self._changing_layout_task and not self._changing_layout_task.done():
@@ -54,12 +58,15 @@ def destroy(self) -> None:
omni.kit.menu.utils.remove_layout(self._current_layout)
self._current_layout = None
- def _menu_hook(self, *args, **kwargs) -> None:
+ def _menu_hook(self, *args, **kwargs):
+ """Get the menu instance and build the desired menu."""
if self._settings.get_as_bool("/app/view/debug/menus"):
return
LAYOUT_EMPTY_ALLOWED_MENUS = set(["Developer",])
- LAYOUT_MODIFY_ALLOWED_MENUS = {"File", "Edit", "Window", "Tools", "Help", "Developer",}
+ LAYOUT_MODIFY_ALLOWED_MENUS = {
+ "File", "Edit", "Window", "Tools", "Help", "Developer",
+ }
# make NEW list object instead of clear original
# the original list may be held by self._current_layout and omni.kit.menu.utils
@@ -71,41 +78,54 @@ def _menu_hook(self, *args, **kwargs) -> None:
return
# Build new layouts using allowlists
- for key in menu_instance._menu_defs:
+ menu_defs, _menu_order, _menu_delegates = menu_instance.get_menu_data()
+
+ for key in menu_defs:
if key.lower().endswith("widget"):
continue
if key not in LAYOUT_EMPTY_ALLOWED_MENUS:
- self._menu_layout_empty.append(MenuLayout.Menu(key, remove=True))
+ self._menu_layout_empty.append(
+ MenuLayout.Menu(key, remove=True)
+ )
if key not in LAYOUT_MODIFY_ALLOWED_MENUS:
- self._menu_layout_modify.append(MenuLayout.Menu(key, remove=True))
+ self._menu_layout_modify.append(
+ MenuLayout.Menu(key, remove=True)
+ )
# Remove 'Viewport 2' entry
if key == "Window":
- for menu_item_1 in menu_instance._menu_defs[key]:
+ for menu_item_1 in menu_defs[key]:
for menu_item_2 in menu_item_1:
if menu_item_2.name == "Viewport":
- menu_item_2.sub_menu = [mi for mi in menu_item_2.sub_menu if mi.name != "Viewport 2"]
+ menu_item_2.sub_menu = [mi for mi in
+ menu_item_2.sub_menu if
+ mi.name != "Viewport 2"]
if self._changing_layout_task is None or self._changing_layout_task.done():
- self._changing_layout_task = asyncio.ensure_future(self._delayed_change_layout())
+ self._changing_layout_task = \
+ asyncio.ensure_future(self._delayed_change_layout())
- def _on_application_mode_changed(self, *args) -> None:
+ def _on_application_mode_changed(self, *args):
+ """Callback for when the application mode changes."""
if self._changing_layout_task is None or self._changing_layout_task.done():
- self._changing_layout_task = asyncio.ensure_future(self._delayed_change_layout())
+ self._changing_layout_task = \
+ asyncio.ensure_future(self._delayed_change_layout())
async def _delayed_change_layout(self):
+ """Delay the layout change to avoid omni.ui error."""
mode = self._settings.get_as_string(SETTINGS_APPLICATION_MODE_PATH)
if mode in ["present", "review"]:
pending_layout = self._menu_layout_empty
else:
pending_layout = self._menu_layout_modify
- # Don't change layout inside of menu callback _on_application_mode_changed
- # omni.ui throws error
+ # Don't change layout inside of menu callback
+ # _on_application_mode_changed omni.ui throws error
if self._current_layout:
- # Here only check number of layout menu items and name of every of layout menu item
+ # Here only check number of layout menu items and name of every of
+ # layout menu item
same_layout = len(self._current_layout) == len(pending_layout)
if same_layout:
for index, item in enumerate(self._current_layout):
diff --git a/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/menubar_helper.py b/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/menubar_helper.py
index 67cbd08..83bb91d 100644
--- a/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/menubar_helper.py
+++ b/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/menubar_helper.py
@@ -1,4 +1,5 @@
-# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES.
+# All rights reserved.
# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
#
# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
@@ -8,79 +9,108 @@
# without an express license agreement from NVIDIA CORPORATION or
# its affiliates is strictly prohibited.
-from pathlib import Path
-
import carb
import carb.settings
import carb.tokens
import omni.ui as ui
+
+from omni.kit.viewport.menubar.core import (
+ DEFAULT_MENUBAR_NAME, SettingModel, SliderMenuDelegate,
+)
+from omni.kit.viewport.menubar.core import get_instance as get_menubar_instance
from omni.ui import color as cl
ICON_PATH = carb.tokens.get_tokens_interface().resolve("${% raw %}{{% endraw %}{{ extension_name }}{% raw %}}{% endraw %}/data/icons")
VIEW_MENUBAR_STYLE = {
"MenuBar.Window": {"background_color": 0xA0000000},
- "MenuBar.Item.Background": { "background_color": 0, },
- "Menu.Item.Background": { "background_color": 0, }
+ "MenuBar.Item.Background": {"background_color": 0},
+ "Menu.Item.Background": {"background_color": 0}
}
VIEWPORT_CAMERA_STYLE = {
- "Menu.Item.Icon::Expand": {"image_url": f"{ICON_PATH}/caret_s2_right_dark.svg", "color": cl.viewport_menubar_light},
- "Menu.Item.Icon::Expand:checked": {"image_url": f"{ICON_PATH}/caret_s2_left_dark.svg"},
+ "Menu.Item.Icon::Expand": {
+ "image_url": f"{ICON_PATH}/caret_s2_right_dark.svg",
+ "color": cl.viewport_menubar_light
+ },
+ "Menu.Item.Icon::Expand:checked": {
+ "image_url":
+ f"{ICON_PATH}/caret_s2_left_dark.svg"
+ },
}
+
class MenubarHelper:
- def __init__(self) -> None:
+ """Helper class to manage menubar settings and style."""
+ def __init__(self):
self._settings = carb.settings.get_settings()
# Set menubar background and style
try:
- from omni.kit.viewport.menubar.core import DEFAULT_MENUBAR_NAME
- from omni.kit.viewport.menubar.core import get_instance as get_menubar_instance
instance = get_menubar_instance()
- if not instance: # pragma: no cover
+ if not instance: # pragma: no cover
return
default_menubar = instance.get_menubar(DEFAULT_MENUBAR_NAME)
default_menubar.background_visible = True
default_menubar.style.update(VIEW_MENUBAR_STYLE)
default_menubar.show_separator = True
- except ImportError: # pragma: no cover
+ except ImportError: # pragma: no cover
carb.log_warn("Viewport menubar not found!")
try:
- import omni.kit.viewport.menubar.camera
- self._camera_menubar_instance = omni.kit.viewport.menubar.camera.get_instance()
- if not self._camera_menubar_instance: # pragma: no cover
+ import omni.kit.viewport.menubar.camera # type: ignore
+ self._camera_menubar_instance = \
+ omni.kit.viewport.menubar.camera.get_instance()
+ if not self._camera_menubar_instance: # pragma: no cover
return
# Change expand button icon
- self._camera_menubar_instance._camera_menu._style.update(VIEWPORT_CAMERA_STYLE)
+ self._camera_menubar_instance._camera_menu._style.update(
+ VIEWPORT_CAMERA_STYLE
+ )
# New menu item for camera speed
- self._camera_menubar_instance.register_menu_item(self._create_camera_speed, order=100)
- self._camera_menubar_instance.deregister_menu_item(self._camera_menubar_instance._camera_menu._build_create_camera)
+ self._camera_menubar_instance.register_menu_item(
+ self._create_camera_speed, order=100
+ )
+ self._camera_menubar_instance.deregister_menu_item(
+ self._camera_menubar_instance._camera_menu._build_create_camera
+ )
except ImportError:
carb.log_warn("Viewport menubar not found!")
self._camera_menubar_instance = None
- except AttributeError: # pragma: no cover
+ except AttributeError: # pragma: no cover
self._camera_menubar_instance = None
# Hide default render and settings menubar
- self._settings.set("/persistent/exts/omni.kit.viewport.menubar.render/visible", False)
- self._settings.set("/persistent/exts/omni.kit.viewport.menubar.settings/visible", False)
+ self._settings.set(
+ "/persistent/exts/omni.kit.viewport.menubar.render/visible", False
+ )
+ self._settings.set(
+ "/persistent/exts/omni.kit.viewport.menubar.settings/visible", False
+ )
- def destroy(self) -> None:
+ def destroy(self):
+ """Remove the camera speed menu item."""
if self._camera_menubar_instance:
- self._camera_menubar_instance.deregister_menu_item(self._create_camera_speed)
+ self._camera_menubar_instance.deregister_menu_item(
+ self._create_camera_speed
+ )
- def _create_camera_speed(self, _vc, _r: ui.Menu) -> None:
- from omni.kit.viewport.menubar.core import SettingModel, SliderMenuDelegate
+ def _create_camera_speed(self, _vc, _r: ui.Menu):
+ """Create a menu item for camera speed."""
ui.MenuItem(
"Speed",
hide_on_click=False,
delegate=SliderMenuDelegate(
- model=SettingModel("/persistent/app/viewport/camMoveVelocity", draggable=True),
- min=self._settings.get_as_float("/persistent/app/viewport/camVelocityMin") or 0.01,
- max=self._settings.get_as_float("/persistent/app/viewport/camVelocityMax"),
+ model=SettingModel(
+ "/persistent/app/viewport/camMoveVelocity", draggable=True
+ ),
+ min=self._settings.get_as_float(
+ "/persistent/app/viewport/camVelocityMin"
+ ) or 0.01,
+ max=self._settings.get_as_float(
+ "/persistent/app/viewport/camVelocityMax"
+ ),
tooltip="Set the Fly Mode navigation speed",
width=0,
reserve_status=True,
diff --git a/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/navigation.py b/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/navigation.py
index 7f1b8c5..695fc4e 100644
--- a/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/navigation.py
+++ b/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/navigation.py
@@ -11,13 +11,13 @@
import asyncio
import carb
+import carb.dictionary
import carb.settings
import carb.tokens
-import carb.dictionary
-import omni.kit.app
import omni.ext
-import omni.ui as ui
import omni.kit.actions.core
+import omni.kit.app
+import omni.ui as ui
from omni.kit.viewport.navigation.core import (
NAVIGATION_TOOL_OPERATION_ACTIVE,
ViewportNavigationTooltip,
@@ -29,26 +29,54 @@
CURRENT_TOOL_PATH = "/app/viewport/currentTool"
SETTING_NAVIGATION_ROOT = "/exts/omni.kit.tool.navigation/"
-NAVIGATION_BAR_VISIBLE_PATH = "/exts/omni.kit.viewport.navigation.core/isVisible"
+NAVIGATION_BAR_VISIBLE_PATH = \
+ "/exts/omni.kit.viewport.navigation.core/isVisible"
APPLICATION_MODE_PATH = "/app/application_mode"
-WALK_VISIBLE_PATH = "/persistent/exts/omni.kit.viewport.navigation.walk/visible"
-CAPTURE_VISIBLE_PATH = "/persistent/exts/omni.kit.viewport.navigation.capture/visible"
-MARKUP_VISIBLE_PATH = "/persistent/exts/omni.kit.viewport.navigation.markup/visible"
-MEASURE_VISIBLE_PATH = "/persistent/exts/omni.kit.viewport.navigation.measure/visible"
-SECTION_VISIBLE_PATH = "/persistent/exts/omni.kit.viewport.navigation.section/visible"
-TELEPORT_SEPARATOR_VISIBLE_PATH = "/persistent/exts/omni.kit.viewport.navigation.teleport/spvisible"
-WAYPOINT_VISIBLE_PATH = "/persistent/exts/omni.kit.viewport.navigation.waypoint/visible"
-VIEWPORT_CONTEXT_MENU_PATH = "/exts/omni.kit.window.viewport/showContextMenu"
-MENUBAR_APP_MODES_PATH = "/exts/omni.kit.usd_explorer.main.menubar/include_modify_mode"
-WELCOME_WINDOW_VISIBLE_PATH = "/exts/omni.kit.usd_explorer.window.welcome/visible"
-ACTIVE_OPERATION_PATH = "/exts/omni.kit.viewport.navigation.core/activeOperation"
+WALK_VISIBLE_PATH = \
+ "/persistent/exts/omni.kit.viewport.navigation.walk/visible"
+CAPTURE_VISIBLE_PATH = \
+ "/persistent/exts/omni.kit.viewport.navigation.capture/visible"
+MARKUP_VISIBLE_PATH = \
+ "/persistent/exts/omni.kit.viewport.navigation.markup/visible"
+MEASURE_VISIBLE_PATH = \
+ "/persistent/exts/omni.kit.viewport.navigation.measure/visible"
+SECTION_VISIBLE_PATH = \
+"/persistent/exts/omni.kit.viewport.navigation.section/visible"
+TELEPORT_SEPARATOR_VISIBLE_PATH = \
+ "/persistent/exts/omni.kit.viewport.navigation.teleport/spvisible"
+WAYPOINT_VISIBLE_PATH = \
+ "/persistent/exts/omni.kit.viewport.navigation.waypoint/visible"
+VIEWPORT_CONTEXT_MENU_PATH = \
+ "/exts/omni.kit.window.viewport/showContextMenu"
+MENUBAR_APP_MODES_PATH = \
+ "/exts/omni.kit.usd_explorer.main.menubar/include_modify_mode"
+WELCOME_WINDOW_VISIBLE_PATH = \
+ "/exts/omni.kit.usd_explorer.window.welcome/visible"
+ACTIVE_OPERATION_PATH = \
+ "/exts/omni.kit.viewport.navigation.core/activeOperation"
+
class Navigation:
+ """Manages the navigation bar and its visibility."""
NAVIGATION_BAR_NAME = None
- # ext_id is current extension id. It can be used with extension manager to query additional information, like where
- # this extension is located on filesystem.
- def on_startup(self, ext_id: str) -> None:
+ def __init__(self):
+ self._ext_name = None
+ self._settings = None
+ self._navigation_bar = None
+ self._tool_bar_button = None
+ self._dict = None
+ self._panel_visible = None
+ self._viewport_welcome_window_visibility_changed_sub = None
+ self._application_mode_changed_sub = None
+ self._show_tooltips = None
+ self._nav_bar_visibility_sub = None
+
+ # ext_id is current extension id. It can be used with extension manager
+ # to query additional information, like where this extension is located
+ # on filesystem.
+ def on_startup(self, ext_id: str):
+ """Initialize the navigation bar and set up event subscriptions."""
sections = ext_id.split("-")
self._ext_name = sections[0]
@@ -63,9 +91,11 @@ def on_startup(self, ext_id: str) -> None:
self._settings.set(CURRENT_TOOL_PATH, "navigation")
self._settings.set(NAVIGATION_TOOL_OPERATION_ACTIVE, "teleport")
- self._viewport_welcome_window_visibility_changed_sub = self._settings.subscribe_to_node_change_events(
- WELCOME_WINDOW_VISIBLE_PATH, self._on_welcome_window_visibility_change
- )
+ self._viewport_welcome_window_visibility_changed_sub = \
+ self._settings.subscribe_to_node_change_events(
+ WELCOME_WINDOW_VISIBLE_PATH,
+ self._on_welcome_window_visibility_change
+ )
self._settings.set(WALK_VISIBLE_PATH, False)
self._settings.set(MARKUP_VISIBLE_PATH, True)
@@ -75,76 +105,103 @@ def on_startup(self, ext_id: str) -> None:
self._settings.set(MEASURE_VISIBLE_PATH, True)
self._settings.set(SECTION_VISIBLE_PATH, True)
- self._application_mode_changed_sub = self._settings.subscribe_to_node_change_events(
- APPLICATION_MODE_PATH, self._on_application_mode_changed
- )
+ self._application_mode_changed_sub = \
+ self._settings.subscribe_to_node_change_events(
+ APPLICATION_MODE_PATH, self._on_application_mode_changed
+ )
self._show_tooltips = False
- self._nav_bar_visibility_sub = self._settings.subscribe_to_node_change_events(
- NAVIGATION_BAR_VISIBLE_PATH, self._delay_reset_tooltip)
+ self._nav_bar_visibility_sub = \
+ self._settings.subscribe_to_node_change_events(
+ NAVIGATION_BAR_VISIBLE_PATH, self._delay_reset_tooltip
+ )
_prev_navbar_vis = None
_prev_tool = None
_prev_operation = None
- def _on_welcome_window_visibility_change(self, item: carb.dictionary.Item, *_) -> None:
+
+ def _on_welcome_window_visibility_change(
+ self, item: carb.dictionary.Item, *_
+ ):
+ """Callback for when the welcome window visibility changes."""
if not isinstance(self._dict, (carb.dictionary.IDictionary, dict)):
return
welcome_window_vis = self._dict.get(item)
- # preserve the state of the navbar upon closing the Welcome window if the app is in Layout mode
+ # preserve the state of the navbar upon closing the Welcome window if
+ # the app is in Layout mode
if self._settings.get_as_string(APPLICATION_MODE_PATH).lower() == "layout":
# preserve the state of the navbar visibility
if welcome_window_vis:
- self._prev_navbar_vis = self._settings.get_as_bool(NAVIGATION_BAR_VISIBLE_PATH)
- self._settings.set(NAVIGATION_BAR_VISIBLE_PATH, not(welcome_window_vis))
+ self._prev_navbar_vis = \
+ self._settings.get_as_bool(NAVIGATION_BAR_VISIBLE_PATH)
+ self._settings.set(
+ NAVIGATION_BAR_VISIBLE_PATH, not (welcome_window_vis)
+ )
self._prev_tool = self._settings.get(CURRENT_TOOL_PATH)
- self._prev_operation = self._settings.get(ACTIVE_OPERATION_PATH)
- else: # restore the state of the navbar visibility
+ self._prev_operation = \
+ self._settings.get(ACTIVE_OPERATION_PATH)
+ else: # restore the state of the navbar visibility
if self._prev_navbar_vis is not None:
- self._settings.set(NAVIGATION_BAR_VISIBLE_PATH, self._prev_navbar_vis)
+ self._settings.set(
+ NAVIGATION_BAR_VISIBLE_PATH, self._prev_navbar_vis
+ )
self._prev_navbar_vis = None
if self._prev_tool is not None:
self._settings.set(CURRENT_TOOL_PATH, self._prev_tool)
if self._prev_operation is not None:
- self._settings.set(ACTIVE_OPERATION_PATH, self._prev_operation)
- return
+ self._settings.set(
+ ACTIVE_OPERATION_PATH, self._prev_operation
+ )
else:
if welcome_window_vis:
self._settings.set(NAVIGATION_TOOL_OPERATION_ACTIVE, "none")
else:
- self._settings.set(NAVIGATION_TOOL_OPERATION_ACTIVE, "teleport")
+ self._settings.set(
+ NAVIGATION_TOOL_OPERATION_ACTIVE, "teleport"
+ )
- self._settings.set(NAVIGATION_BAR_VISIBLE_PATH, not(welcome_window_vis))
+ self._settings.set(
+ NAVIGATION_BAR_VISIBLE_PATH, not (welcome_window_vis)
+ )
- def _on_application_mode_changed(self, item: carb.dictionary.Item, *_) -> None:
+ def _on_application_mode_changed(self, item: carb.dictionary.Item, *_):
+ """Callback for when the application mode changes."""
if not isinstance(self._dict, (carb.dictionary.IDictionary, dict)):
return
current_mode = self._dict.get(item)
self._test = asyncio.ensure_future(self._switch_by_mode(current_mode))
- async def _switch_by_mode(self, current_mode: str) -> None:
+ async def _switch_by_mode(self, current_mode: str):
+ """Switch application mode based on provided mode."""
await omni.kit.app.get_app().next_update_async()
state = True if current_mode == "review" else False
self._settings.set(NAVIGATION_BAR_VISIBLE_PATH, state)
- self._settings.set(VIEWPORT_CONTEXT_MENU_PATH, not(state)) # toggle RMB viewport context menu
+ # toggle RMB viewport context menu
+ self._settings.set(VIEWPORT_CONTEXT_MENU_PATH, not (state))
self._delay_reset_tooltip(None)
- def _delay_reset_tooltip(self, *_) -> None:
- async def delay_set_tooltip() -> None:
+ def _delay_reset_tooltip(self, *_):
+ """Delay setting the tooltip visibility."""
+ async def delay_set_tooltip():
for _i in range(4):
- await omni.kit.app.get_app().next_update_async() # type: ignore
+ await omni.kit.app.get_app().next_update_async()
ViewportNavigationTooltip.set_visible(self._show_tooltips)
asyncio.ensure_future(delay_set_tooltip())
- def _on_showtips_click(self, *_) -> None:
+ def _on_showtips_click(self, *_):
+ """Toggle the visibility of the tooltips."""
self._show_tooltips = not self._show_tooltips
ViewportNavigationTooltip.set_visible(self._show_tooltips)
- def on_shutdown(self) -> None:
+ def on_shutdown(self):
+ """Clean up event subscriptions."""
self._navigation_bar = None
self._viewport_welcome_window_visibility_changed_sub = None
- self._settings.unsubscribe_to_change_events(self._application_mode_changed_sub) # type:ignore
+ self._settings.unsubscribe_to_change_events(
+ self._application_mode_changed_sub
+ )
self._application_mode_changed_sub = None
self._dict = None
diff --git a/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/setup.py b/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/setup.py
index 71b8ad6..d736121 100644
--- a/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/setup.py
+++ b/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/setup.py
@@ -1,4 +1,5 @@
-# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES.
+# All rights reserved.
# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
#
# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
@@ -9,9 +10,11 @@
# its affiliates is strictly prohibited.
import asyncio
-import weakref
-from functools import partial
+import inspect
import os
+import weakref
+import webbrowser
+from contextlib import suppress
from pathlib import Path
from typing import cast, Optional
@@ -26,8 +29,6 @@
from omni.kit.quicklayout import QuickLayout
from omni.kit.menu.utils import MenuLayout
from omni.kit.window.title import get_main_window_title
-from omni.kit.usd.layers import LayerUtils
-
from omni.kit.viewport.menubar.core import get_instance as get_mb_inst, DEFAULT_MENUBAR_NAME
from omni.kit.viewport.menubar.core.viewport_menu_model import ViewportMenuModel
from omni.kit.viewport.utility import get_active_viewport, get_active_viewport_window, disable_selection
@@ -40,7 +41,6 @@
import carb.input
import omni.kit.imgui as _imgui
-from pxr import Sdf, Usd
from .navigation import Navigation
from .menu_helper import MenuHelper
@@ -59,53 +59,67 @@
TELEPORT_VISIBLE_PATH = "/persistent/exts/omni.kit.viewport.navigation.teleport/visible"
-async def _load_layout_startup(layout_file: str, keep_windows_open: bool=False) -> None:
+async def _load_layout_startup(
+ layout_file: str, keep_windows_open: bool = False
+ ):
+ """Loads the startup layout file."""
try:
- # few frames delay to avoid the conflict with the layout of omni.kit.mainwindow
- for i in range(3):
- await omni.kit.app.get_app().next_update_async() # type: ignore
+ # few frames delay to avoid the conflict with the layout of
+ # omni.kit.mainwindow
+ for _ in range(3):
+ await omni.kit.app.get_app().next_update_async()
QuickLayout.load_file(layout_file, keep_windows_open)
# WOR: some layout don't happy collectly the first time
- await omni.kit.app.get_app().next_update_async() # type: ignore
+ await omni.kit.app.get_app().next_update_async()
QuickLayout.load_file(layout_file, keep_windows_open)
- except Exception as exc: # pragma: no cover (Can't be tested because a non-existing layout file prints an log_error in QuickLayout and does not throw an exception)
+ except Exception as exc: # pragma: no coverthrow an exception)
carb.log_warn(f"Failed to load layout {layout_file}: {exc}")
-async def _load_layout(layout_file: str, keep_windows_open:bool=False) -> None:
+async def _load_layout(layout_file: str, keep_windows_open: bool = False):
+ """Load a layout file and catch any exceptions that occur."""
try:
- # few frames delay to avoid the conflict with the layout of omni.kit.mainwindow
+ # few frames delay to avoid the conflict with the layout of
+ # omni.kit.mainwindow
for i in range(3):
- await omni.kit.app.get_app().next_update_async() # type: ignore
+ await omni.kit.app.get_app().next_update_async()
QuickLayout.load_file(layout_file, keep_windows_open)
- except Exception as exc: # pragma: no cover (Can't be tested because a non-existing layout file prints an log_error in QuickLayout and does not throw an exception)
+ except Exception as exc: # pragma: no coverthrow an exception)
carb.log_warn(f"Failed to load layout {layout_file}: {exc}")
-async def _clear_startup_scene_edits() -> None:
+
+async def _clear_startup_scene_edits():
try:
- for i in range(50): # This could possibly be a smaller value. I want to ensure this happens after RTX startup
- await omni.kit.app.get_app().next_update_async() # type: ignore
+ # This could possibly be a smaller value.
+ # I want to ensure this happens after RTX startup
+ for _ in range(50):
+ await omni.kit.app.get_app().next_update_async()
omni.usd.get_context().set_pending_edit(False)
- except Exception as exc: # pragma: no cover
+ except Exception as exc: # pragma: no cover
carb.log_warn(f"Failed to clear stage edits on startup: {exc}")
# This extension is mostly loading the Layout updating menu
class SetupExtension(omni.ext.IExt):
- # ext_id is current extension id. It can be used with extension manager to query additional information, like where
- # this extension is located on filesystem.
+ """Setup extension for USD Explorer."""
+ # ext_id is current extension id. It can be used with extension manager to
+ # query additional information, like where this extension is located
+ # on filesystem.
@property
def _app(self):
+ """Return the app instance."""
return omni.kit.app.get_app()
@property
def _settings(self):
+ """Return the settings instance."""
return carb.settings.get_settings()
- def on_startup(self, ext_id: str) -> None:
+ def on_startup(self, ext_id: str):
+ """Setup the extension on startup."""
self._ext_id = ext_id
self._menubar_helper = MenubarHelper()
self._menu_helper = MenuHelper()
@@ -114,24 +128,41 @@ def on_startup(self, ext_id: str) -> None:
imgui = _imgui.acquire_imgui()
# match Create overides
- imgui.push_style_color(_imgui.StyleColor.ScrollbarGrab, carb.Float4(0.4, 0.4, 0.4, 1))
- imgui.push_style_color(_imgui.StyleColor.ScrollbarGrabHovered, carb.Float4(0.6, 0.6, 0.6, 1))
- imgui.push_style_color(_imgui.StyleColor.ScrollbarGrabActive, carb.Float4(0.8, 0.8, 0.8, 1))
+ imgui.push_style_color(
+ _imgui.StyleColor.ScrollbarGrab,
+ carb.Float4(0.4, 0.4, 0.4, 1)
+ )
+ imgui.push_style_color(
+ _imgui.StyleColor.ScrollbarGrabHovered,
+ carb.Float4(0.6, 0.6, 0.6, 1)
+ )
+ imgui.push_style_color(
+ _imgui.StyleColor.ScrollbarGrabActive,
+ carb.Float4(0.8, 0.8, 0.8, 1)
+ )
- # DockSplitterSize is the variable that drive the size of the Dock Split connection
+ # DockSplitterSize is the variable that drive the size of the Dock
+ # Split connection
imgui.push_style_var_float(_imgui.StyleVar.DockSplitterSize, 2)
# setup the Layout for your app
self._layouts_path = carb.tokens.get_tokens_interface().resolve("${% raw %}{{% endraw %}{{ extension_name }}{% raw %}}{% endraw %}/layouts")
layout_file = Path(self._layouts_path).joinpath(f"{self._settings.get('/app/layout/name')}.json")
- self.__setup_window_task = asyncio.ensure_future(_load_layout_startup(f"{layout_file}", True))
+ asyncio.ensure_future(_load_layout_startup(f"{layout_file}", True))
- self.review_layout_path = str(Path(self._layouts_path) / "comment_layout.json")
- self.default_layout_path = str(Path(self._layouts_path) / "default.json")
- self.layout_user_path = str(Path(self._layouts_path) / "layout_user.json")
+ self.review_layout_path = str(
+ Path(self._layouts_path) / "comment_layout.json"
+ )
+ self.default_layout_path = str(
+ Path(self._layouts_path) / "default.json"
+ )
+ self.layout_user_path = str(
+ Path(self._layouts_path) / "layout_user.json"
+ )
- # remove the user defined layout so that we always load the default layout when startup
- if os.path.exists(self.layout_user_path):
+ # remove the user defined layout so that we always load the default
+ # layout when startup
+ with suppress(FileNotFoundError):
os.remove(self.layout_user_path)
# setup the menu and their layout
@@ -142,7 +173,6 @@ def on_startup(self, ext_id: str) -> None:
if self._settings.get_as_bool('/app/view/debug/menus'):
self._layout_menu()
-
# setup the Application Title
window_title = get_main_window_title()
if window_title:
@@ -153,47 +183,60 @@ def on_startup(self, ext_id: str) -> None:
self._navigation = Navigation()
self._navigation.on_startup(ext_id)
- self._application_mode_changed_sub = self._settings.subscribe_to_node_change_events(
- APPLICATION_MODE_PATH, weakref.proxy(self)._on_application_mode_changed
- )
+ self._application_mode_changed_sub = \
+ self._settings.subscribe_to_node_change_events(
+ APPLICATION_MODE_PATH,
+ weakref.proxy(self)._on_application_mode_changed
+ )
self._set_viewport_menubar_visibility(False)
- self._test = asyncio.ensure_future(_clear_startup_scene_edits())
+ asyncio.ensure_future(_clear_startup_scene_edits())
self._usd_context = omni.usd.get_context()
- self._stage_event_sub = self._usd_context.get_stage_event_stream().create_subscription_to_pop(
- self._on_stage_open_event, name="TeleportDefaultOn"
- )
+ self._stage_event_sub = \
+ self._usd_context.get_stage_event_stream().create_subscription_to_pop(
+ self._on_stage_open_event, name="TeleportDefaultOn"
+ )
if self._settings.get_as_bool(SETTINGS_STARTUP_EXPAND_VIEWPORT):
self._set_viewport_fill_on()
self._stage_templates = [SunnySkyStage()]
-
disable_selection(get_active_viewport())
self._ui_state_manager = UIStateManager()
self._setup_ui_state_changes()
omni.kit.menu.utils.add_layout([
MenuLayout.Menu("Window", [
- MenuLayout.Item("Viewport", source="Window/Viewport/Viewport 1"),
+ MenuLayout.Item(
+ "Viewport", source="Window/Viewport/Viewport 1"
+ ),
MenuLayout.Item("Playlist", remove=True),
MenuLayout.Item("Layout", remove=True),
- MenuLayout.Sort(exclude_items=["Extensions"], sort_submenus=True),
+ MenuLayout.Sort(
+ exclude_items=["Extensions"], sort_submenus=True
+ ),
])
])
- def show_documentation(*x):
- import webbrowser
- webbrowser.open("http://docs.omniverse.nvidia.com/explorer")
+
+ def show_documentation(*_args):
+ """Open the documentation in a web browser."""
+ webbrowser.open("https://docs.omniverse.nvidia.com/explorer")
self._help_menu_items = [
- omni.kit.menu.utils.MenuItemDescription(name="Documentation",
- onclick_fn=show_documentation,
- appear_after=[omni.kit.menu.utils.MenuItemOrder.FIRST])
+ omni.kit.menu.utils.MenuItemDescription(
+ name="Documentation",
+ onclick_fn=show_documentation,
+ appear_after=[omni.kit.menu.utils.MenuItemOrder.FIRST]
+ )
]
omni.kit.menu.utils.add_menu_items(self._help_menu_items, name="Help")
- def _on_stage_open_event(self, event: carb.events.IEvent) -> None:
+ def _on_stage_open_event(self, event: carb.events.IEvent):
+ """Callback to clear tools and switch the app mode after a new stage
+ is opened."""
if event.type == int(omni.usd.StageEventType.OPENED):
- app_mode = self._settings.get_as_string(APPLICATION_MODE_PATH).lower()
+ app_mode = self._settings.get_as_string(
+ APPLICATION_MODE_PATH
+ ).lower()
# exit all tools
self._settings.set(CURRENT_TOOL_PATH, "none")
@@ -206,26 +249,37 @@ def _on_stage_open_event(self, event: carb.events.IEvent) -> None:
self._settings.set(VIEWPORT_CONTEXT_MENU_PATH, value)
# teleport is activated after loading a stage and app is in Review mode
- async def _stage_post_open_teleport_toggle(self) -> None:
+ async def _stage_post_open_teleport_toggle(self):
+ """Toggle the teleport tool after a stage is opened."""
await self._app.next_update_async()
if hasattr(self, "_usd_context") and self._usd_context is not None and not self._usd_context.is_new_stage():
- self._settings.set("/exts/omni.kit.viewport.navigation.core/activeOperation", "teleport")
+ self._settings.set(
+ "/exts/omni.kit.viewport.navigation.core/activeOperation",
+ "teleport"
+ )
- def _set_viewport_fill_on(self) -> None:
+ def _set_viewport_fill_on(self):
+ """Set the viewport fill on."""
vp_window = get_active_viewport_window()
vp_widget = vp_window.viewport_widget if vp_window else None
if vp_widget:
vp_widget.expand_viewport = True
- def _set_viewport_menubar_visibility(self, show: bool) -> None:
+ def _set_viewport_menubar_visibility(self, show: bool):
+ """Set the viewport menubar visibility."""
mb_inst = get_mb_inst()
if mb_inst and hasattr(mb_inst, "get_menubar"):
main_menubar = mb_inst.get_menubar(DEFAULT_MENUBAR_NAME)
if main_menubar.visible_model.as_bool != show:
main_menubar.visible_model.set_value(show)
- ViewportMenuModel()._item_changed(None) # type: ignore
-
- def _on_application_mode_changed(self, item: carb.dictionary.Item, _typ: carb.settings.ChangeEventType) -> None:
+ ViewportMenuModel()._item_changed(None)
+
+ def _on_application_mode_changed(
+ self,
+ item: carb.dictionary.Item,
+ _typ: carb.settings.ChangeEventType
+ ):
+ """Callback for when the application mode changes."""
if self._settings.get_as_string(APPLICATION_MODE_PATH).lower() == "review":
omni.usd.get_context().get_selection().clear_selected_prim_paths()
disable_selection(get_active_viewport())
@@ -233,10 +287,12 @@ def _on_application_mode_changed(self, item: carb.dictionary.Item, _typ: carb.se
current_mode: str = cast(str, item.get_dict())
asyncio.ensure_future(self.defer_load_layout(current_mode))
- async def defer_load_layout(self, current_mode: str) -> None:
+ async def defer_load_layout(self, current_mode: str):
+ """Defer loading the layout based on the current mode."""
keep_windows = True
# Focus Mode Toolbar
- self._settings.set_bool(SETTINGS_PATH_FOCUSED, True) # current_mode not in ("review", "layout"))
+ # current_mode not in ("review", "layout"))
+ self._settings.set_bool(SETTINGS_PATH_FOCUSED, True)
# Turn off all tools and modal
self._settings.set_string(CURRENT_TOOL_PATH, "none")
@@ -245,7 +301,8 @@ async def defer_load_layout(self, current_mode: str) -> None:
if current_mode == "review":
# save the current layout for restoring later if switch back
QuickLayout.save_file(self.layout_user_path)
- # we don't want to keep any windows except the ones which are visible in self.review_layout_path
+ # we don't want to keep any windows except the ones which are
+ # visible in self.review_layout_path
await _load_layout(self.review_layout_path, False)
else: # current_mode == "layout":
# check if there is any user modified layout, if yes use that one
@@ -254,17 +311,21 @@ async def defer_load_layout(self, current_mode: str) -> None:
self._set_viewport_menubar_visibility(current_mode == "layout")
- def _setup_ui_state_changes(self) -> None:
+ def _setup_ui_state_changes(self):
+ """Setup the UI state changes."""
windows_to_hide_on_modal = ["Measure", "Section", "Waypoints"]
- self._ui_state_manager.add_hide_on_modal(window_names=windows_to_hide_on_modal, restore=True)
+ self._ui_state_manager.add_hide_on_modal(
+ window_names=windows_to_hide_on_modal, restore=True
+ )
window_titles = ["Markups", "Waypoints"]
for window in window_titles:
setting_name = f'/exts/omni.usd_explorer.setup/{window}/visible'
- self._ui_state_manager.add_window_visibility_setting(window, setting_name)
+ self._ui_state_manager.add_window_visibility_setting(
+ window, setting_name
+ )
# toggle icon visibilites based on window visibility
-
self._ui_state_manager.add_settings_copy_dependency(
source_path="/exts/omni.usd_explorer.setup/Markups/visible",
target_path="/exts/omni.kit.markup.core/show_icons",
@@ -275,20 +336,21 @@ def _setup_ui_state_changes(self) -> None:
target_path="/exts/omni.kit.waypoint.core/show_icons",
)
- def _custom_quicklayout_menu(self) -> None:
+ def _custom_quicklayout_menu(self):
# we setup a simple ways to Load custom layout from the exts
def add_layout_menu_entry(name, parameter, key):
- import inspect
-
+ """Add a layout menu entry."""
editor_menu = omni.kit.ui.get_editor_menu()
layouts_path = carb.tokens.get_tokens_interface().resolve("${% raw %}{{% endraw %}{{ extension_name }}{% raw %}}{% endraw %}/layouts")
menu_path = f"Layout/{name}"
- menu = editor_menu.add_item(menu_path, None, False, self._current_layout_priority) # type: ignore
+ menu = editor_menu.add_item(
+ menu_path, None, False, self._current_layout_priority
+ )
self._current_layout_priority = self._current_layout_priority + 1
- if inspect.isfunction(parameter): # pragma: no cover (Never used, see commented out section below regarding quick save/load)
+ if inspect.isfunction(parameter): # pragma: no cover
menu_action = omni.kit.menu.utils.add_action_to_menu(
menu_path,
lambda *_: asyncio.ensure_future(parameter()),
@@ -298,38 +360,34 @@ def add_layout_menu_entry(name, parameter, key):
else:
menu_action = omni.kit.menu.utils.add_action_to_menu(
menu_path,
- lambda *_: asyncio.ensure_future(_load_layout(f"{layouts_path}/{parameter}.json")),
+ lambda *_: asyncio.ensure_future(
+ _load_layout(f"{layouts_path}/{parameter}.json")
+ ),
name,
(carb.input.KEYBOARD_MODIFIER_FLAG_CONTROL, key),
)
self._layout_menu_items.append((menu, menu_action))
- add_layout_menu_entry("Reset Layout", "default", carb.input.KeyboardInput.KEY_1)
- add_layout_menu_entry("Viewport Only", "viewport_only", carb.input.KeyboardInput.KEY_2)
- add_layout_menu_entry("Markup Editor", "markup_editor", carb.input.KeyboardInput.KEY_3)
- # add_layout_menu_entry("Waypoint Viewer", "waypoint_viewer", carb.input.KeyboardInput.KEY_4)
-
- # # you can enable Quick Save and Quick Load here
- # if False:
- # # create Quick Load & Quick Save
- # from omni.kit.quicklayout import QuickLayout
-
- # async def quick_save():
- # QuickLayout.quick_save(None, None)
-
- # async def quick_load():
- # QuickLayout.quick_load(None, None)
-
- # add_layout_menu_entry("Quick Save", quick_save, carb.input.KeyboardInput.KEY_7)
- # add_layout_menu_entry("Quick Load", quick_load, carb.input.KeyboardInput.KEY_8)
+ add_layout_menu_entry(
+ "Reset Layout", "default", carb.input.KeyboardInput.KEY_1
+ )
+ add_layout_menu_entry(
+ "Viewport Only", "viewport_only", carb.input.KeyboardInput.KEY_2
+ )
+ add_layout_menu_entry(
+ "Markup Editor", "markup_editor", carb.input.KeyboardInput.KEY_3
+ )
- def _register_my_menu(self) -> None:
- context_menu: Optional[omni.kit.context_menu.ContextMenuExtension] = omni.kit.context_menu.get_instance()
- if not context_menu: # pragma: no cover
+ def _register_my_menu(self):
+ """Gets the context menu and adds the menu items."""
+ context_menu: Optional[omni.kit.context_menu.ContextMenuExtension] = \
+ omni.kit.context_menu.get_instance()
+ if not context_menu: # pragma: no cover
return
- def _layout_file_menu(self) -> None:
+ def _layout_file_menu(self):
+ """Setup the file menu layout."""
self._menu_file_layout = [
MenuLayout.Menu(
"File",
@@ -362,8 +420,8 @@ def _layout_file_menu(self) -> None:
]
omni.kit.menu.utils.add_layout(self._menu_file_layout)
-
- def _layout_menu(self) -> None:
+ def _layout_menu(self):
+ """Layout the Window menu."""
self._menu_layout = [
MenuLayout.Menu(
"Window",
@@ -389,7 +447,9 @@ def _layout_menu(self) -> None:
MenuLayout.SubMenu(
"Browsers",
[
- MenuLayout.Item("Content", source="Window/Content"),
+ MenuLayout.Item(
+ "Content", source="Window/Content"
+ ),
MenuLayout.Item("Materials"),
MenuLayout.Item("Skies"),
],
@@ -409,23 +469,37 @@ def _layout_menu(self) -> None:
MenuLayout.Group(
"Flow",
[
- MenuLayout.Item("Presets", source="Window/Flow/Presets"),
- MenuLayout.Item("Monitor", source="Window/Flow/Monitor"),
+ MenuLayout.Item(
+ "Presets",
+ source="Window/Flow/Presets"
+ ),
+ MenuLayout.Item(
+ "Monitor",
+ source="Window/Flow/Monitor"
+ ),
],
),
MenuLayout.Group(
"Blast",
[
- MenuLayout.Item("Settings", source="Window/Blast/Settings"),
+ MenuLayout.Item(
+ "Settings",
+ source="Window/Blast/Settings"
+ ),
MenuLayout.SubMenu(
"Documentation",
[
- MenuLayout.Item("Kit UI", source="Window/Blast/Documentation/Kit UI"),
MenuLayout.Item(
- "Programming", source="Window/Blast/Documentation/Programming"
+ "Kit UI",
+ source="Window/Blast/Documentation/Kit UI"
+ ),
+ MenuLayout.Item(
+ "Programming",
+ source="Window/Blast/Documentation/Programming"
),
MenuLayout.Item(
- "USD Schemas", source="Window/Blast/Documentation/USD Schemas"
+ "USD Schemas",
+ source="Window/Blast/Documentation/USD Schemas"
),
],
),
@@ -437,7 +511,10 @@ def _layout_menu(self) -> None:
"Physics",
[
MenuLayout.Item("Demo Scenes"),
- MenuLayout.Item("Settings", source="Window/Physics/Settings"),
+ MenuLayout.Item(
+ "Settings",
+ source="Window/Physics/Settings"
+ ),
MenuLayout.Item("Debug"),
MenuLayout.Item("Test Runner"),
MenuLayout.Item("Character Controller"),
@@ -477,31 +554,45 @@ def _layout_menu(self) -> None:
MenuLayout.Item("Markup Editor"),
MenuLayout.Item("Waypoint Viewer"),
MenuLayout.Seperator(),
- MenuLayout.Item("UI Toggle Visibility", source="Window/UI Toggle Visibility"),
- MenuLayout.Item("Fullscreen Mode", source="Window/Fullscreen Mode"),
+ MenuLayout.Item(
+ "UI Toggle Visibility",
+ source="Window/UI Toggle Visibility"
+ ),
+ MenuLayout.Item(
+ "Fullscreen Mode",
+ source="Window/Fullscreen Mode"
+ ),
MenuLayout.Seperator(),
- MenuLayout.Item("Save Layout", source="Window/Layout/Save Layout..."),
- MenuLayout.Item("Load Layout", source="Window/Layout/Load Layout..."),
- # MenuLayout.Seperator(),
- # MenuLayout.Item("Quick Save", source="Window/Layout/Quick Save"),
- # MenuLayout.Item("Quick Load", source="Window/Layout/Quick Load"),
+ MenuLayout.Item(
+ "Save Layout",
+ source="Window/Layout/Save Layout..."
+ ),
+ MenuLayout.Item(
+ "Load Layout",
+ source="Window/Layout/Load Layout..."
+ ),
],
),
- MenuLayout.Menu("Tools", [MenuLayout.SubMenu("Animation", remove=True)]),
+ MenuLayout.Menu(
+ "Tools", [MenuLayout.SubMenu("Animation", remove=True)]
+ ),
]
- omni.kit.menu.utils.add_layout(self._menu_layout) # type: ignore
+ omni.kit.menu.utils.add_layout(self._menu_layout)
# if you want to support the Quick Layout Menu
self._custom_quicklayout_menu()
def on_shutdown(self):
+ """Shutdown the extension."""
if self._menu_layout:
- omni.kit.menu.utils.remove_layout(self._menu_layout) # type: ignore
+ omni.kit.menu.utils.remove_layout(self._menu_layout)
self._menu_layout.clear()
self._layout_menu_items.clear()
self._navigation.on_shutdown()
del self._navigation
- self._settings.unsubscribe_to_change_events(self._application_mode_changed_sub)
+ self._settings.unsubscribe_to_change_events(
+ self._application_mode_changed_sub
+ )
del self._application_mode_changed_sub
self._stage_event_sub = None
diff --git a/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/stage_template.py b/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/stage_template.py
index 17e6cd2..a5b37ea 100644
--- a/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/stage_template.py
+++ b/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/stage_template.py
@@ -16,13 +16,16 @@
class SunnySkyStage:
+ """Stage template for a sunny sky."""
def __init__(self):
register_template("SunnySky", self.new_stage)
def __del__(self):
+ """Unregister the template when the object is deleted."""
unregister_template("SunnySky")
- def new_stage(self, rootname, usd_context_name):
+ def new_stage(self, _rootname, usd_context_name):
+ """Create a new stage with a sunny sky."""
# Create basic DistantLight
usd_context = omni.usd.get_context(usd_context_name)
stage = usd_context.get_stage()
@@ -64,13 +67,23 @@ def new_stage(self, rootname, usd_context_name):
context_name=usd_context_name
)
prim = stage.GetPrimAtPath("/Environment/Sky")
- prim.CreateAttribute("xformOp:scale", Sdf.ValueTypeNames.Double3, False).Set(Gf.Vec3d(1, 1, 1))
- prim.CreateAttribute("xformOp:translate", Sdf.ValueTypeNames.Double3, False).Set(Gf.Vec3d(0, 0, 0))
+ prim.CreateAttribute(
+ "xformOp:scale", Sdf.ValueTypeNames.Double3, False
+ ).Set(Gf.Vec3d(1, 1, 1))
+ prim.CreateAttribute(
+ "xformOp:translate", Sdf.ValueTypeNames.Double3, False
+ ).Set(Gf.Vec3d(0, 0, 0))
if up_axis == "Y":
- prim.CreateAttribute("xformOp:rotateXYZ", Sdf.ValueTypeNames.Double3, False).Set(Gf.Vec3d(270, 0, 0))
+ prim.CreateAttribute(
+ "xformOp:rotateXYZ", Sdf.ValueTypeNames.Double3, False
+ ).Set(Gf.Vec3d(270, 0, 0))
else:
- prim.CreateAttribute("xformOp:rotateXYZ", Sdf.ValueTypeNames.Double3, False).Set(Gf.Vec3d(0, 0, 90))
- prim.CreateAttribute("xformOpOrder", Sdf.ValueTypeNames.String, False).Set(["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"])
+ prim.CreateAttribute(
+ "xformOp:rotateXYZ", Sdf.ValueTypeNames.Double3, False
+ ).Set(Gf.Vec3d(0, 0, 90))
+ prim.CreateAttribute(
+ "xformOpOrder", Sdf.ValueTypeNames.String, False
+ ).Set(["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"])
# create DistantLight
omni.kit.commands.execute(
@@ -82,7 +95,7 @@ def new_stage(self, rootname, usd_context_name):
UsdLux.Tokens.inputsAngle: 4.3,
UsdLux.Tokens.inputsIntensity: 3000,
UsdGeom.Tokens.visibility: "inherited",
- } if hasattr(UsdLux.Tokens, 'inputsIntensity') else \
+ } if hasattr(UsdLux.Tokens, 'inputsIntensity') else
{
UsdLux.Tokens.angle: 4.3,
UsdLux.Tokens.intensity: 3000,
@@ -92,10 +105,33 @@ def new_stage(self, rootname, usd_context_name):
context_name=usd_context_name
)
prim = stage.GetPrimAtPath("/Environment/DistantLight")
- prim.CreateAttribute("xformOp:scale", Sdf.ValueTypeNames.Double3, False).Set(Gf.Vec3d(1, 1, 1))
- prim.CreateAttribute("xformOp:translate", Sdf.ValueTypeNames.Double3, False).Set(Gf.Vec3d(0, 0, 0))
+ prim.CreateAttribute(
+ "xformOp:scale", Sdf.ValueTypeNames.Double3, False
+ ).Set(Gf.Vec3d(1, 1, 1))
+ prim.CreateAttribute(
+ "xformOp:translate", Sdf.ValueTypeNames.Double3, False
+ ).Set(Gf.Vec3d(0, 0, 0))
if up_axis == "Y":
- prim.CreateAttribute("xformOp:rotateXYZ", Sdf.ValueTypeNames.Double3, False).Set(Gf.Vec3d(310.6366313590111, -125.93251524567805, 0.8821359067542289))
+ prim.CreateAttribute(
+ "xformOp:rotateXYZ", Sdf.ValueTypeNames.Double3, False
+ ).Set(
+ Gf.Vec3d(
+ 310.6366313590111,
+ -125.93251524567805,
+ 0.8821359067542289
+ )
+ )
else:
- prim.CreateAttribute("xformOp:rotateXYZ", Sdf.ValueTypeNames.Double3, False).Set(Gf.Vec3d(41.35092544555664, 0.517652153968811, -35.92928695678711))
- prim.CreateAttribute("xformOpOrder", Sdf.ValueTypeNames.String, False).Set(["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"])
+ prim.CreateAttribute(
+ "xformOp:rotateXYZ", Sdf.ValueTypeNames.Double3, False
+ ).Set(
+ Gf.Vec3d(
+ 41.35092544555664,
+ 0.517652153968811,
+ -35.92928695678711
+ )
+ )
+ prim.CreateAttribute(
+ "xformOpOrder", Sdf.ValueTypeNames.String, False).Set(
+ ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
+ )
diff --git a/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/tests/test_app_startup.py b/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/tests/test_app_startup.py
index fdfc8bc..d216165 100644
--- a/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/tests/test_app_startup.py
+++ b/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/tests/test_app_startup.py
@@ -24,7 +24,6 @@ def app_startup_time(self, test_id: str) -> float:
startup_time = omni.kit.app.get_app().get_time_since_start_s()
test_result = {"startup_time_s": startup_time}
print(f"App Startup time: {startup_time}")
- self._post_to_nvdf(test_id, test_result, time.monotonic() - test_start_time)
return startup_time
def app_startup_warning_count(self, test_id: str) -> Tuple[int, int]:
@@ -43,41 +42,8 @@ def app_startup_warning_count(self, test_id: str) -> Tuple[int, int]:
test_result = {"startup_warning_count": warning_count, "startup_error_count": error_count}
print(f"App Startup Warning count: {warning_count}")
print(f"App Startup Error count: {error_count}")
- self._post_to_nvdf(test_id, test_result, time.monotonic() - test_start_time)
return warning_count, error_count
- # TODO: should call proper API from Kit
- def _post_to_nvdf(self, test_id: str, test_result: dict, test_duration: float):
- """Send results to nvdf"""
- try:
- from omni.kit.test.nvdf import _can_post_to_nvdf, _get_ci_info, _post_json, get_app_info, to_nvdf_form
-
- if not _can_post_to_nvdf():
- return
-
- data = {}
- data["ts_created"] = int(time.time() * 1000)
- data["app"] = get_app_info()
- data["ci"] = _get_ci_info()
- data["test"] = {
- "passed": True,
- "skipped": False,
- "unreliable": False,
- "duration": test_duration,
- "test_id": test_id,
- "ext_test_id": "omni.create.tests",
- "test_type": "unittest",
- }
- data["test"].update(test_result)
-
- project = "omniverse-kit-tests-results-v2"
- json_str = json.dumps(to_nvdf_form(data), skipkeys=True)
- _post_json(project, json_str)
- # print(json_str) # uncomment to debug
-
- except Exception as e:
- carb.log_warn(f"Exception occurred: {e}")
-
async def test_l1_app_startup_time(self):
"""Get startup time - send to nvdf"""
for _ in range(60):
diff --git a/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/ui_state_manager.py b/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/ui_state_manager.py
index 8b6404d..9e29516 100644
--- a/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/ui_state_manager.py
+++ b/templates/extensions/usd_explorer.setup/template/{{python_module_path}}/ui_state_manager.py
@@ -8,62 +8,81 @@
# without an express license agreement from NVIDIA CORPORATION or
# its affiliates is strictly prohibited.
-import carb.dictionary
-import carb.settings
-import omni.ui as ui
from functools import partial
from typing import Any, Dict, List, Tuple, Union
+import carb.dictionary
+import carb.settings
+import omni.ui as ui
MODAL_TOOL_ACTIVE_PATH = "/app/tools/modal_tool_active"
class UIStateManager:
- def __init__(self) -> None:
+ """Manages the state of UI elements based on settings and modal tool state."""
+ def __init__(self):
self._settings = carb.settings.acquire_settings_interface()
- self._modal_changed_sub = self._settings.subscribe_to_node_change_events(
- MODAL_TOOL_ACTIVE_PATH,
- self._on_modal_setting_changed
+ self._modal_changed_sub = \
+ self._settings.subscribe_to_node_change_events(
+ MODAL_TOOL_ACTIVE_PATH,
+ self._on_modal_setting_changed
)
- self._hide_on_modal: List[Tuple[str,bool]] = []
- self._modal_restore_window_states: Dict[str,bool] = {}
- self._settings_dependencies: Dict[Tuple(str,str), Dict[Any, Any]] = {}
+ self._hide_on_modal: List[Tuple[str, bool]] = []
+ self._modal_restore_window_states: Dict[str, bool] = {}
+ self._settings_dependencies: Dict[Tuple[str, str], Dict[Any, Any]] = {}
self._settings_changed_subs = {}
self._window_settings = {}
- self._window_vis_changed_id = ui.Workspace.set_window_visibility_changed_callback(self._on_window_vis_changed)
+ self._window_vis_changed_id = \
+ ui.Workspace.set_window_visibility_changed_callback(
+ self._on_window_vis_changed
+ )
- def destroy(self) -> None:
+ def destroy(self):
+ """Unsubscribe from all events and clean up."""
if self._settings:
if self._modal_changed_sub:
- self._settings.unsubscribe_to_change_events(self._modal_changed_sub)
+ self._settings.unsubscribe_to_change_events(
+ self._modal_changed_sub
+ )
self._settings = None
self._hide_on_modal = []
self._modal_restore_window_states = {}
self._settings_dependencies = {}
self._window_settings = {}
if self._window_vis_changed_id:
- ui.Workspace.remove_window_visibility_changed_callback(self._window_vis_changed_id)
+ ui.Workspace.remove_window_visibility_changed_callback(
+ self._window_vis_changed_id
+ )
self._window_vis_changed_id = None
- def __del__(self) -> None:
+ def __del__(self):
+ """Ensure that the object is cleaned up."""
self.destroy()
- def add_hide_on_modal(self, window_names: Union[str, List[str]], restore: bool) -> None:
+ def add_hide_on_modal(
+ self, window_names: Union[str, List[str]], restore: bool
+ ):
+ """Add a window to the list of windows to hide when the modal
+ tool is active."""
if isinstance(window_names, str):
window_names = [window_names]
for window_name in window_names:
if window_name not in self._hide_on_modal:
self._hide_on_modal.append((window_name, restore))
- def remove_hide_on_modal(self, window_names: Union[str, List[str]]) -> None:
+ def remove_hide_on_modal(self, window_names: Union[str, List[str]]):
+ """Remove a window from the list of windows to hide when the modal"""
if isinstance(window_names, str):
window_names = [window_names]
self._hide_on_modal = [item for item in self._hide_on_modal if item[0] not in window_names]
- def add_window_visibility_setting(self, window_name: str, setting_path: str) -> None:
+ def add_window_visibility_setting(
+ self, window_name: str, setting_path: str
+ ):
+ """Add a setting that controls the visibility of a window."""
window = ui.Workspace.get_window(window_name)
if window is not None:
self._settings.set(setting_path, window.visible)
@@ -74,7 +93,10 @@ def add_window_visibility_setting(self, window_name: str, setting_path: str) ->
self._window_settings[window_name] = []
self._window_settings[window_name].append(setting_path)
- def remove_window_visibility_setting(self, window_name: str, setting_path: str) -> None:
+ def remove_window_visibility_setting(
+ self, window_name: str, setting_path: str
+ ):
+ """Remove a setting that controls the visibility of a window."""
if window_name in self._window_settings.keys():
setting_list = self._window_settings[window_name]
if setting_path in setting_list:
@@ -82,25 +104,33 @@ def remove_window_visibility_setting(self, window_name: str, setting_path: str)
if len(setting_list) == 0:
del self._window_settings[window_name]
- def remove_all_window_visibility_settings(self, window_name: str) -> None:
+ def remove_all_window_visibility_settings(self, window_name: str):
+ """Remove all settings that control the visibility of a window."""
if window_name in self._window_settings.keys():
del self._window_settings[window_name]
- def add_settings_dependency(self, source_path: str, target_path: str, value_map: Dict[Any, Any]) -> None:
+ def add_settings_dependency(
+ self, source_path: str, target_path: str, value_map: Dict[Any, Any]
+ ):
+ """Add a dependency between two settings. When the source setting"""
key = (source_path, target_path)
if key in self._settings_dependencies.keys():
carb.log_error(f'Settings dependency {source_path} -> {target_path} already exists. Ignoring.')
return
+
self._settings_dependencies[key] = value_map
- self._settings_changed_subs[key] = self._settings.subscribe_to_node_change_events(
- source_path,
- partial(self._on_settings_dependency_changed, source_path)
- )
+ self._settings_changed_subs[key] = \
+ self._settings.subscribe_to_node_change_events(
+ source_path,
+ partial(self._on_settings_dependency_changed, source_path)
+ )
- def add_settings_copy_dependency(self, source_path: str, target_path: str) -> None:
+ def add_settings_copy_dependency(self, source_path: str, target_path: str):
+ """Add a dependency between two settings. When the source setting"""
self.add_settings_dependency(source_path, target_path, None)
- def remove_settings_dependency(self, source_path: str, target_path: str) -> None:
+ def remove_settings_dependency(self, source_path: str, target_path: str):
+ """Remove a dependency between two settings."""
key = (source_path, target_path)
if key in self._settings_dependencies.keys():
del self._settings_dependencies[key]
@@ -108,12 +138,15 @@ def remove_settings_dependency(self, source_path: str, target_path: str) -> None
sub = self._settings_changed_subs.pop(key)
self._settings.unsubscribe_to_change_events(sub)
- def _on_settings_dependency_changed(self, path: str, item, event_type) -> None:
+ def _on_settings_dependency_changed(self, path: str, _item, _event_type):
+ """Callback for when a setting changes."""
value = self._settings.get(path)
# setting does not exist
if value is None:
return
- target_settings = [source_target[1] for source_target in self._settings_dependencies.keys() if source_target[0] == path]
+ target_settings = [source_target[1] for source_target in
+ self._settings_dependencies.keys() if
+ source_target[0] == path]
for target_setting in target_settings:
value_map = self._settings_dependencies[(path, target_setting)]
# None means copy everything
@@ -122,35 +155,45 @@ def _on_settings_dependency_changed(self, path: str, item, event_type) -> None:
elif value in value_map.keys():
self._settings.set(target_setting, value_map[value])
- def _on_modal_setting_changed(self, item, event_type) -> None:
+ def _on_modal_setting_changed(self, _item, _event_type):
+ """Callback to handle changes to window visibility based on the
+ app mode setting."""
modal = self._settings.get_as_bool(MODAL_TOOL_ACTIVE_PATH)
if modal:
self._hide_windows()
else:
self._restore_windows()
- def _hide_windows(self) -> None:
+ def _hide_windows(self):
+ """Hide all windows that are set to be hidden when the modal tool
+ is active."""
for window_info in self._hide_on_modal:
window_name, restore_later = window_info[0], window_info[1]
window = ui.Workspace.get_window(window_name)
if window is not None:
if restore_later:
- self._modal_restore_window_states[window_name] = window.visible
+ self._modal_restore_window_states[window_name] = \
+ window.visible
window.visible = False
- def _restore_windows(self) -> None:
+ def _restore_windows(self):
+ """Restore all windows that were hidden when the modal tool
+ was active."""
for window_info in self._hide_on_modal:
window_name, restore_later = window_info[0], window_info[1]
if restore_later:
if window_name in self._modal_restore_window_states.keys():
- old_visibility = self._modal_restore_window_states[window_name]
+ old_visibility = \
+ self._modal_restore_window_states[window_name]
if old_visibility is not None:
window = ui.Workspace.get_window(window_name)
if window is not None:
window.visible = old_visibility
- self._modal_restore_window_states[window_name] = None
+ self._modal_restore_window_states[window_name] = \
+ None
- def _on_window_vis_changed(self, title: str, state: bool) -> None:
+ def _on_window_vis_changed(self, title: str, state: bool):
+ """Callback to handle changes to window visibility."""
if title in self._window_settings.keys():
for setting in self._window_settings[title]:
self._settings.set_bool(setting, state)
diff --git a/templates/extensions/usd_viewer.messaging/template/config/extension.toml b/templates/extensions/usd_viewer.messaging/template/config/extension.toml
index 14aa034..5beddc6 100644
--- a/templates/extensions/usd_viewer.messaging/template/config/extension.toml
+++ b/templates/extensions/usd_viewer.messaging/template/config/extension.toml
@@ -41,8 +41,10 @@ pages = [
[[test]]
dependencies = [
+ "omni.activity.ui",
"omni.kit.mainwindow",
"omni.kit.stage_templates",
+ # "omni.kit.test_suite.helpers",
"omni.kit.ui_test",
"omni.kit.viewport.utility",
"omni.kit.viewport.window",
diff --git a/templates/extensions/usd_viewer.messaging/template/data/testing.usd b/templates/extensions/usd_viewer.messaging/template/data/testing.usd
new file mode 100644
index 0000000..35e52f4
--- /dev/null
+++ b/templates/extensions/usd_viewer.messaging/template/data/testing.usd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3686afba555cd20dade989920624ebaf1ed84de4420015fde5a3b4eeaa2518d0
+size 50011
diff --git a/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/extension.py b/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/extension.py
index 9c1af48..d0b0cbf 100644
--- a/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/extension.py
+++ b/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/extension.py
@@ -7,21 +7,28 @@
# disclosure or distribution of this material and related documentation
# without an express license agreement from NVIDIA CORPORATION or
# its affiliates is strictly prohibited.
-import omni.ext
+
from .stage_loading import LoadingManager
from .stage_management import StageManager
+import omni.ext
-# Any class derived from `omni.ext.IExt` in top level module (defined in `python.modules` of `extension.toml`) will be
-# instantiated when extension gets enabled and `on_startup(ext_id)` will be called. Later when extension gets disabled
-# on_shutdown() is called.
-class Extension(omni.ext.IExt):
+# Any class derived from `omni.ext.IExt` in top level module (defined in
+# `python.modules` of `extension.toml`) will be instantiated when extension
+# gets enabled and `on_startup(ext_id)` will be called. Later when extension
+# gets disabled on_shutdown() is called.
+class Extension(omni.ext.IExt):
+ """This extension manages creating the loading and stage
+ messaging managers"""
def on_startup(self):
+ """This is called every time the extension is activated."""
# Internal messaging state
self._loading_manager: LoadingManager = LoadingManager()
self._stage_manager: StageManager = StageManager()
def on_shutdown(self):
+ """This is called every time the extension is deactivated. It is used to
+ clean up the extension state."""
# Resetting the state.
if self._loading_manager:
self._loading_manager.on_shutdown()
diff --git a/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/stage_loading.py b/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/stage_loading.py
index 43f91b1..936fd45 100644
--- a/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/stage_loading.py
+++ b/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/stage_loading.py
@@ -8,35 +8,46 @@
# without an express license agreement from NVIDIA CORPORATION or
# its affiliates is strictly prohibited.
-import time
-import omni.ext
-import omni.kit.app
-import omni.usd
import asyncio
+
import carb
+import carb.events
+import carb.tokens
+import omni.ext
import omni.log
+import omni.kit.app
import omni.kit.livestream.messaging as messaging
+import omni.usd
class LoadingManager:
-
+ """Manages the loading of USD stages and sends messages to the client"""
def __init__(self):
- self._subscriptions = [] # Holds subscription pointers
+ self._subscriptions = [] # Holds subscription pointers
# -- state variables
- self._requested_stage_url: str = "" # URL of stage load request. Can be used in messaging with client.
+ # URL of stage load request. Can be used in messaging with client.
+ self._requested_stage_url: str = ""
self._stage_is_opening: bool = False
- self._opened_stage_url: str = "" # URL of loaded stage. Should not be used in messaging with client because it may reveal directory paths in environment where application runs.
+
+ # URL of loaded stage. Should not be used in messaging with client
+ # because it may reveal directory paths in environment where
+ # application runs.
+ self._opened_stage_url: str = ""
self._stage_has_opened = False
self._streaming_manager_is_busy: bool = False
- self._persisted_stage: bool = False # States if opened stage is opened from storage as in not a new unsaved stage
+
+ # States if opened stage is opened from storage as in not a
+ # new unsaved stage
+ self._persisted_stage: bool = False
self._is_evaluating_loading_status: bool = False
# -- register outgoing events/messages
outgoing = [
"openedStageResult", # notify when USD Stage has loaded.
"updateProgressAmount", # Status bar event denoting progress
- "updateProgressActivity", # Status bar event denoting current activity
+ "updateProgressActivity", # Status bar event denoting activity
+ "loadingStateResponse", # Response to loadingStateQuery
]
for o in outgoing:
@@ -45,8 +56,11 @@ def __init__(self):
# -- register incoming events/messages
incoming = {
'openStageRequest': self._on_open_stage, # request to open a stage
- "omni.kit.window.status_bar@progress": self._on_progress, # internal event to capture progress status
- "omni.kit.window.status_bar@activity": self._on_activity, # internal event to capture progress activity
+ # internal event to capture progress status
+ "omni.kit.window.status_bar@progress": self._on_progress,
+ # internal event to capture progress activity
+ "omni.kit.window.status_bar@activity": self._on_activity,
+ "loadingStateQuery": self._on_load_state_query,
}
message_bus = omni.kit.app.get_app().get_message_bus_event_stream()
@@ -64,16 +78,34 @@ def __init__(self):
)
# -- subscribe to RTX streaming status events
- RTX_STREAMING_STATUS_EVENT: int = carb.events.type_from_string("omni.rtx.StreamingStatus")
+ RTX_STREAMING_STATUS_EVENT: int = carb.events.type_from_string(
+ "omni.rtx.StreamingStatus"
+ )
self._subscriptions.append(
- event_stream.create_subscription_to_pop_by_type(RTX_STREAMING_STATUS_EVENT, self._on_rxt_streaming_event)
+ event_stream.create_subscription_to_pop_by_type(
+ RTX_STREAMING_STATUS_EVENT, self._on_rxt_streaming_event
+ )
)
+ def _on_load_state_query(self, event: carb.events.IEvent) -> None:
+ if event.type == carb.events.type_from_string("loadingStateQuery"):
+ message_bus = omni.kit.app.get_app().get_message_bus_event_stream()
+ event_type = carb.events.type_from_string("loadingStateResponse")
+ payload = {"loading_state": "idle", "url": self._opened_stage_url}
+ if self._stage_is_opening:
+ payload = { "loading_state": "busy", "url": self._requested_stage_url }
+ elif self._stage_has_opened:
+ payload = { "loading_state": "idle", "url": self._requested_stage_url }
+
+ message_bus.dispatch(event_type, payload=payload)
+
+
def _on_open_stage(self, event: carb.events.IEvent) -> None:
"""
Handler for `openStageRequest` event.
- Starts loading a given URL, will send success if the layer is already loaded, and an error on any failure.
+ Starts loading a given URL, will send success if the layer is already
+ loaded, and an error on any failure.
"""
if event.type == carb.events.type_from_string("openStageRequest"):
@@ -83,12 +115,17 @@ def _on_open_stage(self, event: carb.events.IEvent) -> None:
return
self._requested_stage_url = event.payload["url"]
- carb.log_info(f"Received message to load '{self._requested_stage_url}'")
+ carb.log_info(
+ f"Received message to load '{self._requested_stage_url}'"
+ )
def process_url(url):
- # Using a single leading `.` to signify that the path is relative to the ${app} token's parent directory.
+ # Using a single leading `.` to signify that the path is
+ # relative to the ${app} token's parent directory.
if url.startswith(("./", ".\\")):
- return carb.tokens.acquire_tokens_interface().resolve("${app}/.." + url[1:])
+ return carb.tokens.acquire_tokens_interface().resolve(
+ "${app}/.." + url[1:]
+ )
return url
# Check to see if we've already loaded the current stage.
@@ -111,22 +148,31 @@ def process_url(url):
async def open_stage():
carb.log_info(f'Opening stage per client request: {url}')
usd_context = omni.usd.get_context()
- result, error = await usd_context.open_stage_async(url, omni.usd.UsdContextInitialLoadSet.LOAD_ALL)
+ if url:
+ result, error = await usd_context.open_stage_async(url, omni.usd.UsdContextInitialLoadSet.LOAD_ALL)
+ else:
+ result, error = await usd_context.new_stage_async()
+
+ message_bus = omni.kit.app.get_app().get_message_bus_event_stream()
+ event_type = carb.events.type_from_string("openedStageResult")
+
if result is not True:
# Send message to client that loading failed.
carb.log_warn(f'The file that the client requested failed to load: {url} (error: {error})')
- message_bus = omni.kit.app.get_app().get_message_bus_event_stream()
- event_type = carb.events.type_from_string("openedStageResult")
payload = {"url": url, "result": "error", "error": error}
message_bus.dispatch(event_type, payload=payload)
self._reset_state()
+ return
- asyncio.ensure_future(open_stage())
+ payload = {"url": url, "result": "success", "error": ''}
+ message_bus.dispatch(event_type, payload=payload)
+ asyncio.ensure_future(open_stage())
def _on_stage_event(self, event: carb.events.IEvent) -> None:
- """Manage extension state via the stage event stream. When a new stage is open we reload the data model
- and set the state for the UI.
+ """Manage extension state via the stage event stream.
+ When a new stage is open we reload the data model and
+ set the state for the UI.
Args:
event (carb.events.IEvent): Event type
@@ -157,13 +203,15 @@ def _on_rxt_streaming_event(self, event: carb.events.IEvent) -> None:
Notes streaming manager's busy state
Args:
- event (carb.events.IEvent): Contains payload sender and type - https://docs.omniverse.nvidia.com/kit/docs/kit-manual/105.0/carb.events/carb.events.IEvent.html
+ event (carb.events.IEvent): Contains payload sender and type -
+ https://docs.omniverse.nvidia.com/kit/docs/kit-manual/105.0/carb.events/carb.events.IEvent.html
"""
self._streaming_manager_is_busy = event.payload['isBusy']
async def _evaluate_load_status(self):
"""
- If streaming manager is not busy and the stage is loaded from storage we notify client.
+ If streaming manager is not busy and the stage is loaded from storage,
+ notify the client.
"""
# Only evaluate for stage loaded from storage.
if not self._persisted_stage:
@@ -183,8 +231,10 @@ async def _evaluate_load_status(self):
# Stage has loaded with all dependencies. Send message to client.
message_bus = omni.kit.app.get_app().get_message_bus_event_stream()
event_type = carb.events.type_from_string("openedStageResult")
- url = self._requested_stage_url if self._requested_stage_url else '[obfuscated]'
- carb.log_info(f'Sending message to client that stage has loaded: {url}')
+ url = self._requested_stage_url if self._requested_stage_url else '[obfuscated]'
+ carb.log_info(
+ f'Sending message to client that stage has loaded: {url}'
+ )
payload = {"url": url, "result": "success", "error": ''}
message_bus.dispatch(event_type, payload=payload)
@@ -200,12 +250,15 @@ def _on_progress(self, event: carb.events.IEvent):
# Only notify for stage loaded from storage.
if not self._persisted_stage:
return
- if event.type == carb.events.type_from_string("omni.kit.window.status_bar@progress"):
+ if event.type == carb.events.type_from_string(
+ "omni.kit.window.status_bar@progress"
+ ):
message_bus = omni.kit.app.get_app().get_message_bus_event_stream()
# Send progress message
carb.log_info('Sending message to client about loading progress.')
event_type = carb.events.type_from_string("updateProgressAmount")
- # event.payload.get_dict() is used to capture a copy of the incoming event's payload as a python dictionary
+ # event.payload.get_dict() is used to capture a copy of the
+ # incoming event's payload as a python dictionary
message_bus.dispatch(event_type, payload=event.payload.get_dict())
def _on_activity(self, event: carb.events.IEvent):
@@ -216,7 +269,9 @@ def _on_activity(self, event: carb.events.IEvent):
# Only notify for stage loaded from storage.
if not self._persisted_stage:
return
- if event.type == carb.events.type_from_string("omni.kit.window.status_bar@activity"):
+ if event.type == carb.events.type_from_string(
+ "omni.kit.window.status_bar@activity"
+ ):
carb.log_info('Storing message about loading activity.')
message_bus = omni.kit.app.get_app().get_message_bus_event_stream()
# Send activity message
@@ -235,8 +290,9 @@ def _reset_state(self):
"""
Reset the internal state - ready for new stage to be loaded
"""
+ stage = omni.usd.get_context().get_stage()
self._requested_stage_url = ""
- self._opened_stage_url = ""
+ self._opened_stage_url = stage.GetRootLayer().identifier if stage else ""
self._stage_has_opened = False
self._streaming_manager_is_busy = False
self._persisted_stage = False
diff --git a/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/stage_management.py b/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/stage_management.py
index 5edbf7d..afc5248 100644
--- a/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/stage_management.py
+++ b/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/stage_management.py
@@ -8,27 +8,24 @@
# without an express license agreement from NVIDIA CORPORATION or
# its affiliates is strictly prohibited.
-import omni.ext
-import omni.kit.app
+from pxr import UsdGeom, Usd
import carb
-import carb.settings
import carb.dictionary
import carb.events
+import carb.settings
import carb.tokens
-import carb.input
-import omni.usd
import omni.client.utils
+import omni.ext
+import omni.usd
+import omni.kit.app
import omni.kit.livestream.messaging as messaging
from omni.kit.viewport.utility import get_active_viewport_camera_string
-from pxr import UsdGeom, Usd
-
class StageManager:
-
+ """This class manages the stage and its related events."""
def __init__(self):
-
# Internal messaging state
self._is_external_update: bool = False
self._camera_attrs = {}
@@ -36,10 +33,14 @@ def __init__(self):
# -- register outgoing events/messages
outgoing = [
- "stageSelectionChanged", # notify when user selects something in the viewport.
- "getChildrenResponse", # response to request for children of a prim
- "makePrimsPickableResponse", # response to request for primitive being pickable.
- "resetStageResponse", # response to the request to reset camera attributes
+ # notify when user selects something in the viewport.
+ "stageSelectionChanged",
+ # response to request for children of a prim
+ "getChildrenResponse",
+ # response to request for primitive being pickable.
+ "makePrimsPickableResponse",
+ # response to the request to reset camera attributes
+ "resetStageResponse",
]
for o in outgoing:
@@ -47,17 +48,20 @@ def __init__(self):
# -- register incoming events/messages
incoming = {
- 'getChildrenRequest': self._on_get_children, # request to get children of a prim
- 'selectPrimsRequest' : self._on_select_prims, # request to select a prim
- 'makePrimsPickable' : self._on_make_pickable, # request to make primitives pickable
- 'resetStage' : self._on_reset_camera, # request to make primitives pickable
+ # request to get children of a prim
+ 'getChildrenRequest': self._on_get_children,
+ # request to select a prim
+ 'selectPrimsRequest': self._on_select_prims,
+ # request to make primitives pickable
+ 'makePrimsPickable': self._on_make_pickable,
+ # request to make primitives pickable
+ 'resetStage': self._on_reset_camera,
}
for event_type, handler in incoming.items():
self._subscriptions.append(
- omni.kit.app.get_app().get_message_bus_event_stream().create_subscription_to_pop(
- handler, name=event_type
- )
+ omni.kit.app.get_app().get_message_bus_event_stream().
+ create_subscription_to_pop(handler, name=event_type)
)
# -- subscribe to stage events
@@ -66,13 +70,14 @@ def __init__(self):
event_stream.create_subscription_to_pop(self._on_stage_event)
)
-
def get_children(self, prim_path, filters=None):
"""
Collect any children of the given `prim_path`, potentially filtered by `filters`
"""
stage = omni.usd.get_context().get_stage()
prim = stage.GetPrimAtPath(prim_path)
+ if not prim:
+ return []
filter_types = {
"USDGeom": UsdGeom.Mesh,
@@ -96,12 +101,13 @@ def get_children(self, prim_path, filters=None):
# Also skipping rendering primitives.
if prim_path == '/' and child_name == 'Render':
continue
- child_path = child_path if not child_path == '/' else ''
+ child_path = child_path if child_path != '/' else ''
carb.log_info(f'child_path: {child_path}')
info = {"name": child_name, "path": f'{child_path}/{child_name}'}
- # We return an empty list here to indicate that children are available, but
- # the current app does not support pagination, so we use this to lazy load the stage tree.
+ # We return an empty list here to indicate that children are
+ # available, but the current app does not support pagination,
+ # so we use this to lazy load the stage tree.
if child.GetChildren():
info["children"] = []
@@ -115,12 +121,17 @@ def _on_get_children(self, event: carb.events.IEvent) -> None:
Collects a filtered collection of a given primitives children.
"""
if event.type == carb.events.type_from_string("getChildrenRequest"):
- carb.log_info(f"Received message to return list of a prim\'s children")
+ carb.log_info(
+ "Received message to return list of a prim\'s children"
+ )
message_bus = omni.kit.app.get_app().get_message_bus_event_stream()
event_type = carb.events.type_from_string("getChildrenResponse")
- children = self.get_children(prim_path=event.payload["prim_path"], filters=event.payload["filters"])
+ children = self.get_children(
+ prim_path=event.payload["prim_path"],
+ filters=event.payload["filters"]
+ )
payload = {
- "prim_path" : event.payload["prim_path"],
+ "prim_path": event.payload["prim_path"],
"children": children
}
message_bus.dispatch(event_type, payload=payload)
@@ -137,7 +148,8 @@ def _on_select_prims(self, event: carb.events.IEvent) -> None:
if "paths" in event.payload:
new_selection = list(event.payload["paths"])
carb.log_info(f"Received message to select '{new_selection}'")
- # Flagging this as an external event because it was initiated by the client.
+ # Flagging this as an external event because it
+ # was initiated by the client.
self._is_external_update = True
sel = omni.usd.get_context().get_selection()
sel.clear_selected_prim_paths()
@@ -147,25 +159,33 @@ def _on_stage_event(self, event):
"""
Hanles all stage related events.
- `omni.usd.StageEventType.SELECTION_CHANGED`: Informs the StreamerApp that the selection has changed.
- `omni.usd.StageEventType.ASSETS_LOADED`: Informs the StreamerApp that a stage has finished loading its assets.
- `omni.usd.StageEventType.OPENED`: On stage opened, we collect some of the camera properties to allow for them to be reset.
-
+ `omni.usd.StageEventType.SELECTION_CHANGED`:
+ Informs the StreamerApp that the selection has changed.
+ `omni.usd.StageEventType.ASSETS_LOADED`:
+ Informs the StreamerApp that a stage has finished loading
+ its assets.
+ `omni.usd.StageEventType.OPENED`:
+ On stage opened, we collect some of the camera properties
+ to allow for them to be reset.
"""
if event.type == int(omni.usd.StageEventType.SELECTION_CHANGED):
- # If the selection changed came from an external event, we don't need to let the streaming client know
- # because it initiated the change and is already aware.
+ # If the selection changed came from an external event,
+ # we don't need to let the streaming client know because it
+ # initiated the change and is already aware.
if self._is_external_update:
self._is_external_update = False
else:
- message_bus = omni.kit.app.get_app().get_message_bus_event_stream()
- event_type = carb.events.type_from_string("stageSelectionChanged")
- payload = {"prims": omni.usd.get_context().get_selection().get_selected_prim_paths()}
+ message_bus = omni.kit.app.get_app()\
+ .get_message_bus_event_stream()
+ event_type = carb.events.type_from_string(
+ "stageSelectionChanged"
+ )
+ payload = {"prims": omni.usd.get_context().get_selection().
+ get_selected_prim_paths()}
message_bus.dispatch(event_type, payload=payload)
message_bus.pump()
carb.log_info(f"Selection changed: Path to USD prims currently selected = {omni.usd.get_context().get_selection().get_selected_prim_paths()}")
- # elif event.type == int(omni.usd.StageEventType.ASSETS_LOADED):
elif event.type == int(omni.usd.StageEventType.OPENED):
stage = omni.usd.get_context().get_stage()
stage_url = stage.GetRootLayer().identifier if stage else ''
@@ -174,9 +194,11 @@ def _on_stage_event(self, event):
# Set the entire stage to not be pickable.
ctx = omni.usd.get_context()
ctx.set_pickable("/", False)
- # Clear before using, so that we're sure the data is only from the new stage.
+ # Clear before using, so that we're sure the data is only
+ # from the new stage.
self._camera_attrs.clear()
- # Capture the active camera's camera data, used to reset the scene to a known good state.
+ # Capture the active camera's camera data, used to reset
+ # the scene to a known good state.
if (prim := ctx.get_stage().GetPrimAtPath(get_active_viewport_camera_string())):
for attr in prim.GetAttributes():
self._camera_attrs[attr.GetName()] = attr.Get()
@@ -193,10 +215,15 @@ def _on_reset_camera(self, event: carb.events.IEvent):
stage = ctx.get_stage()
try:
# Reset the camera.
- # The camera lives on the session layer, which has a higher opinion than the root stage.
- # So we need to explicitly target the session layer when resetting the camera's attributes.
- camera_prim = ctx.get_stage().GetPrimAtPath(get_active_viewport_camera_string())
- edit_context = Usd.EditContext(stage, Usd.EditTarget(stage.GetSessionLayer()))
+ # The camera lives on the session layer, which has a higher
+ # opinion than the root stage. So we need to explicitly target
+ # the session layer when resetting the camera's attributes.
+ camera_prim = ctx.get_stage().GetPrimAtPath(
+ get_active_viewport_camera_string()
+ )
+ edit_context = Usd.EditContext(
+ stage, Usd.EditTarget(stage.GetSessionLayer())
+ )
with edit_context:
for name, value in self._camera_attrs.items():
attr = camera_prim.GetAttribute(name)
@@ -215,15 +242,22 @@ def _on_make_pickable(self, event: carb.events.IEvent):
Handler for `makePrimsPickable` event.
Enables viewport selection for the provided primitives.
- Sends 'makePrimsPickableResponse' back to streamer with current success status.
+ Sends 'makePrimsPickableResponse' back to streamer with
+ current success status.
"""
if event.type == carb.events.type_from_string("makePrimsPickable"):
message_bus = omni.kit.app.get_app().get_message_bus_event_stream()
- event_type = carb.events.type_from_string("makePrimsPickableResponse")
+ event_type = carb.events.type_from_string(
+ "makePrimsPickableResponse"
+ )
+ # Reset the stage to not be pickable.
+ ctx = omni.usd.get_context()
+ ctx.set_pickable("/", False)
+ # Set the provided paths to be pickable.
try:
paths = event.payload['paths'] or []
for path in paths:
- omni.usd.get_context().set_pickable(path, True)
+ ctx.set_pickable(path, True)
except Exception as e:
payload = {"result": "error", "error": str(e)}
else:
@@ -231,8 +265,9 @@ def _on_make_pickable(self, event: carb.events.IEvent):
message_bus.dispatch(event_type, payload=payload)
message_bus.pump()
-
def on_shutdown(self):
+ """This is called every time the extension is deactivated. It is used
+ to clean up the extension state."""
# Reseting the state.
self._subscriptions.clear()
self._is_external_update: bool = False
diff --git a/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/tests/__init__.py b/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/tests/__init__.py
new file mode 100644
index 0000000..170eeda
--- /dev/null
+++ b/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/tests/__init__.py
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
+#
+# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
+# property and proprietary rights in and to this material, related
+# documentation and any modifications thereto. Any use, reproduction,
+# disclosure or distribution of this material and related documentation
+# without an express license agreement from NVIDIA CORPORATION or
+# its affiliates is strictly prohibited.
+
+# from .messaging_tests import *
diff --git a/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/tests/messaging_tests.py b/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/tests/messaging_tests.py
new file mode 100644
index 0000000..fb9d414
--- /dev/null
+++ b/templates/extensions/usd_viewer.messaging/template/{{python_module_path}}/tests/messaging_tests.py
@@ -0,0 +1,145 @@
+# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
+#
+# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
+# property and proprietary rights in and to this material, related
+# documentation and any modifications thereto. Any use, reproduction,
+# disclosure or distribution of this material and related documentation
+# without an express license agreement from NVIDIA CORPORATION or
+# its affiliates is strictly prohibited.
+
+# from pathlib import Path
+# from typing import Dict, List
+
+# import carb.events
+# import carb.tokens
+# import omni.kit.app
+# from omni.kit.test import AsyncTestCase
+# # from omni.kit.test_suite.helpers import wait_stage_loading
+
+
+# class MessagingTest(AsyncTestCase):
+# async def setUp(self):
+# self._app = omni.kit.app.get_app()
+# self._message_bus = self._app.get_message_bus_event_stream()
+
+# # Capture extension root path
+# extension = "{{ extension_name }}"
+# ext_root = Path(carb.tokens.get_tokens_interface().resolve({% raw %}f"${{{extension}}}"{% endraw %}))
+# self._data_path = ext_root / "data"
+
+ # async def test_stage_loading_incoming(self):
+ # """
+ # Simulate incoming events of the stage loading messaging system
+ # """
+ # import omni.kit.livestream.messaging as messaging
+
+ # def on_message_event(event: carb.events.IEvent) -> None:
+ # if event.type == carb.events.type_from_string("updateProgressAmount"):
+ # outgoing["updateProgressAmount"] = True
+ # elif event.type == carb.events.type_from_string("updateProgressActivity"):
+ # outgoing["updateProgressActivity"] = True
+
+
+ # # Register the open stage request type
+ # messaging.register_event_type_to_send("openStageRequest")
+
+ # outgoing: Dict[str, bool] = {
+ # "updateProgressAmount": False, # Status bar event denoting progress
+ # "updateProgressActivity": False # Status bar event denoting current activity
+ # }
+
+ # subscriptions: List[int] = []
+ # for event in outgoing.keys():
+ # subscriptions.append(
+ # self._message_bus.create_subscription_to_pop(
+ # on_message_event,
+ # name=event
+ # )
+ # )
+ # await self._app.next_update_async()
+
+ # # Send the openStageRequest event
+ # event_type = carb.events.type_from_string("openStageRequest")
+ # url = self._data_path / "testing.usd"
+ # self._message_bus.dispatch(event_type, payload={"url": url.as_posix()})
+
+ # await wait_stage_loading(wait_frames=300)
+ # self.assertTrue(all(outgoing.values()))
+
+
+ # async def test_stage_management_incoming(self):
+ # """
+ # Simulate incoming events of the stage management messaging system
+ # """
+ # import omni.kit.livestream.messaging as messaging
+
+ # subscriptions: List[int] = []
+
+ # outgoing: Dict[str, bool] = {
+ # "stageSelectionChanged": False, # notify when user selects something in the viewport.
+ # "getChildrenResponse": False, # response to request for children of a prim
+ # "makePrimsPickableResponse": False, # response to request for primitive being pickable.
+ # "resetStageResponse": False, # response to the request to reset camera attributes
+ # }
+
+ # def on_message_event(event: carb.events.IEvent) -> None:
+ # if event.type == carb.events.type_from_string("stageSelectionChanged"):
+ # outgoing["stageSelectionChanged"] = True
+ # elif event.type == carb.events.type_from_string("getChildrenResponse"):
+ # outgoing["getChildrenResponse"] = True
+ # elif event.type == carb.events.type_from_string("makePrimsPickableResponse"):
+ # outgoing["makePrimsPickableResponse"] = True
+ # elif event.type == carb.events.type_from_string("resetStageResponse"):
+ # outgoing["resetStageResponse"] = True
+
+ # incoming: List[str] = [
+ # 'getChildrenRequest',
+ # 'selectPrimsRequest',
+ # 'makePrimsPickable',
+ # 'resetStage'
+ # ]
+
+ # # Register outgoing event
+ # # Subscribe to messaging events
+ # # Send event to validate
+ # for event in outgoing.keys():
+ # subscriptions.append(self._message_bus.create_subscription_to_pop(
+ # on_message_event,
+ # name=event
+ # ))
+
+ # event_type = carb.events.type_from_string(event)
+ # self._message_bus.dispatch(event_type, payload={})
+
+ # await self._app.next_update_async()
+
+ # # Send the openStageRequest event
+ # event_type = carb.events.type_from_string("openStageRequest")
+ # url = self._data_path / "testing.usd"
+ # self._message_bus.dispatch(event_type, payload={"url": url.as_posix()})
+
+ # # Wait for the stage to load
+ # await wait_stage_loading(wait_frames=30)
+
+ # # Get children of root
+ # event_type = carb.events.type_from_string("getChildrenRequest")
+ # self._message_bus.dispatch(event_type, payload={"prim_path": "/World"})
+ # await self._app.next_update_async()
+
+ # # Select Prims Request
+ # event_type = carb.events.type_from_string("selectPrimsRequest")
+ # self._message_bus.dispatch(event_type, payload={"paths": ["/World/Cube"]})
+ # await self._app.next_update_async()
+
+ # # Make Prims Pickable Request
+ # event_type = carb.events.type_from_string("makePrimsPickable")
+ # self._message_bus.dispatch(event_type, payload={"paths": ["/World/Cube", "/World/Sphere"]})
+ # await self._app.next_update_async()
+
+ # # Reset Stage Request
+ # event_type = carb.events.type_from_string("resetStage")
+ # self._message_bus.dispatch(event_type)
+ # await self._app.next_update_async()
+
+ # self.assertTrue(all(outgoing.values()))
diff --git a/templates/extensions/usd_viewer.setup/template/config/extension.toml b/templates/extensions/usd_viewer.setup/template/config/extension.toml
index c879025..818dc0c 100644
--- a/templates/extensions/usd_viewer.setup/template/config/extension.toml
+++ b/templates/extensions/usd_viewer.setup/template/config/extension.toml
@@ -22,12 +22,17 @@ repository = "https://github.com/NVIDIA-Omniverse/kit-app-template" # URL of th
[dependencies]
-"omni.activity.ui" = {} # Progress activity messages
+"omni.kit.usd.layers" = {}
+"omni.activity.ui" = {order=1000} # Progress activity messages
"omni.kit.quicklayout" = {}
"omni.kit.viewport.utility" = {}
"{{ extra_extension_name }}" = {} # Required messaging extension
+[settings.app]
+useFabricSceneDelegate = true # Turn on the Fabric scene delegate by default
+
+
[settings.exts."{{ extension_name }}"]
menu_visible = false
@@ -45,9 +50,20 @@ pages = [
[[test]]
dependencies = [
+ "{{ application_name }}",
+ "omni.kit.mainwindow",
+ "omni.kit.ui_test",
]
args = [
"--/app/layout/name=default",
+ "--/app/fastShutdown=1",
+ "--/app/file/ignoreUnsavedOnExit=true",
+ "--/app/window/dpiScaleOverride=1.0",
+ "--/app/window/height=720",
+ "--/app/window/scaleToMonitor=false",
+ "--/app/window/width=1280",
+ "--/exts/omni.kit.viewport.window/startup/windowName=Viewport",
"--no-window",
+ "--reset-user"
]
diff --git a/templates/extensions/usd_viewer.setup/template/{{python_module_path}}/setup.py b/templates/extensions/usd_viewer.setup/template/{{python_module_path}}/setup.py
index e224056..a7943e0 100644
--- a/templates/extensions/usd_viewer.setup/template/{{python_module_path}}/setup.py
+++ b/templates/extensions/usd_viewer.setup/template/{{python_module_path}}/setup.py
@@ -17,7 +17,6 @@
import omni.kit.app
import omni.kit.imgui as _imgui
import omni.kit.viewport
-import omni.ui as ui
import omni.usd
from omni.kit.mainwindow import get_main_window
from omni.kit.quicklayout import QuickLayout
@@ -26,9 +25,10 @@
COMMAND_MACRO_SETTING = "/exts/omni.kit.command_macro.core/"
COMMAND_MACRO_FILE_SETTING = COMMAND_MACRO_SETTING + "macro_file"
+
async def _load_layout(layout_file: str):
- """this private methods just help loading layout, you can use it in the Layout Menu"""
- await omni.kit.app.get_app().next_update_async() # type: ignore
+ """Loads a provided layout file and ensures the viewport is set to FILL."""
+ await omni.kit.app.get_app().next_update_async()
QuickLayout.load_file(layout_file)
# Set viewport to FILL
@@ -38,22 +38,25 @@ async def _load_layout(layout_file: str):
class SetupExtension(omni.ext.IExt):
- # ext_id is current extension id. It can be used with extension manager to query additional information, like where
- # this extension is located on filesystem.
+ """Extension that sets up the USD Viewer application."""
def on_startup(self, _ext_id: str):
-
- # get the settings
+ """This is called every time the extension is activated. It is used to
+ set up the application and load the stage."""
self._settings = carb.settings.get_settings()
# get auto load stage name
stage_url = self._settings.get_as_string("/app/auto_load_usd")
- # check if setup have benchmark macro file to activate - ignore setup auto_load_usd name, in order to run proper benchmark.
- benchmark_macro_file_name = self._settings.get(COMMAND_MACRO_FILE_SETTING)
+ # check if setup have benchmark macro file to activate - ignore setup
+ # auto_load_usd name, in order to run proper benchmark.
+ benchmark_macro_file_name = self._settings.get(
+ COMMAND_MACRO_FILE_SETTING)
if benchmark_macro_file_name:
stage_url = None
- # if no benchmark is activated (not applicable on production - provided macro file name will always be None) - load provided by setup stage.
+ # if no benchmark is activated (not applicable on production -
+ # provided macro file name will always be None) -
+ # load provided by setup stage.
if stage_url:
stage_url = carb.tokens.get_tokens_interface().resolve(stage_url)
asyncio.ensure_future(self.__open_stage(stage_url))
@@ -62,13 +65,15 @@ def on_startup(self, _ext_id: str):
get_main_window().get_main_menu_bar().visible = False
async def _delayed_layout(self):
- # few frame delay to allow automatic Layout of window that want their own positionsdef _setup_menu(self):
- # Menubar
+ """This function is used to delay the layout loading until the
+ application has finished its initial setup."""
main_menu_bar = get_main_window().get_main_menu_bar()
main_menu_bar.visible = False
- # few frame delay to allow automatic Layout of window that want their own positions
- for _i in range(4):
- await omni.kit.app.get_app().next_update_async() # type: ignore
+ # few frame delay to allow automatic Layout of window that want their
+ # own positions
+ app = omni.kit.app.get_app()
+ for _ in range(4):
+ await app.next_update_async() # type: ignore
settings = carb.settings.get_settings()
# setup the Layout for your app
@@ -83,16 +88,29 @@ async def _delayed_layout(self):
# using imgui directly to adjust some color and Variable
imgui = _imgui.acquire_imgui()
- # DockSplitterSize is the variable that drive the size of the Dock Split connection
+ # DockSplitterSize is the variable that drive the size of the
+ # Dock Split connection
imgui.push_style_var_float(_imgui.StyleVar.DockSplitterSize, 2)
- async def __open_stage(self, url):
- # 10 frame delay to allow Layout
- for i in range(5):
- await omni.kit.app.get_app().next_update_async() # type: ignore
+ async def __open_stage(self, url, frame_delay: int = 5):
+ """Opens the provided USD stage and loads the render settings."""
+ # default 5 frame delay to allow for Layout
+ if frame_delay:
+ app = omni.kit.app.get_app()
+ for _ in range(frame_delay):
+ await app.next_update_async()
usd_context = omni.usd.get_context()
- await usd_context.open_stage_async(url, omni.usd.UsdContextInitialLoadSet.LOAD_ALL) # type: ignore
+ await usd_context.open_stage_async(
+ url, omni.usd.UsdContextInitialLoadSet.LOAD_ALL)
+
+ # If this was the first Usd data opened, explicitly restore
+ # render-settings now as the renderer may not have been fully
+ # setup when the stage was opened.
+ if not bool(self._settings.get("/app/content/emptyStageOnStart")):
+ usd_context.load_render_settings_from_stage(
+ usd_context.get_stage_id())
def on_shutdown(self):
- pass
+ """This is called every time the extension is deactivated."""
+ return
diff --git a/templates/extensions/usd_viewer.setup/template/{{python_module_path}}/tests/__init__.py b/templates/extensions/usd_viewer.setup/template/{{python_module_path}}/tests/__init__.py
new file mode 100644
index 0000000..e866cdc
--- /dev/null
+++ b/templates/extensions/usd_viewer.setup/template/{{python_module_path}}/tests/__init__.py
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
+#
+# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
+# property and proprietary rights in and to this material, related
+# documentation and any modifications thereto. Any use, reproduction,
+# disclosure or distribution of this material and related documentation
+# without an express license agreement from NVIDIA CORPORATION or
+# its affiliates is strictly prohibited.
+
+# run startup tests first
+from .test_app_startup import *
+from .test_app_extensions import *
diff --git a/templates/extensions/usd_viewer.setup/template/{{python_module_path}}/tests/test_app_extensions.py b/templates/extensions/usd_viewer.setup/template/{{python_module_path}}/tests/test_app_extensions.py
new file mode 100644
index 0000000..3f71bf2
--- /dev/null
+++ b/templates/extensions/usd_viewer.setup/template/{{python_module_path}}/tests/test_app_extensions.py
@@ -0,0 +1,45 @@
+# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
+#
+# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
+# property and proprietary rights in and to this material, related
+# documentation and any modifications thereto. Any use, reproduction,
+# disclosure or distribution of this material and related documentation
+# without an express license agreement from NVIDIA CORPORATION or
+# its affiliates is strictly prohibited.
+
+import omni.kit.app
+from omni.kit.test import AsyncTestCase
+
+
+class TestUSDViewerExtensions(AsyncTestCase):
+ # NOTE: Function pulled to remove dependency from omni.kit.core.tests
+ def _validate_extensions_load(self):
+ failures = []
+ manager = omni.kit.app.get_app().get_extension_manager()
+ for ext in manager.get_extensions():
+ ext_id = ext["id"]
+ ext_name = ext["name"]
+ info = manager.get_extension_dict(ext_id)
+
+ enabled = ext.get("enabled", False)
+ if not enabled:
+ continue
+
+ failed = info.get("state/failed", False)
+ if failed:
+ failures.append(ext_name)
+
+ if len(failures) == 0:
+ print("\n[success] All extensions loaded successfully!\n")
+ else:
+ print("")
+ print(f"[error] Found {len(failures)} extensions that could not load:")
+ for count, ext in enumerate(failures):
+ print(f" {count+1}: {ext}")
+ print("")
+ return len(failures)
+
+ async def test_l1_extensions_load(self):
+ """Loop all enabled extensions to see if they loaded correctly"""
+ self.assertEqual(self._validate_extensions_load(), 0)
\ No newline at end of file
diff --git a/templates/extensions/usd_viewer.setup/template/{{python_module_path}}/tests/test_app_startup.py b/templates/extensions/usd_viewer.setup/template/{{python_module_path}}/tests/test_app_startup.py
new file mode 100644
index 0000000..30575b1
--- /dev/null
+++ b/templates/extensions/usd_viewer.setup/template/{{python_module_path}}/tests/test_app_startup.py
@@ -0,0 +1,77 @@
+# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
+#
+# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
+# property and proprietary rights in and to this material, related
+# documentation and any modifications thereto. Any use, reproduction,
+# disclosure or distribution of this material and related documentation
+# without an express license agreement from NVIDIA CORPORATION or
+# its affiliates is strictly prohibited.
+
+# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
+#
+# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
+# property and proprietary rights in and to this material, related
+# documentation and any modifications thereto. Any use, reproduction,
+# disclosure or distribution of this material and related documentation
+# without an express license agreement from NVIDIA CORPORATION or
+# its affiliates is strictly prohibited.
+
+import json
+import time
+from typing import Tuple
+
+import carb.settings
+import omni.kit.app
+from omni.kit.test import AsyncTestCase
+
+
+class TestAppStartup(AsyncTestCase):
+ def app_startup_time(self, test_id: str) -> float:
+ """Get startup time - send to nvdf"""
+ test_start_time = time.monotonic()
+ startup_time = omni.kit.app.get_app().get_time_since_start_s()
+ test_result = {"startup_time_s": startup_time}
+ print(f"App Startup time: {startup_time}")
+ return startup_time
+
+ def app_startup_warning_count(self, test_id: str) -> Tuple[int, int]:
+ """Get the count of warnings during startup - send to nvdf"""
+ test_start_time = time.monotonic()
+ warning_count = 0
+ error_count = 0
+ log_file_path = carb.settings.get_settings().get("/log/file")
+ with open(log_file_path, "r") as file:
+ for line in file:
+ if "[Warning]" in line:
+ warning_count += 1
+ elif "[Error]" in line:
+ error_count += 1
+
+ test_result = {"startup_warning_count": warning_count, "startup_error_count": error_count}
+ print(f"App Startup Warning count: {warning_count}")
+ print(f"App Startup Error count: {error_count}")
+ return warning_count, error_count
+
+ async def test_l1_app_startup_time(self):
+ """Get startup time - send to nvdf"""
+ for _ in range(60):
+ await omni.kit.app.get_app().next_update_async()
+
+ self.app_startup_time(self.id())
+ self.assertTrue(True)
+
+ async def test_l1_app_startup_warning_count(self):
+ """Get the count of warnings during startup - send to nvdf"""
+ for _ in range(60):
+ await omni.kit.app.get_app().next_update_async()
+
+ self.app_startup_warning_count(self.id())
+ self.assertTrue(True)
+
+ async def test_l1_app_startup_fsd_enabled(self):
+ """Check if Fabric Scene Delegate is enabled at startup"""
+
+ fsd_enabled: bool = carb.settings.get_settings().get("/app/useFabricSceneDelegate")
+ self.assertTrue(fsd_enabled)
\ No newline at end of file
diff --git a/templates/templates.toml b/templates/templates.toml
index b4338ef..4b9f73d 100644
--- a/templates/templates.toml
+++ b/templates/templates.toml
@@ -80,6 +80,7 @@ type = "extra"
template = "omni_usd_viewer_setup"
type = "setup"
+
[[templates."omni_usd_viewer".applications]]
template = "omni_gdn_streaming"
type = "gdn"
@@ -178,7 +179,7 @@ variables.version = "0.1.0"
[templates."omni_usd_viewer_messaging"]
-class = "ExtensionTemplate"
+class = "SetupExtensionTemplate"
name = "Omni USD Viewer Messaging"
url = "."
subpath = "extensions/usd_viewer.messaging/template"
diff --git a/tools/VERSION.md b/tools/VERSION.md
index de75f5f..b738a70 100644
--- a/tools/VERSION.md
+++ b/tools/VERSION.md
@@ -1 +1 @@
-106.0.2
\ No newline at end of file
+106.1
\ No newline at end of file
diff --git a/tools/containers/Dockerfile.j2 b/tools/containers/Dockerfile.j2
index 2e4d05b..40c491e 100644
--- a/tools/containers/Dockerfile.j2
+++ b/tools/containers/Dockerfile.j2
@@ -1,7 +1,7 @@
# This Dockerfile is used as the base for containerizing applications rendered out via repo_kit_template.
# Utilize Kit Kernel 106.0.0
-FROM nvcr.io/nvidia/omniverse/ov-kit-kernel:106.0.1-ga.126909.3a7abd1c
+FROM nvcr.io/nvidia/omniverse/ov-kit-kernel:106.1.0-ea.140981.10a4b5c0
# root user to bootstrap the container
USER root
@@ -29,9 +29,9 @@ WORKDIR /app
# Copy the startup.sh scripts
COPY --chown=ubuntu:ubuntu entrypoint.sh /entrypoint.sh
-RUN chmod +x /entrypoint.sh
+RUN chmod +x,-w /entrypoint.sh
COPY --chown=ubuntu:ubuntu entrypoint_memcached.sh /entrypoint_memcached.sh
-RUN chmod +x /entrypoint_memcached.sh
+RUN chmod +x,-w /entrypoint_memcached.sh
# Create Kit expected caching directories that we'll map a volume onto.
RUN mkdir -p /home/ubuntu/.cache/ov
diff --git a/tools/deps/kit-sdk-deps.packman.xml b/tools/deps/kit-sdk-deps.packman.xml
index 5ecee6b..5c19b04 100644
--- a/tools/deps/kit-sdk-deps.packman.xml
+++ b/tools/deps/kit-sdk-deps.packman.xml
@@ -27,4 +27,5 @@
+
diff --git a/tools/deps/kit-sdk.packman.xml b/tools/deps/kit-sdk.packman.xml
index 552a4c9..4f019d7 100644
--- a/tools/deps/kit-sdk.packman.xml
+++ b/tools/deps/kit-sdk.packman.xml
@@ -1,7 +1,4 @@
-
-
-
-
+
diff --git a/tools/deps/repo-deps.packman.xml b/tools/deps/repo-deps.packman.xml
index b73bde7..72e77df 100644
--- a/tools/deps/repo-deps.packman.xml
+++ b/tools/deps/repo-deps.packman.xml
@@ -1,20 +1,23 @@
-
+
-
+
-
+
-
+
-
+
-
+
+
+
+
diff --git a/tools/packman/bootstrap/configure.bat b/tools/packman/bootstrap/configure.bat
index afaf2fb..1511537 100755
--- a/tools/packman/bootstrap/configure.bat
+++ b/tools/packman/bootstrap/configure.bat
@@ -12,7 +12,7 @@
:: See the License for the specific language governing permissions and
:: limitations under the License.
-set PM_PACKMAN_VERSION=7.23
+set PM_PACKMAN_VERSION=7.23.2
:: Specify where packman command is rooted
set PM_INSTALL_PATH=%~dp0..
diff --git a/tools/packman/bootstrap/install_package.py b/tools/packman/bootstrap/install_package.py
index 706be4f..5baa9ec 100644
--- a/tools/packman/bootstrap/install_package.py
+++ b/tools/packman/bootstrap/install_package.py
@@ -142,7 +142,7 @@ def generate_sha256_for_file(file_path: Union[str, os.PathLike]) -> str:
def install_common_module(package_path, install_path):
- COMMON_SHA256 = "b39889fbcf49cbbc66f913f2a3a73817ec3afcf5ae3e4ba9cf9f6fd3e775aa34"
+ COMMON_SHA256 = "dc5b18d2b507f04ce560b3d3e8b6b1eeac55b8931653096378cb45337874fd33"
package_sha256 = generate_sha256_for_file(package_path)
if package_sha256 != COMMON_SHA256:
raise RuntimeError(
diff --git a/tools/packman/packman b/tools/packman/packman
index d26c28f..2577fbe 100755
--- a/tools/packman/packman
+++ b/tools/packman/packman
@@ -24,7 +24,7 @@ else
PM_CURL_SILENT="-s -S"
PM_WGET_QUIET="--quiet"
fi
-export PM_PACKMAN_VERSION=7.23
+export PM_PACKMAN_VERSION=7.23.2
# This is necessary for newer macOS
if [ `uname` == 'Darwin' ]; then
diff --git a/tools/packman/packmanconf.py b/tools/packman/packmanconf.py
index db220d2..2a2f8a5 100644
--- a/tools/packman/packmanconf.py
+++ b/tools/packman/packmanconf.py
@@ -98,7 +98,7 @@ def get_module_dir(conf_dir, packages_root: str, version: str) -> str:
tf = tempfile.NamedTemporaryFile(delete=False)
target_name = tf.name
tf.close()
- url = f"http://bootstrap.packman.nvidia.com/packman-common@{version}.zip"
+ url = f"https://bootstrap.packman.nvidia.com/packman-common@{version}.zip"
print(f"Downloading '{url}' ...")
import urllib.request
diff --git a/tools/repoman/launch.py b/tools/repoman/launch.py
index fc175ca..034ea1b 100644
--- a/tools/repoman/launch.py
+++ b/tools/repoman/launch.py
@@ -4,6 +4,7 @@
import os
import platform
import shutil
+import subprocess
import sys
from collections import defaultdict
from glob import glob
@@ -13,11 +14,10 @@
import omni.repo.man
from omni.repo.kit_template.backend import read_toml
from omni.repo.kit_template.frontend import CLIInput, Separator
-from omni.repo.man.configuration import add_config_arg
from omni.repo.man.exceptions import QuietExpectedError
from omni.repo.man.fileutils import rmtree
from omni.repo.man.guidelines import get_host_platform
-from omni.repo.man.utils import find_and_extract_package, run_process, run_process_return_output
+from omni.repo.man.utils import find_and_extract_package, process_args_to_cmd, run_process, run_process_return_output
# These dependencies come from repo_kit_template
from rich.console import Console
@@ -53,6 +53,48 @@ def _select(query: str, apps: list) -> str:
return cli_input.select(message=query, choices=apps, default=apps[0])
+def _run_process(args: List, exit_on_error=False, timeout=None, **kwargs) -> int:
+ """Run system process and wait for completion.
+
+ This was copy/pasted out of omni.repo.man.utils to work around the KeyboardInterrupt error message.
+
+ Args:
+ args (List): List of arguments.
+ exit_on_error (bool, optional): Exit if return code is non zero.
+ """
+ returncode = 0
+ message = ""
+ try:
+ logger.info(f"running process: {process_args_to_cmd(args)}")
+ # Optionally map in sys.stdin for local debugging via pdb.set_trace.
+ # Otherwise use DEVNULL, prevents weird failures on Windows.
+ stdin = subprocess.DEVNULL
+ if os.environ.get("repo_diagnostic"):
+ stdin = sys.stdin
+
+ p = subprocess.run(args, stdin=stdin, stdout=sys.stdout, stderr=subprocess.STDOUT, timeout=timeout, **kwargs)
+ returncode = p.returncode
+ except subprocess.CalledProcessError as e:
+ returncode = e.returncode
+ message = e
+ except subprocess.TimeoutExpired as e:
+ returncode = -1
+ message = e
+ except (FileNotFoundError, OSError, Exception) as e:
+ returncode = -1
+ message = str(e)
+ except KeyboardInterrupt:
+ returncode = -1
+ message = "KeyboardInterrupt"
+
+ # Do not logger.error for a KeyboardInterrupt
+ if returncode != 0 and message != "KeyboardInterrupt":
+ logger.error(f'error running: {process_args_to_cmd(args)}, code: {returncode}, message: "{message}"')
+ if exit_on_error:
+ sys.exit(returncode)
+ return returncode
+
+
def discover_kit_files(target_directory: Path) -> List:
if not target_directory.is_dir():
return []
@@ -81,9 +123,14 @@ def discover_typed_kit_files(target_directory: Path) -> Dict:
discovered_apps = defaultdict(list)
for app in glob("**/*.kit", root_dir=target_directory, recursive=True):
app_path = target_directory / app
- app_data = read_toml(app_path)
- app_type = app_data.get("template", {}).get("type", "ApplicationTemplate")
- discovered_apps[app_type].append(app_path.name)
+ try:
+ app_data = read_toml(app_path)
+ app_type = app_data.get("template", {}).get("type", "ApplicationTemplate")
+ discovered_apps[app_type].append(app_path.name)
+ except Exception as e:
+ # For now broadly catching until repo_kit_template's read_toml can instead raise a omni.repo.man.exception
+ err_msg = f"Failed to read kit file: {app_path.resolve()}. There might be a duplicate toml key: {e}"
+ _quiet_error(err_msg)
return discovered_apps
@@ -186,7 +233,10 @@ def run_selected_image(image_id: str, dev_bundle: bool, extra_args: List[str], v
if extra_args:
docker_run_cmd += extra_args
- run_process(docker_run_cmd, exit_on_error=False)
+ _ = _run_process(
+ docker_run_cmd,
+ exit_on_error=False,
+ )
def nvidia_driver_check():
@@ -381,8 +431,10 @@ def launch_kit(
if extra_args:
kit_cmd += extra_args
- ret_code = run_process(kit_cmd, exit_on_error=False)
- # TODO: do something with this ret_code
+ _ = _run_process(
+ kit_cmd,
+ exit_on_error=False,
+ )
def expand_package(package_path: str) -> Path:
@@ -451,6 +503,8 @@ def add_args(parser: argparse.ArgumentParser):
help="Enable the developer debugging extension bundle.",
)
+
+def add_package_arg(parser: argparse.ArgumentParser):
parser.add_argument(
"-p",
PACKAGE_ARG,
@@ -460,6 +514,8 @@ def add_args(parser: argparse.ArgumentParser):
help="Path to a kit app package that you want to launch",
)
+
+def add_name_arg(parser: argparse.ArgumentParser):
parser.add_argument(
"-n",
"--name",
@@ -474,25 +530,30 @@ def setup_repo_tool(parser: argparse.ArgumentParser, config: Dict) -> Optional[C
parser.description = "Simple tool to launch Kit applications"
add_args(parser)
+ add_package_arg(parser)
+ add_name_arg(parser)
# Currently unused
# tool_config = config.get("repo_launch", {})
+ # Get list of kit apps on filesystem.
+ app_names = discover_kit_files(KIT_APP_PATH)
+
+ subparsers = parser.add_subparsers()
+ for app in app_names:
+ subparser = subparsers.add_parser(app)
+ subparser.set_defaults(app_name=app)
+ # Add --dev-bundle and --container args
+ add_args(subparser)
+
def run_repo_tool(options: argparse.Namespace, config_dict: Dict):
app_name = None
config = "release"
dev_bundle = False
- extra_args = []
# Providing a kit file is optional, otherwise we present a select
app_name = options.app_name
-
- # If a kit file was selected then a config value was optionally set.
- if hasattr(options, "config"):
- config = options.config
-
- if hasattr(options, "dev_bundle"):
- dev_bundle = options.dev_bundle
+ dev_bundle = options.dev_bundle
try:
# Launching from a distributed package