Extension: omni.graph.template.mixed-2.20.0

Documentation Generated: Oct 24, 2024

Changelog

Overview

This is the gold standard template for creating a Kit extension that contains a mixture of Python and C++ OmniGraph nodes.

The Files

To use this template first copy the entire directory into a location that is visible to your build, such as source/extensions. The copy will have this directory structure. The highlighted lines should be renamed to match your extension, or removed if you do not want to use them.

omni.graph.template.mixed/
    bindings/
        Bindings.cpp
    config/
        extension.toml
    data/
        icon.svg
        preview.png
    docs/
        CHANGELOG.md
        directory.txt
        Overview.md
        README.md
    nodes/
        OgnTemplateNodeMixedCpp.cpp
        OgnTemplateNodeMixedCpp.ogn
    plugins/
        Module.cpp
        OmniGraphTemplateMixed.h
    premake5.lua
    python/
        __init__.py
        _impl/
            __init__.py
            extension.py
        nodes/
            OgnTemplateNodePy.ogn
            OgnTemplateNodePy.py
        tests/
            __init__.py
            test_api.py
            test_omni_graph_template_python.py

The convention of having implementation details of a module in the _impl/ subdirectory is to make it clear to the user that they should not be directly accessing anything in that directory, only what is exposed in the __init__.py.

The Build File

