Agent

Overview

Omniverse Agent is responsible for executing a set of tasks based on the capabilities of the environment on which it runs. Once launched, Agent queries Omniverse Queue for the tasks that have been submitted, and selects one for processing.

During the execution of the task, it regularly reports its progress back to Queue, so that the entire fleet of Agents and tasks can be managed from a single location.

Installation

Agent can be installed from the Omniverse Launcher, by navigating to the Exchange header menu, then selecting Agent from the Apps section of the left-hand sidebar.

After first installing Agent, the Install button will then allow you to launch it and start processing content.

Omniverse Launcher: Agent

Interface Overview

Omniverse Agent interface

#

UI Element

Action

1

Queue address

Enter the URL of the Queue the Agent should connect to in order to retrieve the list of pending tasks.

2

Test connection

Click this button to validate that the provided Queue URL can be reached by the Agent. This can be helpful in order to investigate and diagnose potential network or connectivity issues when running Agents and Queues on different environments.

3

Connect/Disconnect

After validating that the connection is successful, clicking the Connect button will have the Agent query, pick up and start processing tasks. Alternatively, clicking the Disconnect button will release the Agent from the pool.

4

Agent ID

Label displaying the unique identifier of the Agent. When running multiple Agents on the same environment, this makes it possible to connect/disconnect, or otherwise perform tasks on individual Agents via their own UI.

5

Agent task status

Label displaying the status of the task currently performed by the Agent.

6

Agent connection status

Label displaying the status of the connection from the Agent to the pool.

Usage

Once Agent is launched, it will present a UI allowing you to specify the URL of Queue to connect to in order to retrieve the list of pending tasks.

For convenience, a default URL is provided, which should match the default URL used by Queue when running both on the same machine. Should you need to connect to a Queue hosted on a different machine on the network, provide its hostname or IP address in the text field and click the Test connection to confirm the Agent is indeed able to reach the Queue.

After clicking the Connect button, Agent will retrieve the list of pending tasks from Queue, identify one that it is able to fulfill based on the capabilities of the machine and proceed to execute the task. Looking at Queue’s dashboard after a few seconds should then reflect the new state of the system, where:

  • An Agent is now available, since it was launched from the Omniverse Launcher

  • The task previously submitted has transitioned to the Running state, illustrating that processing is currently underway.

  • The Agent’s status is Active, and the ID of its active task matches the one submitted earlier.

Tip

In the case of a Queue managing multiple Agents, and where multiple machines can host Agents offering various capabilities, having a real-time overview of the state of the cluster can provide a wealth of information. As usage scales and so does the number of tasks processed in parallel, having the ability to monitor cluster activity in real time can greatly help identifying potential issues or bottlenecks.

For this reason, we encourage you to provide descriptive comments when submitting tasks, as this can help quickly identify the nature of tasks.

Omniverse Task Dashboard

Once the task has been completed, Agent will return in Idle state and the task’s status will show that it is Finished.

Navigate to the output folder of your task to see the fruit of your labor!

Note

This was only a brief overview of some of the capabilities offered by Omniverse Agents and Queues for automation, batch processing and parallelization of tasks. To learn how to create your own automation workflows, see the next section which presents an overview of the task customization features.

Have any use cases you’d like to share with others?
Want to see the creative ways in which the community has leveraged Omniverse Agent and Queue for automation?
Head to the Showcase section of our forums to join the conversation!

Defining custom jobs

Example: Using Blender to decimate meshes

Now that we’ve learned how to execute built-in tasks, let’s see how to define and run our own custom ones.

For the purposes of this example, we will be using Omniverse Agent to automate a mesh simplification task using Blender, as an illustration of typical batch operations you may find in studio or enterprise scenarios. The task will:

  1. Open an OBJ file

  2. Decimate its meshes to a given ratio

  3. Export the scene as a USD asset

While this is a simple scenario, it does demonstrate important notions:

  • No task is too small to benefit from automation.

  • Creating custom job types for Omniverse Agent is a fairly straightforward process.

  • USD is the open-source pillar of the 3D world to facilitate data exchange and interoperability. We are sure you will love working with it just as much as we do.

Blender mesh decimation script

Let’s start where most automation tasks begin: by running our task locally.

Below is our Python script which will be driving the work to do in Blender. It supports the following arguments:

  • --source: The absolute path to the OBJ file to decimate.

  • --destination: The absolute path where the resulting USD file will be saved.

  • --ratio: An optional parameter between 0.0 and 1.0, specifying the amount by which meshes should be simplified.

