Houdini 20.5 Unreal

How-to instantiate HDA using the API

How-to guide to Instantiate HDA using the Public API

On this page

This tutorial covers a few functionalities of the Public API by:

For a list of commands available, see Public API Documentation.

Houdini Public API Instance

To get the UHoudiniPublicAPI instance, fetch it via its associated blueprint function library: UHoudiniPublicAPIBlueprintLib.

The Public API in the plugin is in an Editor module so it can only be accessed from Blueprints that are editor-only. When you create the Blueprint you must choose an editor-only base class such as EditorUtilityActor or EditorUtilityWidget (for UI widgets) for the Blueprint to be editor-only.

Warning

Existing Blueprint can be re-parented to an editor-only Blueprint when editing the Blueprint. However, re-parenting can lead to problems or data loss if the new parent class isn’t compatible with the old parent class.

See Unreal’s Blueprint scripting documentation for more information

In C++:

#include "HoudiniPublicAPIBlueprintLib.h"
#include "HoudiniPublicAPI.h"

UHoudiniPublicAPI* const API = UHoudiniPublicAPIBlueprintLib::GetAPI();

In Blueprints:

In Python:

import unreal

api = unreal.HoudiniPublicAPIBlueprintLib.get_api()    

Start Session

With the API instance setup, check the status of the Houdini Engine session and start the session (if required).

In C++:

if (!API->IsSessionValid())
    API->CreateSession();

In Blueprints:

In Python:

if not api.is_session_valid():
    api.create_session()  

Instantiating HDAs

You can instantiate an HDA in the world. This uses one of the example HDAs included with the plugin: copy_to_curve_1_0.

In C++:

// Load our HDA uasset
UHoudiniAsset* const ExampleHDA = Cast<UHoudiniAsset>(StaticLoadObject(UHoudiniAsset::StaticClass(), nullptr, TEXT("/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0")));
// Create an API wrapper instance for instantiating the HDA and interacting with it
UHoudiniPublicAPIAssetWrapper* const Wrapper = API->InstantiateAsset(ExampleHDA, FTransform::Identity)

In Blueprints:

In Python:

# Load our HDA uasset
example_hda = unreal.load_object(None, '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0')
# Create an API wrapper instance for instantiating the HDA and interacting with it
wrapper = api.instantiate_asset(example_hda, instantiate_at=unreal.Transform())

The InstantiateAsset function returns an API wrapper class (UHoudiniPublicAPIAssetWrapper) to interact with the instantiated HDA. An example is to set parameter values or assign inputs.

Asset Wrapper - Delegates

In many instances, the plugin interacts with Houdini Engine asynchrounsly. This means you need to bind to a delegate to receive a callback after an operation has completed.

Once the parameter interface is ready but before the first cook, bind OnPreInstantiationDelegate to set parameters.

Note

This is the earliest time to set parameters but not necessary. You can also instantiate the HDA, cook, set the parameters, and have it recook. If the HDA takes a long time to cook, it might be better to use a few delegates efficiently to avoid expensive recooks.

Using the class ACurveInputExample, you can then bind some of its functions to the delegates to set parameters and/or inputs.

In C++:

void ACurveInputExample::RunCurveInputExample_Implementation()
{
    // Get the API instance
    UHoudiniPublicAPI* const API = UHoudiniPublicAPIBlueprintLib::GetAPI();
    // Ensure we have a running session
    if (!API->IsSessionValid())
        API->CreateSession();
    // Load our HDA uasset
    UHoudiniAsset* const ExampleHDA = Cast<UHoudiniAsset>(StaticLoadObject(UHoudiniAsset::StaticClass(), nullptr, TEXT("/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0")));
    // Create an API wrapper instance for instantiating the HDA and interacting with it
    AssetWrapper = API->InstantiateAsset(ExampleHDA, FTransform::Identity);
    if (IsValid(AssetWrapper))
    {
        // Pre-instantiation is the earliest point where we can set parameter values
        AssetWrapper->GetOnPreInstantiationDelegate().AddUniqueDynamic(this, &ACurveInputExample::SetInitialParameterValues);
        // Jumping ahead a bit: we also want to configure inputs, but inputs are only available after instantiation
        AssetWrapper->GetOnPostInstantiationDelegate().AddUniqueDynamic(this, &ACurveInputExample::SetInputs);
        // Jumping ahead a bit: we also want to print the outputs after the node has cook and the plug-in has processed the output
        AssetWrapper->GetOnPostProcessingDelegate().AddUniqueDynamic(this, &ACurveInputExample::PrintOutputs);
    }
}