Kit normally uses premake for building so this example shows how to use the template premake5.lua file to customize your build. By default the build file is set up to correspond to the directory structure shown above. By using this standard layout the utility functions can do most of the work for you.

  1-- --------------------------------------------------------------------------------------------------------------------
  2-- Build file for the build tools used by the OmniGraph Python mixed extension. These are tools required in order to
  3-- run the build on that extension, and all extensions dependent on it.
  4
  5-- --------------------------------------------------------------------------------------------------------------------
  6-- This sets up a shared extension configuration, used by most Kit extensions.
  7local ext = get_current_extension_info()
  8
  9-- --------------------------------------------------------------------------------------------------------------------
 10-- Set up a variable containing standard configuration information for projects containing OGN files
 11local ogn = get_ogn_project_information(ext, "omni/graph/template/mixed")
 12
 13-- --------------------------------------------------------------------------------------------------------------------
 14-- Put this project into the "omnigraph" IDE group
 15ext.group = "omnigraph"
 16
 17-- --------------------------------------------------------------------------------------------------------------------
 18-- Set up the basic shared project information first
 19project_ext( ext )
 20
 21-- --------------------------------------------------------------------------------------------------------------------
 22-- ONI binding generation. The code generation is a separate project to ensure it happens before any of the
 23-- generated code is used. See the full documentation for OmniGraph Native Interfaces online at
 24-- https://docs.omniverse.nvidia.com/kit/docs/carbonite/latest/docs/OmniverseNativeInterfaces.html
 25--
 26project_ext_omnibind(ext, ext.id..".interfaces")
 27    omnibind {
 28        {
 29            -- This file is written by the developer, containing the basic ONI definition of the interface
 30            file = "%{root}/include/omni/graph/template/mixed/IOmniGraphTemplateMixed.h",
 31            -- These two files are generated by omni.bind from the definition and can be used in the C++ and Python
 32            -- bindings code respectively.
 33            api = "%{root}/include/omni/graph/template/mixed/IOmniGraphTemplateMixed.gen.h",
 34            py = "%{root}/include/omni/graph/template/mixed/PyIOmniGraphTemplateMixed.gen.h"
 35        }
 36    }
 37    dependson { "omni.core.interfaces" }
 38
 39-- --------------------------------------------------------------------------------------------------------------------
 40-- Define a build project to process the ogn files to create the generated code that will be used by the node
 41-- implementations. The (optional) "toc" value points to the directory where the table of contents with the OmniGraph
 42-- nodes in this extension will be generated. Omit it if you will be generating your own table of contents.
 43project_ext_ogn( ext, ogn, { toc="docs/Overview.md" } )
 44
 45-- --------------------------------------------------------------------------------------------------------------------
 46-- The main plugin project is what implements the nodes and extension interface
 47project_ext_plugin( ext, ogn.plugin_project )
 48
 49    -- These lines add the files in the project to the IDE where the first argument is the group and the second
 50    -- is the set of files in the source tree that are populated into that group.
 51    add_files("impl", ogn.plugin_path)
 52    add_files("nodes", ogn.nodes_path)
 53    add_files("config", "config")
 54    add_files("docs", ogn.docs_path)
 55
 56    -- omni.bind must run on the interfaces first so that the generated interfaces exist for the build
 57    dependson { ext.id..".interfaces", ogn.python_project }
 58
 59    -- Add the standard dependencies all OGN projects have. The second parameter is normally omitted for C++ nodes
 60    -- as hot reload of C++ definitions is not yet supported.
 61    add_ogn_dependencies(ogn)
 62
 63    -- This optional line adds support for CUDA (.cu) files in your project. Only include it if you are building nodes
 64    -- that will run on the GPU and implement CUDA code to do so. Your deps/ directory should contain a file with a
 65    -- cuda dependency that looks like the following to access the cuda library:
 66    -- <dependency name="cuda" linkPath="../_build/target-deps/cuda">
 67    --   <package name="cuda" version="11.8.0_520.61-d8963068-${platform}" platforms="linux-x86_64"/>
 68    --   <package name="cuda" version="11.8.0_520.61-abe3d9d7-${platform}" platforms="linux-aarch64"/>
 69    --   <package name="cuda" version="11.8.0_522.06-abe3d9d7-${platform}" platforms="windows-x86_64"/>
 70    -- </dependency>
 71    -- add_cuda_build_support()
 72
 73-- --------------------------------------------------------------------------------------------------------------------
 74-- Build project responsible for generating the Python nodes and installing them and any scripts into the build tree.
 75-- This includes support for Python bindings of the C++ interface ABI.
 76project_ext_bindings {
 77    ext = ext,  -- Shared project definitions
 78    project_name = ogn.python_project,  -- Name of this project (omni.graph.template.mixed.python)
 79    module = "_o_g_t_m",  -- Name of the bindings module (the normal ogn.bindings_module is too long for Windows)
 80    src = ogn.bindings_path,  -- Location of the bindings source files (bindings/)
 81    target_subdir = ogn.import_path,  -- Subdirectory in the build tree for Python files (omni/graph/template/mixed/)
 82    iface_project = ext.id..".interfaces" -- Project with omni.bind interfaces - ensures they generate first
 83}
 84
 85    -- These lines add the files in the project to the IDE where the first argument is the group and the second
 86    -- is the set of files in the source tree that are populated into that group.
 87    add_files("python", "*.py")
 88    add_files("python/_impl", "python/_impl/**.py")
 89    add_files("python/nodes", "python/nodes")
 90    add_files("python/tests", "python/tests")
 91    add_files("docs", "docs")
 92    add_files("data", "data")
 93
 94    -- Add the standard dependencies all OGN projects have. The second parameter is a table of all directories
 95    -- containing Python nodes. Here there is only one.
 96    add_ogn_dependencies(ogn, {"python/nodes"})
 97
 98    -- Copy the init script directly into the build tree. This is required because the build will create an ogn/
 99    -- subdirectory in the Python module so only the subdirectories can be linked.
100    repo_build.prebuild_copy {
101        { "python/__init__.py", ogn.python_target_path },
102    }
103
104    -- Linking directories allows them to hot reload when files are modified in the source tree.
105	-- Docs are linked to get the README into the extension window.
106    -- Data contains the images used by the extension configuration preview.
107    -- The "nodes/" directory does not have to be mentioned here as it will be handled by add_ogn_dependencies() above.
108    repo_build.prebuild_link {
109        { "docs", ext.target_dir.."/docs" },
110        { "data", ext.target_dir.."/data" },
111        { "python/tests", ogn.python_tests_target_path },
112        { "python/_impl", ogn.python_target_path.."/_impl" },
113    }
114
115-- --------------------------------------------------------------------------------------------------------------------
116-- With the above copy/link operations this is what the source and build trees will look like
117--
118-- SOURCE                          BUILD
119-- omni.graph.template.mixed/      omni.graph.template.mixed/
120--   bindings/                       config@ -> SOURCE/config
121--   config/                         data@ -> SOURCE/data
122--   data/                           docs@ -> SOURCE/docs
123--   docs/                           ogn/  (generated by the build)
124--   nodes/                          omni/
125--   plugins/                          graph/
126--   python/                             template/
127--     __init__.py                         mixed/
128--     _impl/                                python/
129--     nodes/                                  __init__.py  (copied from SOURCE/python)
130--                                            _impl@ -> SOURCE/python/_impl
131--                                             nodes@ -> SOURCE/python/nodes
132--                                             tests@ -> SOURCE/python/tests
133--                                             ogn/  (generated by the build)