job.blender-decimate.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File: job.blender-decimate.py

import argparse
import sys
from typing import List

import bpy


class BlenderArgumentParser(argparse.ArgumentParser):
    """Argument parser for Python scripts running in Blender."""

    def _get_arguments_after_double_dash(self) -> List[str]:
        """
        Extract any arguments intended to be passed to Blender scripts.

        This uses the fact that Blender will ignore any command-line arguments provided after the `--` command. This
        effectively extracts any arguments following `--`, and forwards them to the default `argparse.parse_args()`
        implementation.

        Args:
            None

        Returns:
            List[str]: The command-line arguments provided to the Blender executable, and intended for the Python script.
        """
        try:
            double_dash_index = sys.argv.index("--")
            return sys.argv[double_dash_index + 1:]
        except ValueError:  # Case where no arguments were provided after `--`.
            return []

    def parse_args(self) -> argparse.Namespace:
        return super().parse_args(args=self._get_arguments_after_double_dash())


def main() -> None:
    """Decimation script for Blender."""

    # Parse the arguments provided to the script:
    parser = BlenderArgumentParser()
    parser.add_argument("--source", required=True, help="Path of the source file to decimate.")
    parser.add_argument("--destination", required=True, help="Path of the destination file to save.")
    parser.add_argument("--ratio", type=float, default=0.5, help="Mesh decimation ratio (between 0.0 and 1.0).")
    args = parser.parse_args()

    # Clear the Blender scene, effectively removing the default Cube, Light and Camera:
    bpy.ops.wm.read_factory_settings(use_empty=True)

    # Import the given OBJ file into the scene:
    bpy.ops.import_scene.obj(filepath=args.source)

    # Decimate the meshes of the scene using the provided decimation ratio.
    #
    # The decimation ratio represented the ratio of faces to keep from the original mesh:
    #   * At 1.0, the mesh remains unchanged.
    #   * At 0.5, edges have been collapsed in such a way that half the number of faces are retained.
    #   * At 0.0, all faces have been removed.
    for object in bpy.data.objects:
        if object.type == "MESH":
            modifier = object.modifiers.new(name="DecimateModifier", type="DECIMATE")
            modifier.ratio = args.ratio

    # Export the scene to the given USD file:
    bpy.ops.wm.usd_export(filepath=args.destination)


if __name__ == "__main__":
    main()

Testing our mesh decimation script

Following best practices, this task can be executed locally from the command-line. This makes it possible to develop, validate and debug it to allow fast iteration cycles, and also promotes reusability of components.

Before integrating our task, let’s first make sure it runs as expected in standalone mode by running it from the command-line:

1
2
3
4
5
6
7
./blender \
    --background \
    --python path/to/job.blender-decimate.py \
    -- \
    --source path/to/source.obj \
    --destination path/to/destination.usd \
    --ratio 0.5

Using the mandatory Suzanne monkey as an OBJ source file, we should see the following result, with the original model on the left and its simplified version on the right:

Mesh decimation comparison

Integrating the job with Omniverse Agent and Queue

Now that we know that our task works as expected, we will integrate it in Omniverse Agent so anyone can submit tasks and benefit from our work.

Jobs are defined in KIT files, using the TOML syntax. Here is an example of the schema defining jobs, which Agents will use to launch and process our task:

job.blender-decimate.kit
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File: job.blender-decimate.kit

[package]
title = "Blender decimation task"
description = "Decimate a mesh using Blender and export it as a USD asset"
version = "1.0.0"
category = "jobs"
authors = ["Omniverse Team"]
readme = "Omniverse decimation task"
keywords = ["job"]

# Schema for the Blender mesh decimation task.
[job.blender-decimate]
job_type = "base"
name = "blender-decimate"
log_to_stdout = true # Capture information from `stdout` and `stderr` for the task's logs
# Since the `command` property supports standard Kit token resolution, we're using `${exe_ext}` so the executable resolves to `blender.exe` on Windows and
# `blender` on Linux. For the environment where Agent will be running, we have the choice of either:
#   * Making sure the `blender` executable can be found in the `PATH` or `HOME` environment variable.
#   * Providing the full path to the Blender executable.
command = "blender${exe_ext}"
# The command-line arguments listed in the `args` array will be supplied to the `command` for each task of this type. This has 2 main benefits:
#    1. It prevents having to provide these arguments for each task.
#    2. Makes it possible to seamlessly roll out updates to the "job.blender-decimate.py" script on the Agent, without the need for clients to also
#       update their APIs. For example, Agents could update to a "job.blender-decimate.v2.py" and clients could still submit tasks using the same
#       `allowed_args` listed below.
args = [
    "--background",
    "--python", "path/to/job.blender-decimate.py",
    "--", # In the case of Blender, this additional `--` token is required to supply arguments to the Python script itself.
]

