Tutorial 21 - Adding Bundled Attributes

Sometimes instead of simply copying data from an input or input bundle into an output bundle you might want to construct a bundle from some other criteria. For example a bundle construction node could take in an array of names and attribute types and output a bundle consisting of those attributes with some default values.

The bundle accessor provides a simple method that can accomplish this task. Adding a new attribute is as simple as providing those two values to the bundle for every attribute you wish to add.

There is also a complementary function to remove named bundle attributes.

OgnTutorialBundleAddAttributes.ogn

The ogn file shows the implementation of a node named “omni.graph.tutorials.BundleData”, which has one input bundle and one output bundle.

 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
{
    "BundleAddAttributes": {
        "description": [
            "This is a tutorial node. It exercises functionality for adding and removing attributes on",
            "output bundles."
        ],
        "version": 1,
        "categories": "tutorials",
        "uiName": "Tutorial Node: Bundle Add Attributes",
        "inputs": {
            "typesToAdd": {
                "type": "token[]",
                "description": [
                    "List of type descriptions to add to the bundle. The strings in this list correspond to the",
                    "strings that represent the attribute types in the .ogn file (e.g. float[3][], colord[3], bool"
                ],
                "uiName": "Attribute Types To Add"
            },
            "addedAttributeNames": {
                "type": "token[]",
                "description": [
                    "Names for the attribute types to be added. The size of this array must match the size",
                    "of the 'typesToAdd' array to be legal."
                ]
            },
            "removedAttributeNames": {
                "type": "token[]",
                "description": "Names for the attribute types to be removed. Non-existent attributes will be ignored."
            },
            "useBatchedAPI": {
                "type": "bool",
                "description": "Controls whether or not to used batched APIS for adding/removing attributes"
            }
        },
        "outputs": {
            "bundle": {
                "type": "bundle",
                "description": ["This is the bundle with all attributes added by compute."],
                "uiName": "Constructed Bundle"
             }
        }
    }
}

OgnTutorialBundleAddAttributes.cpp

The cpp file contains the implementation of the compute method. It accesses the attribute descriptions on the inputs and creates a bundle with attributes matching those descriptions as its output.

 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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto.  Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#include <OgnTutorialBundleAddAttributesDatabase.h>
#include <omni/graph/core/IAttributeType.h>

using omni::graph::core::Type;


class OgnTutorialBundleAddAttributes
{
public:
    static bool compute(OgnTutorialBundleAddAttributesDatabase& db)
    {
        const auto& attributeTypeNames = db.inputs.typesToAdd();
        const auto& attributeNames = db.inputs.addedAttributeNames();
        const auto& useBatchedAPI = db.inputs.useBatchedAPI();
        auto& outputBundle = db.outputs.bundle();

        // The usual error checking. Being diligent about checking the data ensures you will have an easier time
        // debugging the graph if anything goes wrong.
        if (attributeTypeNames.size() != attributeNames.size())
        {
            db.logWarning(
                "Number of attribute types (%zu) does not match number of attribute names (%zu)",
                attributeTypeNames.size(), attributeNames.size()
            );
            return false;
        }

        // Make sure the bundle is starting from empty
        outputBundle.clear();

        if (useBatchedAPI)
        {
            //
            // unfortunately we need to build a vector of the types
            //
            auto typeNameIt = std::begin(attributeTypeNames);
            std::vector<Type> types;
            types.reserve(attributeTypeNames.size());
            for (; typeNameIt != std::end(attributeTypeNames); ++typeNameIt)
            {
                auto typeName = *typeNameIt;
                types.emplace_back(db.typeFromName(typeName));
            }
            outputBundle.addAttributes(attributeTypeNames.size(), attributeNames.data(), types.data());

            // Remove attributes from the bundle that were already added. This is a somewhat contrived operation that
            // allows testing of both adding and removal within a simple environment.
            if (db.inputs.removedAttributeNames().size()) {
                outputBundle.removeAttributes(db.inputs.removedAttributeNames().size(), db.inputs.removedAttributeNames().data());
            }
        }
        else
        {
            // Since the two arrays are the same size a dual loop can be used to walk them in pairs
            auto typeNameIt = std::begin(attributeTypeNames);
            auto attributeNameIt = std::begin(attributeNames);
            for (; typeNameIt != std::end(attributeTypeNames) && attributeNameIt != std::end(attributeNames); ++typeNameIt, ++attributeNameIt)
            {
                auto typeName = *typeNameIt;
                auto attributeName = *attributeNameIt;
                auto typeNameString = db.tokenToString(typeName);
                Type newType = db.typeFromName(typeName);
                // Ignore the output since for this example there will not be any values set on the new attribute
                (void)outputBundle.addAttribute(attributeName, newType);
            }

            // Remove attributes from the bundle that were already added. This is a somewhat contrived operation that
            // allows testing of both adding and removal within a simple environment.
            for (const auto& toRemove : db.inputs.removedAttributeNames())
            {
                outputBundle.removeAttribute(toRemove);
            }
        }

        return true;
    }
};

REGISTER_OGN_NODE()

OgnTutorialBundleAddAttributesPy.py

The py file contains the same algorithm as the C++ node, with only the implementation language being different.

 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
"""
Implementation of the Python node adding attributes with a given description to an output bundle.
"""
import omni.graph.core as og


class OgnTutorialBundleAddAttributesPy:
    """Exercise the bundled data types through a Python OmniGraph node"""

    @staticmethod
    def compute(db) -> bool:
        """Implements the same algorithm as the C++ node OgnTutorialBundleAddAttributes.cpp using the Python
        bindings to the bundle method
        """
        # Start with an empty output bundle.
        output_bundle = db.outputs.bundle
        output_bundle.clear()

        if db.inputs.useBatchedAPI:
            attr_types = [og.AttributeType.type_from_ogn_type_name(type_name) for type_name in db.inputs.typesToAdd]
            output_bundle.add_attributes(attr_types, db.inputs.addedAttributeNames)

            output_bundle.remove_attributes(db.inputs.removedAttributeNames)

        else:
            for attribute_type_name, attribute_name in zip(db.inputs.typesToAdd, db.inputs.addedAttributeNames):
                attribute_type = og.AttributeType.type_from_ogn_type_name(attribute_type_name)
                output_bundle.insert((attribute_type, attribute_name))

            # Remove attributes from the bundle that were already added. This is a somewhat contrived operation that
            # allows testing of both adding and removal within a simple environment.
            for attribute_name in db.inputs.removedAttributeNames:
                output_bundle.remove(attribute_name)

        return True