By convention the installed Python files are structured in a directory tree that matches a namespace corresponding to the extension name, in this case omni/graph/template/mixed/, which corresponds to the extension name omni.graph.template.mixed. You’ll want to modify this to match your own extension’s name. Changing the first highlighted line is all you have to do to make that happen.

The omnibind section is an example of how you would expose an Omniverse Native Interface (ONI) to the build so that you can define interfaces that can be used by other extensions.

The Configuration

Every extension requires a config/extension.toml file with metadata describing the extension to the extension management system. Below is the annotated version of this file, where the highlighted lines are the ones you should change to match your own extension.

 1# Main extension description values
 2[package]
 3# The current extension version number - uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
 4version = "2.20.0"
 5# The title of the extension that will appear in the extension window
 6title = "OmniGraph Mixed C++ and Python Template"
 7# Longer description of the extension
 8description = "Templates for setting up an extension containing both OmniGraph Python and C++ nodes."
 9# Authors/owners of the extension - usually an email by convention
10authors = ["NVIDIA <no-reply@nvidia.com>"]
11# Category under which the extension will be organized
12category = "Graph"
13# Location of the main README file describing the extension for extension developers
14readme = "docs/README.md"
15# Location of the main CHANGELOG file describing the modifications made to the extension during development
16changelog = "docs/CHANGELOG.md"
17# Location of the repository in which the extension's source can be found
18repository = "https://gitlab-master.nvidia.com/omniverse/kit-extensions/kit-omnigraph"
19# Keywords to help identify the extension when searching
20keywords = ["kit", "omnigraph", "nodes", "python"]
21# Image that shows up in the preview pane of the extension window
22preview_image = "data/preview.png"
23# Image that shows up in the navigation pane of the extension window - can be a .png, .jpg, or .svg
24icon = "data/icon.svg"
25# Specifying this ensures that the extension is always published for the matching version of the Kit SDK
26writeTarget.kit = true
27# Specify the minimum level for support
28support_level = "Sample"
29
30# This extension has a compiled C++ project and so requires this declaration that it exists
31[[native.plugin]]
32path = "bin/*.plugin"
33recursive = false
34
35# Main module for the Python interface. This is how the module will be imported.
36[[python.module]]
37name = "omni.graph.template.mixed"
38
39# Watch the .ogn files for hot reloading. Only useful during development as after delivery files cannot be changed.
40[fswatcher.patterns]
41include = ["*.ogn", "*.py"]
42exclude = ["Ogn*Database.py"]
43
44# Other extensions that need to load in order for this one to work
45[dependencies]
46"omni.graph" = {}       # For basic functionality
47"omni.graph.tools" = {} # For node generation
48
49# Main pages published as part of documentation. (Only if you build and publish your documentation.)
50[documentation]
51pages = [
52    "docs/Overview.md",
53    "docs/CHANGELOG.md",
54]
55# Since this module publishes an interface the documentation for it should also be included in the output.
56# The paths to the include files documenting the interface are relative to the directory this file lives in.
57cpp_api = [
58    "../../../include/omni/graph/template/mixed/IOmniGraphTemplateMixed.h",
59]
60
61# Some extensions are only needed when writing tests, including those automatically generated from a .ogn file.
62# Having special test-only dependencies lets you avoid introducing a dependency on the test environment when only
63# using the functionality.
64[[test]]
65dependencies = [
66    "omni.kit.test"  # Brings in the Kit testing framework
67]