# The `allowed_args` are appended to the "static" `args` listed above for each task. Effectively, this means that the resulting command-line executed by
# Agents takes the form of:
#    $ <command> <args> <allowed_args>
#
# In the case of our example, this resolves to:
#    $ blender \
#       --background --python path/to/job.blender-decimate.py -- \
#       --source <source_obj> --destination <destination_usd> --ratio <ratio=0.5>
#
# Each `allowed_args` supports 2 properties:
#    * `arg`: The argument token as it will appear on the command-line.
#    * `default`: A default value for the argument, if none is supplied by clients.
#
# Note that `allowed_args` defines the explicit list of arguments which can be submitted by clients, and that any additional arbitrary arguments will be
# ignored. For security reasons, you would not want external clients to be able to supply extra arguments, which could otherwise allow arbitrary external
# code execution. In our example, this prevents Users from submitting tasks to Blender with the `--python-expr <script>` argument, which executes the
# provided text string as a Python script.
[job.blender-decimate.allowed_args]
source      = { arg = "--source",      default = "" }
destination = { arg = "--destination", default = "" }
ratio       = { arg = "--ratio",       default = "0.5" }

All that is now left to do is to inform our Agents where to find this job.

This can be done by adding the directory where we saved our job’s KIT file to the list of folders from where Agent will scan for content:

  1. From the Omniverse Launcher, launch an instance of Agent.

  2. From the UI, under the Agent settings section, click the “+” icon on the right-hand side of the Paths list to add a new location from where Agent will start searching for KIT files containing job definitions.

  3. Double click the newly-added row, and paste the absolute path of the directory where the KIT file is located.

  4. Click the Apply Settings button at the bottom of the Agent Settings section.

Mesh decimation comparison

Success! We can see that our job definition was properly resolved by our Agent. Should we want to make further changes to the definition of our job, we could also use the UI of the Agent itself to make edits, then clicking the Save Job Settings button after scrolling to the bottom of the screen to save our settings to the KIT file once.

Alternatively, you could also inform Agent where to find your custom KIT job definition files by mapping folders on your environment to the --exts/omni.services.farm.agent.operator/job_store_args/job_directories setting:

1
2
3
./kit \
    path/to/omni.farm.agent.kit \
    --exts/omni.services.farm.agent.operator/job_store_args/job_directories/0=directory/where/to/find/the/blender/kit/file

Let’s now complete our project by submitting a task:

1
2
3
4
curl -X POST "http://localhost:8222/farm/management/tasks/submit" \
    --header "Accept: application/json" \
    --header "Content-Type: application/json" \
    --data '{"user":"my-user-id","task_type":"blender-decimate","task_args":{"source":"path/to/suzanne.obj","destination":"path/to/suzanne-decimated.usd","ratio":0.2},"task_comment":"My first decimation job!"}'

The only thing left to do now is to wait a few seconds for an Agent to pick up the task!

Note

Astute readers may see how this simple example could be further improved.

For example, you may have noticed that it might be more performant for Agents to execute multiple tasks at the same time. Indeed, since an Agent is already launched, it might be more efficient to have it process a number of conversions at the same time, rather than only process a single file.

A few ideas come to mind on this topic:

  • Perhaps we could edit our script to instead support supplying the path to a source folder where to find and process all OBJ files, instead of a single file.

  • Perhaps we could even provide our script with a glob wildcard pattern to find source files of multiple formats, and export them all as USD.

Where to go from here

To make this feature accessible to others, we could now bring it to the next level by:

  • Writing a script or plugin to submit tasks directly from our favorite DCC. This would make it possible for artists to submit tasks once they are done modelling new revisions of assets.

  • Alternatively, we could also submit tasks directly from our production tracking tool, and have the system automatically submit tasks once they have been reviewed and approved.

Conclusion

With this ability to scale, distribute and share tasks across multiple environments, there are no limits to what we could do:

  • A tool to render turntable-style preview of assets once they are submitted to the production tracking system.

  • Making sure all resources are checked in, and that no texture files are missing from files before.

  • Validating that asset nomenclature conforms to studio requirements.

  • Updating fluids or animation caches.

  • Training machine learning on new datasets.

  • etc.

Want to showcase your creations, or see what others from the community have created?
Join the Showcase section of our forums to join the conversation!