Note

This example sets prameters on pre-instantiation and inputs on post-intiations. You can also set both parameters and inputs on post-instantiation.

In Blueprints:

In Python:

def run_curve_input_example(self):
    # Get the API instance
    api = unreal.HoudiniPublicAPIBlueprintLib.get_api()
    # Ensure we have a running session
    if not api.is_session_valid():
        api.create_session()
    # Load our HDA uasset
    example_hda = unreal.load_object(None, '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0')
    # Create an API wrapper instance for instantiating the HDA and interacting with it
    self._asset_wrapper = api.instantiate_asset(example_hda, instantiate_at=unreal.Transform())
    if self._asset_wrapper:
        # Pre-instantiation is the earliest point where we can set parameter values
        self._asset_wrapper.on_pre_instantiation_delegate.add_callable(self._set_initial_parameter_values)
        # Jumping ahead a bit: we also want to configure inputs, but inputs are only available after instantiation
        self._asset_wrapper.on_post_instantiation_delegate.add_callable(self._set_inputs)
        # Jumping ahead a bit: we also want to print the outputs after the node has cook and the plug-in has processed the output
        self._asset_wrapper.on_post_processing_delegate.add_callable(self._print_outputs)

Set Parameters

Set two parameters during the HDA’s pre-instantiation phase:

  • upvectoratstart to false

  • scale to 0.2

In delegates, you bound the SetInitialParameterValues() function to the pre-instantation delegate. You can now implement the function to set the parameter values.

In C++:

#pragma once
#include "CoreMinimal.h"
#include "EditorUtilityActor.h"
#include "CurveInputExample.generated.h"
class UHoudiniPublicAPIAssetWrapper;
UCLASS()
class HOUDINIENGINEEDITOR_API ACurveInputExample : public AEditorUtilityActor
{
    GENERATED_BODY()
public:
    ACurveInputExample();

    UFUNCTION(BlueprintNativeEvent, BlueprintCallable, CallInEditor)
    void RunCurveInputExample();
protected:
    // Set our initial parameter values: disable upvectorstart and set the scale to 0.2.
    UFUNCTION(BlueprintNativeEvent, BlueprintCallable, CallInEditor)
    void SetInitialParameterValues(UHoudiniPublicAPIAssetWrapper* InWrapper);
    // Configure our inputs: input 0 is a cube and input 1 a helix.
    UFUNCTION(BlueprintNativeEvent, BlueprintCallable, CallInEditor)
    void SetInputs(UHoudiniPublicAPIAssetWrapper* InWrapper);
    // Print the outputs that were generated by the HDA (after a cook)
    UFUNCTION(BlueprintNativeEvent, BlueprintCallable, CallInEditor)
    void PrintOutputs(UHoudiniPublicAPIAssetWrapper* InWrapper);
    UPROPERTY(BlueprintReadWrite)
    UHoudiniPublicAPIAssetWrapper* AssetWrapper;
};
void ACurveInputExample::SetInitialParameterValues_Implementation(UHoudiniPublicAPIAssetWrapper* InWrapper)
{
    // Uncheck the upvectoratstart parameter
    InWrapper->SetBoolParameterValue(TEXT("upvectoratstart"), false);
    // Set the scale to 0.2
    InWrapper->SetFloatParameterValue(TEXT("scale"), 0.2f);
    // Since we are done with setting the initial values, we can unbind from the delegate
    InWrapper->GetOnPreInstantiationDelegate().RemoveDynamic(this, &ACurveInputExample::SetInitialParameterValues);
}

In Blueprints:

In Python:

import math
import unreal
@unreal.uclass()
class CurveInputExample(unreal.PlacedEditorUtilityBase):
    def __init__(self, *args, **kwargs):
        self._asset_wrapper = None
    def run_curve_input_example(self):
        ...
    def _set_initial_parameter_values(self, in_wrapper):
        """ Set our initial parameter values: disable upvectorstart and set the scale to 0.2. """
        # Uncheck the upvectoratstart parameter
        in_wrapper.set_bool_parameter_value('upvectoratstart', False)
        # Set the scale to 0.2
        in_wrapper.set_float_parameter_value('scale', 0.2)
        # Since we are done with setting the initial values, we can unbind from the delegate
        in_wrapper.on_pre_instantiation_delegate.remove_callable(self._set_initial_parameter_values)
    def _set_inputs(self, in_wrapper):
        """ Configure our inputs: input 0 is a cube and input 1 a helix. """
        raise NotImplementedError
    def _print_outputs(self, in_wrapper):
        """  Print the outputs that were generated by the HDA (after a cook) """
        raise NotImplementedError

Once the plugin begins process of instantiating the HDA, the parameters are set and ready for the first cook.

Create Inputs

To set an input on a wrapped instantiated HDA, you have to instantiate the appropriate input class and then set input objects and settings on the input. W

Instantiate inputs with the UHoudiniPublicAPIAssetWrapper::CreateEmptyInput() function. The function takes the class of the input as its only argument. This example creates a geometry and curve input.

To create the geometry input:

  1. Enter CreateEmptyInput() to instantiate the new input.

  2. Set the cube asset as its input object.

  3. Set the input on the instantiated HDA.

In C++:

void ACurveInputExample::SetInputs_Implementation(UHoudiniPublicAPIAssetWrapper* InWrapper)
{
    // Create an empty geometry input
    UHoudiniPublicAPIGeoInput* const GeoInput = Cast<UHoudiniPublicAPIGeoInput>(InWrapper->CreateEmptyInput(UHoudiniPublicAPIGeoInput::StaticClass()));
    // Load the cube static mesh asset
    UStaticMesh* const Cube = Cast<UStaticMesh>(StaticLoadObject(UStaticMesh::StaticClass(), nullptr, TEXT("/Engine/BasicShapes/Cube.Cube")));
    // Set the input object array for our geometry input, in this case containing only the cube
    GeoInput->SetInputObjects({Cube});
    // Set the input on the instantiated HDA via the wrapper
    InWrapper->SetInputAtIndex(0, GeoInput);
    // TODO: Create curve input
    // Since we are done with setting the initial values, we can unbind from the delegate
    InWrapper->GetOnPostInstantiationDelegate().RemoveDynamic(this, &ACurveInputExample::SetInputs);
}

In Blueprints:

In Python:

class CurveInputExample(unreal.PlacedEditorUtilityBase):
    ... 
    def _set_inputs(self, in_wrapper):
        """ Configure our inputs: input 0 is a cube and input 1 a helix. """
        # Create an empty geometry input
        geo_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIGeoInput)
        # Load the cube static mesh asset
        cube = unreal.load_object(None, '/Engine/BasicShapes/Cube.Cube')
        # Set the input object array for our geometry input, in this case containing only the cube
        geo_input.set_input_objects((cube, ))
        # Set the input on the instantiated HDA via the wrapper
        in_wrapper.set_input_at_index(0, geo_input)
        # TODO: Create curve input
        # unbind from the delegate, since we are done with setting inputs
        in_wrapper.on_post_instantiation_delegate.remove_callable(self._set_inputs)

Curve Inputs

To create the curve input:

  1. Enter CreateEmptyInput() to instantiate the new input.

  2. create a UHoudiniPublicAPICurveInputObject.

  3. Add the curve points to UHoudiniPublicAPICurveInputObject.

  4. Set the curve object as an input object on the curve input.

  5. Set the input on the instantiated HDA.

Note

API curve inputs are not restricted to UHoudiniPublicAPICurveInputObject instances as input objects. It also supports UHoudiniSplineComponent instances as input objects.

In C++:

void ACurveInputExample::SetInputs_Implementation(UHoudiniPublicAPIAssetWrapper* InWrapper)
{
    // Create an empty geometry input
    ...
    // Create an empty curve input
    UHoudiniPublicAPICurveInput* const CurveInput = InWrapper->CreateEmptyInput(UHoudiniPublicAPICurveInput::StaticClass());
    // Create the curve input object
    UHoudiniPublicAPICurveInputObject* const CurveObject = Cast<UHoudiniPublicAPICurveInput>(NewObject<UHoudiniPublicAPICurveInputObject>(CurveInput));
    // Make it a Nurbs curve
    CurveObject->SetCurveType(EHoudiniPublicAPICurveType::Nurbs)
    // Set the points of the curve, for this example we create a helix consisting of 100 points
    TArray<FTransform> CurvePoints;
    CurvePoints.Reserve(100);
    for (int32 i = 0; i < 100; ++i)
    {
        const float t = i / 20.0f * PI * 2.0f;
        const float x = 100.0f * cos(t);
        const float y = 100.0f * sin(t);
        const float z = i;
        CurvePoints.Emplace(FTransform(FVector(x, y, z)));
    }
    CurveObject->SetCurvePoints(CurvePoints);
    // Set the curve wrapper as an input object
    CurveInput->SetInputObjects({CurveObject});
    // Copy the input data to the HDA as node input 1
    InWrapper->SetInputAtIndex(1, CurveInput);
    // Since we are done with setting the initial values, we can unbind from the delegate
    InWrapper->GetOnPostInstantiationDelegate().RemoveDynamic(this, &ACurveInputExample::SetInputs);
}

In Blueprints:

In Python:

class CurveInputExample(unreal.PlacedEditorUtilityBase):
    ... 
    def _set_inputs(self, in_wrapper):
        """ Configure our inputs: input 0 is a cube and input 1 a helix. """
        # Create an empty geometry input
        ...

        # Create a curve input
        curve_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPICurveInput)
        # Create a curve wrapper/helper
        curve_object = unreal.HoudiniPublicAPICurveInputObject(curve_input)
        # Make it a Nurbs curve
        curve_object.set_curve_type(unreal.HoudiniPublicAPICurveType.NURBS)
        # Set the points of the curve, for this example we create a helix
        # consisting of 100 points
        curve_points = []
        for i in range(100):
            t = i / 20.0 * math.pi * 2.0
            x = 100.0 * math.cos(t)
            y = 100.0 * math.sin(t)
            z = i
            curve_points.append(unreal.Transform([x, y, z], [0, 0, 0], [1, 1, 1]))
        curve_object.set_curve_points(curve_points)
        # Set the curve wrapper as an input object
        curve_input.set_input_objects((curve_object, ))
        # Copy the input data to the HDA as node input 1
        in_wrapper.set_input_at_index(1, curve_input)

        # unbind from the delegate, since we are done with setting inputs
        in_wrapper.on_post_instantiation_delegate.remove_callable(self._set_inputs)

To fetch the existing inputs of a wrapped asset, you can use the GetInputAtIndex() and GetInputParameter() functions for node inputs and parameter-based inputs.

Recap

A recap of the example:

  • In the The Houdini Public API Instance, you make sure to have a running Houdini Engine session and start the instantiation process of an HDA

  • In Delegates, you use delegates to bind custom functions into the HDAs pre-instantiation and post-instantiation phases

  • In Parameters, you set the parameters of the HDA during the pre-instantiation

  • In Inputs, you built and set inputs for the HDA during post-instantiation.

At this point the plugin will continue processing the HDA asynchronously, sending parameters and inputs to Houdini. Once Houdini has cooked the node, you receive the resulting output and then build any outputs (meshes, materials).

The result of the example in the Unreal viewport:

The details panel (notice the inputs and modified parameter values):

Outputs

This section covers how to access the outputs of the HDA after it has cooked and processed output object and assets.

  1. Bind to the OnPostProcessingDelegate (already done in Delegates),

  2. Iterate all the outputs and print the output object and component names,

  3. Print if the output object is a proxy or not.

In C++:

void ACurveInputExample::PrintOutputs_Implementation(UHoudiniPublicAPIAssetWrapper* InWrapper)
{
    // Print out all outputs generated by the HDA
    const int32 NumOutputs = InWrapper->GetNumOutputs();
    UE_LOG(LogTemp, Log, TEXT("NumOutputs: %d"), NumOutputs);
    if (NumOutputs > 0)
    {
        for (int32 OutputIndex = 0; OutputIndex < NumOutputs; ++OutputIndex)
        {
            TArray<FHoudiniPublicAPIOutputObjectIdentifier> Identifiers;
            InWrapper->GetOutputIdentifiersAt(OutputIndex, Identifiers);
            UE_LOG(LogTemp, Log, TEXT("\toutput index: %d"), OutputIndex);
            UE_LOG(LogTemp, Log, TEXT("\toutput type: %d"), InWrapper->GetOutputTypeAt(OutputIndex));
            UE_LOG(LogTemp, Log, TEXT("\tnum_output_objects: %d"), Identifiers.Num());
            if (Identifiers.Num() > 0)
            {
                for (const FHoudiniPublicAPIOutputObjectIdentifier& Identifier : Identifiers)
                {
                    UObject* const OutputObject = InWrapper->GetOutputObjectAt(OutputIndex, Identifier);
                    UObject* const OutputComponent = InWrapper->GetOutputComponentAt(OutputIndex, Identifier);
                    const bool bIsProxy = InWrapper->IsOutputCurrentProxyAt(OutputIndex, Identifier);
                    UE_LOG(LogTemp, Log, TEXT("\t\tidentifier: %s_%s"), *(Identifier.PartName), *(Identifier.SplitIdentifier));
                    UE_LOG(LogTemp, Log, TEXT("\t\toutput_object: %s"), IsValid(OutputObject) ? *(OutputObject->GetFName().ToString()) : TEXT("None"))
                    UE_LOG(LogTemp, Log, TEXT("\t\toutput_component: %s"), IsValid(OutputComponent) ? *(OutputComponent->GetFName().ToString()) : TEXT("None"))
                    UE_LOG(LogTemp, Log, TEXT("\t\tis_proxy: %d"), bIsProxy)
                    UE_LOG(LogTemp, Log, TEXT(""))
                }
            }
        }
    }
}

In Blueprints:

In Python:

class CurveInputExample(unreal.PlacedEditorUtilityBase):
    ... 
    def _print_outputs(self, in_wrapper):
        """  Print the outputs that were generated by the HDA (after a cook) """
        num_outputs = in_wrapper.get_num_outputs()
        print('num_outputs: {}'.format(num_outputs))
        if num_outputs > 0:
            for output_idx in range(num_outputs):
                identifiers = in_wrapper.get_output_identifiers_at(output_idx)
                print('\toutput index: {}'.format(output_idx))
                print('\toutput type: {}'.format(in_wrapper.get_output_type_at(output_idx)))
                print('\tnum_output_objects: {}'.format(len(identifiers)))
                if identifiers:
                    for identifier in identifiers:
                        output_object = in_wrapper.get_output_object_at(output_idx, identifier)
                        output_component = in_wrapper.get_output_component_at(output_idx, identifier)
                        is_proxy = in_wrapper.is_output_current_proxy_at(output_idx, identifier)
                        print('\t\tidentifier: {}'.format(identifier))
                        print('\t\toutput_object: {}'.format(output_object.get_name() if output_object else 'None'))
                        print('\t\toutput_component: {}'.format(output_component.get_name() if output_component else 'None'))
                        print('\t\tis_proxy: {}'.format(is_proxy))
                        print('')

Baking Outputs

The UHoudiniPublicAPIAssetWrapper class also supports baking outputs.

There are three ways approach baking:

  1. Enable auto-bake which automatically bakes the output after a book and uses the bake settings configured on the instantiated HDA.

  2. Manually bake all outputs on an HDA

  3. Bake a specific output object only identified by its output index and identifier.

See Baking Outputs for more information.

Next Steps

For more information on blueprint/python versions for the async process, content examples, and a list of commands for the Public API, see Public API Documentation.

Unreal

Getting started

Basics

Using Houdini Engine

Scripting

Reference