Contained in this file are references to the icon file in data/icon.svg and the preview image in data/preview.png which control how your extension appears in the extension manager. You will want to customize those.

The Plugin Module

Every C++ extension requires some standard code setup to register and deregister the node types at the proper time. The minimum requirements for the Carbonite wrappers that implement this are containing in the file plugins/Module.cpp.

 1// SPDX-FileCopyrightText: Copyright (c) 2023-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 2// SPDX-License-Identifier: LicenseRef-NvidiaProprietary
 3//
 4// NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
 5// property and proprietary rights in and to this material, related
 6// documentation and any modifications thereto. Any use, reproduction,
 7// disclosure or distribution of this material and related documentation
 8// without an express license agreement from NVIDIA CORPORATION or
 9// its affiliates is strictly prohibited.
10
11// ==============================================================================================================
12//
13// This file contains mostly boilerplate code required to register the interfaces with Carbonite.
14//
15// See the full documentation for OmniGraph Native Interfaces online at
16// https://docs.omniverse.nvidia.com/kit/docs/carbonite/latest/docs/OmniverseNativeInterfaces.html
17//
18// ==============================================================================================================
19
20
21#include "OmniGraphTemplateMixed.h"
22
23#include <omni/core/ModuleInfo.h>
24#include <omni/core/Omni.h>
25#include <omni/graph/core/ogn/Registration.h>
26#include <omni/graph/template/mixed/IOmniGraphTemplateMixed.h>
27
28// These are the most common interfaces that will be used by nodes. Others that are used within the extension but
29// not registered here will issue a warning and can be added.
30OMNI_PLUGIN_IMPL_DEPS(omni::graph::core::IGraphRegistry, omni::fabric::IToken)
31
32OMNI_MODULE_GLOBALS("omni.graph.template.mixed.plugin", "OmniGraph Template With Mixed Nodes");
33
34// This declaration is required in order for registration of C++ OmniGraph nodes to work
35DECLARE_OGN_NODES();
36
37namespace
38{
39using namespace omni::graph::template_mixed;
40
41omni::core::Result onLoad(const omni::core::InterfaceImplementation** out, uint32_t* outCount)
42{
43    // clang-format off
44    static const char* omniGraphTemplateMixedImplemented[] = { "omni.graph.template.mixed.IOmniGraphTemplateMixed" };
45
46    static omni::InterfaceImplementation impls[] =
47    {
48        {
49            "nv.OmniGraphTemplateMixedImpl",
50            []()
51            {
52                return static_cast<omni::IObject*>(new OmniGraphTemplateMixed());
53            },
54            1, // version
55            omniGraphTemplateMixedImplemented, CARB_COUNTOF32(omniGraphTemplateMixedImplemented)
56        },
57    };
58    // clang-format on
59
60    *out = impls;
61    *outCount = CARB_COUNTOF32(impls);
62
63    return omni::core::kResultSuccess;
64}
65
66void onStarted()
67{
68    // Macro required to register all of the C++ OmniGraph nodes in the extension
69    INITIALIZE_OGN_NODES()
70}
71
72bool onCanUnload()
73{
74    return true;
75}
76
77void onUnload()
78{
79    // Macro required to deregister all of the C++ OmniGraph nodes in the extension
80    RELEASE_OGN_NODES()
81}
82
83} // end of anonymous namespace
84
85// Hook up the above functions to the module to be called at the right times
86OMNI_MODULE_API omni::Result omniModuleGetExports(omni::ModuleExports* exports)
87{
88    OMNI_MODULE_SET_EXPORTS(exports);
89    OMNI_MODULE_ON_MODULE_LOAD(exports, onLoad);
90    OMNI_MODULE_ON_MODULE_STARTED(exports, onStarted);
91    OMNI_MODULE_ON_MODULE_CAN_UNLOAD(exports, onCanUnload);
92    OMNI_MODULE_ON_MODULE_UNLOAD(exports, onUnload);
93    OMNI_MODULE_GET_MODULE_DEPENDENCIES(exports, omniGetDependencies);
94
95    return omni::kResultSuccess;
96}

The first highlighted line shows where you customize the extension plugin name to match your own. The macros ending in _OGN_NODES set up the OmniGraph node type registration and deregistration process. Without these lines your node types will not be known to OmniGraph and will not be available in any of the editors.

The code in the onLoad method is what is required to register your interface definition with Carbonite. If you do not implement an interface then this method can be omitted.

Bindings

If you have an interface set up then you will also want to expose it to Python. Kit uses the pybind11 library for exposing Python bindings of C++ classes. This template shows how the bindings that are automatically generated by ONI can be added to your extension’s Python module.

 1// SPDX-FileCopyrightText: Copyright (c) 2023-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 2// SPDX-License-Identifier: LicenseRef-NvidiaProprietary
 3//
 4// NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
 5// property and proprietary rights in and to this material, related
 6// documentation and any modifications thereto. Any use, reproduction,
 7// disclosure or distribution of this material and related documentation
 8// without an express license agreement from NVIDIA CORPORATION or
 9// its affiliates is strictly prohibited.
10
11// ==============================================================================================================
12//
13// This file provides the bindings of the C++ ABI to the Python interface equivalent. Anything can be added to the
14// Python interface here using the pybind11 syntax. The standard binding functions generated by omni.bind will
15// add any bindings that were automatically generated from the ONI definitions.
16//
17// ==============================================================================================================
18
19#include <omni/core/Omni.h>
20#include <omni/graph/template/mixed/IOmniGraphTemplateMixed.h>
21#include <omni/graph/template/mixed/PyIOmniGraphTemplateMixed.gen.h> // generated file
22#include <omni/python/PyBind.h>
23
24OMNI_PYTHON_GLOBALS("omni.graph.template.mixed-pyd", "Python bindings for omni::graph::template_mixed");
25
26PYBIND11_MODULE(_o_g_t_m, m)
27{
28    // This function is defined by PyIOmniGraphTemplateMixed.gen.h and was generated by omni.bind.
29    // Every function in the interface is enabled for Python but if there were any that were not, usually due to
30    // multiple return values or return values that are "out" function arguments, then you can add manual bindings
31    // for them here by adding ".def()" calls to the return value of this function.
32    bindIOmniGraphTemplateMixed(m);
33}

Notice how the names of the libraries and modules correspond to the ones you defined in the premake5.lua file. The others, such as bindIOmniGraphTemplateMixed, follow an obvious naming pattern.

Documentation

Everything in the docs/ subdirectory is considered documentation for the extension.

  • README.md The contents of this file appear in the extension manager window so you will want to customize it. The location of this file is configured in the extension.toml file as the readme value.

  • CHANGELOG.md It is good practice to keep track of changes to your extension so that users know what is available. The location of this file is configured in the extension.toml file as the changelog value, and as an entry in the [documentation] pages.

  • Overview.md This contains the main documentation page for the extension. It can stand alone or reference an arbitrarily complex set of files, images, and videos that document use of the extension. The toctree reference at the bottom of the file contains at least GeneratedNodeDocumentation/, which creates links to all of the documentation that is automatically generated for your nodes. The location of this file is configured in the extension.toml file in the [documentation] pages section.

  • directory.txt This file can be deleted as it is specific to these instructions.

The Node Type Definitions

You define a new node type using two files, examples of which are in the nodes/ and the python/nodes subdirectories. Although they do not have to be separated this way it is cleaner to do so. The Python implementations must be part of the build directory since they are used at runtime to define the nodes, whereas the C++ implementations are only used at build time and do not need to be part of the shipped extension. (The plugin library will contain everything the node type needs.)

Tailor the definition of your node types for your computations. Start with the OmniGraph User Guide for information on how to configure your own definitions.

Tests

While completely optional it’s always a good idea to add a few tests for your node to ensure that it works as you intend it and continues to work when you make changes to it. Automated tests will be generated for each of your node type definitions to exercise basic functionality. What you want to write here are more complex tests that use your node types in more complex graphs.

The sample tests in the tests/ subdirectory show you how you can integrate with the Kit testing framework to easily run tests on nodes built from your node type definition.

That’s all there is to creating an extension with both Python and C++ node types! You can now open your app, enable the new extension, and your sample node types will be available to use within OmniGraph.

OmniGraph Nodes In This Extension