Houdini Engine for Unreal
 All Files Pages
Public API

Version 2 of the Houdini Engine Plugin for Unreal now contains a public API. The public API is usable in C++, Blueprints and Python. The public API supports instantiating HDAs as actors in a world, setting parameters and inputs, cooking, inspecting and iterating over outputs and baking outputs.

We shall cover the functionality of the public API by building a script to instantiate an HDA, configure a curve input and a geometry input, and then iterate over the outputs once the node has cooked.

The public API provides a class, UHoudiniPublicAPI, with some functions for controlling the session and instantiating Houdini Asset Actors.

The Houdini Public API Instance

The UHoudiniPublicAPI class provides some functions for controlling the session and instantiating Houdini Asset Actors via the API:

  • IsSessionValid: Returns true if there is a valid Houdini Engine session running/connected.
  • CreateSession: Start a new Houdini Engine Session if there is no current session.
  • StopSession: Stops the current Houdini Engine session.
  • RestartSession: Stops, then creates a new Houdini Engine session.
  • InstantiateAsset: Instantiates an HDA in the specified world/level. Returns a wrapper for instantiated asset, see Instantiating HDAs.
  • IsAssetCookingPaused: Returns true if asset cooking is paused.
  • PauseAssetCooking: Pause asset cooking (if not already paused).
  • ResumeAssetCooking: Resume asset cooking (if it was paused).
  • CreateEmptyInput: Create a new empty API input object. The user must populate it and then set it as an input on an asset wrapper. See Inputs.

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

In C++:

#include "HoudiniPublicAPIBlueprintLib.h"
#include "HoudiniPublicAPI.h"
UHoudiniPublicAPI* const API = UHoudiniPublicAPIBlueprintLib::GetAPI();

In Blueprints:

Unreal_PublicAPIGetAPI.png

In Python:

import unreal
api = unreal.HoudiniPublicAPIBlueprintLib.get_api()

With the API instance in hand we can check the status of the Houdini Engine session, and start a session if required.

In C++:

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

In Blueprints:

Unreal_PublicAPIStartSession.png

In Python:

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

Instantiating HDAs

Now we can instantiate an HDA in our world. In our case we shall use 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:

Unreal_PublicAPIInstantiateAsset.png

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) that we can use to interact with the instantiated HDA, for example to set parameter values or assign inputs. The InstantiateAsset function has a number of parameters:

  • InHoudiniAsset: The HDA uasset to instantiate.
  • InInstantiateAt: The Transform to instantiate the HDA actor with.
  • InWorldContextObject: A world context object for identifying the world to spawn in, if InSpawnInLevelOverride is null.
  • InSpawnInLevelOverride: If not nullptr, then the AHoudiniAssetActor is spawned in that level. If both InSpawnInLevelOverride and InWorldContextObject are null, then the actor is spawned in the current editor context world's current level.
  • bInEnableAutoCook: If true (the default) the HDA will cook automatically after instantiation and after parameter, transform and input changes.
  • bInEnableAutoBake: If true, the HDA output is automatically baked after a cook. Defaults to false.
  • InBakeDirectoryPath: The directory to bake to if the bake path is not set via attributes on the HDA output.
  • InBakeMethod: The bake target (to actor vs blueprint).
  • bInRemoveOutputAfterBake: If true, HDA temporary outputs are removed after a bake. Defaults to false.
  • bInRecenterBakedActors: Recenter the baked actors to their bounding box center. Defaults to false.
  • bInReplacePreviousBake: If true, on every bake replace the previous bake's output (assets + actors) with the new bake's output. Defaults to false.

The Houdini Public API Asset Wrapper

The API wraps instantiated HDAs in instances of UHoudiniPublicAPIAssetWrapper. This class allows us to manipulate the instantiated HDA by setting parameters, inputs, cooking, baking and accessing outputs.

In the following subsections we shall cover the delegates the wrapper provides, how to set and get parameter values, how to set and get inputs and how to get outputs after a successful cook.

Delegates

In many instances version 2 of the plug-in interacts with Houdini Engine asynchrounsly (when cooking, for example). This means that we need to be able to bind to a delegate in order to receive a callback after an operation has completed. For example, if we want to change a parameter, have the node cook and then fetch the output, we need to bind to the appropriate delegate in order to receive a callback once the process has completed. For these purposes the API wrapper provides a number of delegates:

  • OnPreInstantiationDelegate: Broadcast just before an HDA is instantitated: the HDA's default parameter definitions are available, but the node has not yet been instantiated in HAPI/Houdini Engine. Parameters can be set at this point.
  • OnPostInstantiationDelegate: Broadcast after the asset was successfully instantiated. This is a good place to set/configure inputs before the first cook.
  • OnPostCookDelegate: Broadcast after a cook completes. Output objects/assets have not yet been created/updated.
  • OnPreProcessStateExitedDelegate: Broadcast at the end of the pre-processing phase (after a cook). Output objects/assets have not yet been created/updated.
  • OnPostProcessingDelegate: Broadcast after output processing has been done in Unreal. Output objects/assets have been created/updated.
  • OnPostBakeDelegate: Broadcast after baking the asset (not called for individual outputs that are baked to the content browser).
  • OnPostPDGTOPNetworkCookDelegate: Broadcast after a cook of a TOP network completes. Work item results have not necessarily yet been loaded.
  • OnPostPDGBakeDelegate: Broadcast after baking PDG outputs (not called for individual outputs that are baked to the content browser).
  • OnProxyMeshesRefinedDelegate: Broadcast after refining all proxy meshes for this wrapped asset.

In the case of our example, we want to bind to OnPreInstantiationDelegate to set some parameters once the parameter interface is ready, but before the first cook.

Note
This is the earliest point at which we can set parameters, but we do not necessarily have to set parameters before the first cook. One could also instantiate the HDA, let it cook, and then set parameters and have it recook. But if the HDA takes a long time to cook it might be advantageous to use the various delegates as efficiently as possible to avoid expensive recooks where possible.

Assuming we have a class ACurveInputExample (see Parameters for the definition of ACurveInputExample), we can bind some of its functions to the delegates where we want 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
We are setting parameters on pre-instantiation and inputs on post-instantiation, but using both here is not strictly neccessary, we could also simply set both parameters and inputs on post-instantiation.

In Blueprints:

Unreal_PublicAPIPreInstantationDelegate.png

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)

Parameters

The API wrapper provides a number of functions for setting and getting parameter values. The following functions can be used to set and get parameter values of a named parameter tuple (the parameter tuple name is a required argument, and the index of the parameter in the tuple is optional, defaulting to 0).

  • SetFloatParameterValue: Set the value of a float parameter
  • GetFloatParameterValue: Get the value of a float parameter
  • SetColorParameterValue: Set the value of a float parameter
  • GetColorParameterValue: Get the value of a float parameter
  • SetIntParameterValue: Set the value of a integer parameter
  • GetIntParameterValue: Get the value of a integer parameter
  • SetBoolParameterValue: Set the value of a boolean/toggle parameter
  • GetBoolParameterValue: Get the value of a boolean/toggle parameter
  • SetStringParameterValue: Set the value of a string parameter
  • GetStringParameterValue: Get the value of a string parameter
  • SetAssetRefParameterValue: Set the value of string parameter from a UObject as an asset reference
  • GetAssetRefParameterValue: Get the UObject instance referenced by a string parameter
  • TriggerButtonParameter: Executes the callback script of a button parameter
  • GetParameterTuples: Gets the values of all parameter tuples as an array of structs
  • SetParameterTuples: Sets the values of the parameter tuples in the given array of structs

The following functions are available to simplify working with ramp parameters:

  • SetRampParameterNumPoints: Set the number of points of ramp parameter
  • GetRampParameterNumPoints: Get the number of points of ramp parameter
  • SetFloatRampParameterPointValue: Set the point position, value and interpolation of a float ramp point by index
  • GetFloatRampParameterPointValue: Get the point position, value and interpolation of a float ramp point by index
  • SetFloatRampParameterPoints: Set all of the points of a float ramp parameter from an array
  • GetFloatRampParameterPoints: Get all of the points of a float ramp parameter as an array.
  • SetColorRampParameterPointValue: Set the point position, value and interpolation of a color ramp point by index
  • GetColorRampParameterPointValue: Get the point position, value and interpolation of a color ramp point by index
  • SetColorRampParameterPoints: Set all of the points of a color ramp parameter from an array
  • GetColorRampParameterPoints: Get all of the points of a color ramp parameter as an array.

Continuing with our example, we want to set two parameters: upvectoratstart to false and scale to 0.2 during the HDA's pre-instantiation phase. In Delegates we bound the SetInitialParameterValues() function to the pre-instantiation delegate. We shall now implement the function to set our 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:

Unreal_PublicAPISetInitialParameterValues.png

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

At this point, once the plug-in begins the process of instantiating the HDA, the parameters will be set, ready for the first cook. The next thing we want to do is to set the inputs we need.

Inputs

The API exposes all of the input types supported by the plug-in via API wrapper classes for each input type:

  • UHoudiniPublicAPIInput: The base class of the inputs, one would only use this directly when creating subclasses from it for new input types.
  • UHoudiniPublicAPIGeoInput: A geometry input.
  • UHoudiniPublicAPICurveInput: A curve input.
  • UHoudiniPublicAPIAssetInput: An asset input (using another instantiated asset as an input).
  • UHoudiniPublicAPIWorldInput: A world input: using an actor from the world as an input.
  • UHoudiniPublicAPILandscapeInput: A landscape as an input.

In order to set an input on a wrapped instantiated HDA, we must first instantiate the appropriate input class from the list above, and then set input objects and settings on the input. We can instantiate inputs by using the UHoudiniPublicAPIAssetWrapper::CreateEmptyInput() function. The function takes the class of the input we want to create as its only argument.

Continuing with our example, we want to create two inputs: a geometry input, for the purpose of our example a cube will suffice, and a curve input.

In order to create the geometry input we must:

  1. use CreateEmptyInput() to instantiate the new input
  2. set the cube asset as its input object
  3. then 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:

Unreal_PublicAPISetInputs.png

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)

Now that we have created and assigned the geometry input, we must create the second input, our curve input.

Curve Inputs

Curve inputs in the API have an additional helper class for constructing a curve from points: UHoudiniPublicAPICurveInputObject. The object has a number of functions for setting/adding curve points, where each point is represented by an FTransform:

  • SetCurvePoints: Set the curve points to the specified array of FTransforms.
  • AppendCurvePoint: Append a point, as an FTransform, to the curve.
  • ClearCurvePoints: Remove all points from the curve.
  • GetCurvePoints: Get the curve's points as an array of FTransforms.

In addition to this other curve properties, such as the type (NURBS, Bezier etc), and whether the curve is open or closed, can also be configured on UHoudiniPublicAPICurveInputObject via the following functions:

  • IsClosed: Returns true if the curve is closed.
  • SetClosed: Set whether the curve is closed or not.
  • IsReversed: Returns true if the curve is reversed.
  • SetReversed: Set whether the curve is reversed.
  • GetCurveType: Get the curve type as an enum (Polygon, NURBs, Bezier, Points): EHoudiniPublicAPICurveType.
  • SetCurveType: Set the curve type via the EHoudiniPublicAPICurveType enum.
  • GetCurveMethod: Get the curve method as an enum (CVs, Breakpoints, Freehand): EHoudiniPublicAPICurveMethod.
  • SetCurveMethod: Set the curve method via an enum EHoudiniPublicAPICurveMethod.

In order to create the curve input we must:

  1. use 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. then set the input on the instantiated HDA
Note
API curve inputs are not restricted to only using UHoudiniPublicAPICurveInputObject instances as input objects, it also supports using 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:

Unreal_PublicAPICurveInput.png

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)

Let us recap the example up to this point:

  1. In the The Houdini Public API Instance section we ensured that we have a running Houdini Engine session and started the instantiation process of an HDA
  2. In Delegates we used delegates to bind our custom functions into the HDAs pre-instantiation and post-instantiation phases
  3. In Parameters we set the parameters of our HDA during the pre-instantiation
  4. In Inputs we built and set inputs for our HDA during post-instantiation

At this point the plug-in will continue processing the HDA asynchronously, sending parameters and inputs to Houdini, and once Houdini has cooked the node, receive the resulting output and build any outputs (meshes, materials etc).

The result of our example looks like this in the viewport:

Unreal_PublicAPIExampleViewport.png

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

Unreal_PublicAPIExampleDetail.png

In the next section we shall look at how to access the outputs of the HDA after it has cooked and processed (created/updated) output objects/assets.

Outputs

The UHoudiniPublicAPIAssetWrapper class provides a number of functions that can be used to access to outputs of the HDA after a cook:

  • GetNumOutputs: Returns the number of outputs the HDA has.
  • GetOutputTypeAt: Returns the type of the output (EHoudiniOutputType: Mesh, Instancer, Landscape, Curve, Skeletal) at the given output index.
  • GetOutputIdentifiersAt: Populates an array of FHoudiniPublicAPIOutputObjectIdentifier for a given output index. These identify individual objects in the output at the given index. For example, a rendered static mesh, and its collision meshes.
  • GetOutputObjectAt: Returns the output object at the given output index identified by the a FHoudiniPublicAPIOutputObjectIdentifier. For example, a UStaticMesh.
  • GetOutputComponentAt: Returns the output component for the object at the given output index identified by the a FHoudiniPublicAPIOutputObjectIdentifier. For example, a UStaticMeshComponent.
  • HasAnyCurrentProxyOutput: Returns true any output contains any current proxy mesh.
  • HasAnyCurrentProxyOutputAt: Returns true if the output at the specified index.
  • IsOutputCurrentProxyAt: Returns true if the output at the specified index and identifier is a current proxy.

As an example we shall:

  1. Bind to the OnPostProcessingDelegate (already done in Delegates),
  2. Iterate over all the outputs and print the output object and component names,
  3. and 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:

Unreal_PublicAPIPrintOutputs.png

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. Broadly speaking there are 3 ways to approach baking. The first is to enable auto-baking:

  • SetAutoBakeEnabled: Enables/disables auto-baking after a successful cook.
  • IsAutoBakeEnabled: Returns true if auto-baking is enabled.

Auto-baking will automatically bake the outputs after a cook and uses the bake settings configured on the instantiated HDA. These settings can be queried/set via:

  • SetBakeMethod: Sets the bake method: bake to actors, blueprints or foliage.
  • GetBakeMethod: Gets the bake method.
  • SetRemoveOutputAfterBake: Enables/disables automatically removing temporary HDA output from the world after a bake.
  • GetRemoveOutputAfterBake: Return true if temporary HDA output will be removed from the world after a bake.
  • SetRecenterBakedActors: Enables/disables recentering baked output actors at their bounding box center.
  • GetRecenterBakedActors: Returns true if recentering is enabled.
  • SetReplacePreviousBake: Enables/disables bake replacement mode: if enabled the previous bake's assets and actors are replaced (if names match).
  • GetReplacePreviousBake: Returns true if replacement mode is enabled.

The second way is to manually bake all outputs of an HDA:

  • BakeAllOutputs: This bakes all outputs with the settings configured via the wrapper.
  • BakeAllOutputsWithSettings: This bakes all outputs but the bake settings can be passed to the function, overriding settings configured on the wrapper.

The third way to bake is to bake a specific output object only, to the content browser, identified by its output index and identifier:

  • BakeOutputObjectAt: Bakes the output object specified by its output index and indentifier.

The Blueprint Async Processor

Since the instantiation and cooking of the HDAs are handled asynchronously in the plug-in we use delegates in order to be notified when HDA processing has entered a certain phase or when cooking is complete. This means we would have to manually bind and unbind to a lot of delegates while writing scripts/tools using the API. In order to simplify this a bit in Blueprints, the API also includes a Blueprint Async Action node: UHoudiniPublicAPIProcessHDANode, or ProcessHDA.

The ProcessHDA node has all of the options of the UHoudiniPublicAPI::InstantiateAsset() function, but it also provides some quality of life features:

  1. It accepts a map of FName to FHoudiniParameterTuple to set parameters without having to manually bind to delegates,
  2. It accepts a map of int32 to UHoudiniPublicAPIInput to set index-based node iputs without having to manually bind to delegates,
  3. It accepts a map of FName to UHoudiniPublicAPIInput to set parameter-based inputs without having to manually bind to delegates,
  4. It is a latent node with output pins that are bound to the various delegates (such as OnPreInstantiateDelegate, OnPostInstantiateDelegate and OnPostProcessDelegate) to make it easier to bind custom logic to these events.

Here is a screenshot of using the Process HDA node with the blueprint version of our curve input example (slightly modified in that we have MakeGeoInput and MakeCurveInput functions to create and populate the UHoudiniPublicAPIGeoInput and UHoudiniPublicAPICurveInput respectively):

Unreal_PublicAPIProcessHDANode.png

The Python Version Of The Async Processor

For Python there is something similar to the ProcessHDA Blueprint node (see The Blueprint Async Processor) available: the ProcessHDA Python class, located in the HoudiniEngineV2.asyncprocessor module. The ProcessHDA class constructor takes the same arguments as the Async Blueprint node. Processing is started by calling the ProcessHDA.activate() function. The difference is that the ProcessHDA class has a function for each of the delegates:

  • on_failure
  • on_complete
  • on_pre_instantiation
  • on_post_instantiation
  • on_post_auto_cook
  • on_pre_process
  • on_post_processing
  • on_post_auto_bake

These are internally bound to a UHoudiniPublicAPIAssetWrapper and called when the delegates are broadcast. Users can subclass the ProcessHDA class and override the above functions to implement their own functionality.

For example, here is our curve input example, but implemented as a subclass of ProcessHDA:

import math
import unreal
from HoudiniEngineV2.asyncprocessor import ProcessHDA
_g_processor = None
class ProcessHDAExample(ProcessHDA):
def on_failure(self):
print('on_failure')
global _g_processor
_g_processor = None
def on_complete(self):
print('on_complete')
global _g_processor
_g_processor = None
def on_post_processing(self):
# Print out all outputs generated by the HDA
num_outputs = self.asset_wrapper.get_num_outputs()
print('num_outputs: {}'.format(num_outputs))
if num_outputs > 0:
for output_idx in range(num_outputs):
identifiers = self.asset_wrapper.get_output_identifiers_at(output_idx)
print('\toutput index: {}'.format(output_idx))
print('\toutput type: {}'.format(self.asset_wrapper.get_output_type_at(output_idx)))
print('\tnum_output_objects: {}'.format(len(identifiers)))
if identifiers:
for identifier in identifiers:
output_object = self.asset_wrapper.get_output_object_at(output_idx, identifier)
output_component = self.asset_wrapper.get_output_component_at(output_idx, identifier)
is_proxy = self.asset_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('')
def make_geo_input():
""" Makes a cube geometry input. """
# get the API singleton
houdini_api = unreal.HoudiniPublicAPIBlueprintLib.get_api()
# Create a geo input
geo_input = houdini_api.create_empty_input(unreal.HoudiniPublicAPIGeoInput)
# Set the input objects/assets for this input
geo_object = unreal.load_object(None, '/Engine/BasicShapes/Cube.Cube')
geo_input.set_input_objects((geo_object, ))
return geo_input
def make_curve_input():
""" Makes a helix input curve. """
# get the API singleton
houdini_api = unreal.HoudiniPublicAPIBlueprintLib.get_api()
# Create a curve input
curve_input = houdini_api.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, ))
return curve_input
def make_parameters():
""" Make a dict of HoudiniParameterTuple containing `upvectoratstart` and
`scale`.
"""
parameters = {}
parameter_tuple = unreal.HoudiniParameterTuple()
parameter_tuple.bool_values = (False, )
parameters['upvectoratstart'] = parameter_tuple
parameter_tuple = unreal.HoudiniParameterTuple()
parameter_tuple.float_values = (0.2, )
parameters['scale'] = parameter_tuple
return parameters
def run():
# Create the processor with preconfigured inputs
global _g_processor
_g_processor = ProcessHDAExample(
unreal.load_object(None, '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0'),
parameters=make_parameters(),
node_inputs={0: make_geo_input(), 1: make_curve_input()},
)
# Activate the processor, this will start instantiation, and then cook
if not _g_processor.activate():
unreal.log_warning('Activation failed.')
else:
unreal.log('Activated!')
if __name__ == '__main__':
run()

PDG / TOP Networks

The Public API also supports interacting with the PDG asset link if an HDA contains one or more TOP networks.

There are two delegates for PDG related events:

  • GetOnPostPDGTOPNetworkCookDelegate(): Broadcast after cooking a TOP Network in the HDA. Work item results have not necessarily yet been loaded.
  • GetOnPostPDGBakeDelegate(): Broadcast after baking PDG outputs.

The following functions are available in UHoudiniPublicAPIAssetWrapper for interacting with the PDG asset link:

  • HasPDGAssetLink(): Returns true if the wrapped asset is valid and has a PDG asset link.
  • GetPDGTOPNetworkPaths(): Gets the paths (relative to the instantiated asset) of all TOP networks in the HDA.
  • GetPDGTOPNodePaths(): Gets the paths (relative to the specified TOP network) of all TOP nodes in the network.
  • PDGDirtyAllNetworks(): Dirty all TOP networks in this asset.
  • PDGDirtyNetwork(): Dirty the specified TOP network.
  • PDGDirtyNode(): Dirty the specified TOP node.
  • PDGCookOutputsForNetwork(): Cook all outputs for the specified TOP network.
  • PDGCookNode(): Cook the specified TOP node.
  • PDGBakeAllOutputs(): Bake all outputs of the instantiated asset's PDG contexts using the settings configured on the asset.
  • PDGBakeAllOutputsWithSettings(): Bake all outputs of the instantiated asset's PDG contexts using the specified settings.
  • SetPDGAutoBakeEnabled(): Set whether to automatically bake PDG work items after a successfully loading them.
  • IsPDGAutoBakeEnabled(): Returns true if PDG auto bake is enabled.
  • SetPDGBakeMethod(): Sets the bake method to use for PDG baking (to actor, blueprint, foliage).
  • GetPDGBakeMethod(): Gets the currently set bake method to use for PDG baking (to actor, blueprint, foliage).
  • SetPDGBakeSelection(): Set which outputs to bake for PDG, for example, all, selected network, selected node.
  • GetPDGBakeSelection(): Get which outputs to bake for PDG, for example, all, selected network, selected node.
  • SetPDGRecenterBakedActors(): Determines if baked actors are recentered to their bounding box center after a PDG bake, on the PDG asset link.
  • GetPDGRecenterBakedActors(): Returns true if baked actors are recentered to their bounding box center after a PDG bake, on the PDG asset link.
  • SetPDGBakingReplacementMode(): Set the replacement mode to use for PDG baking (replace previous bake output vs incrementing the output names).
  • GetPDGBakingReplacementMode(): Get the replacement mode to use for PDG baking.
Note
PDG / TOP network cooking is not directly linked the HDAs cooking itself. So even if auto-cook is enabled for the HDA (or the asset was instantiated with bEnableAutoCook == true), PDG will not cook automatically. The auto-cook setting for the PDG asset link is not currently exposed via the public API, so the user must call PDGCookNode() or PDGCookOutputsForNetwork() to cook PDG.

Error Messages

A common pattern in the public API is that functions return a boolean to indicate success or failure. If an error occurred, the last error message can be retrieved use the GetLastErrorMessage() function (available on any classes that derive from UHoudiniPublicAPIObjectBase, such as UHoudiniPublicAPI, UHoudiniPublicAPIAssetWrapper, the input classes deriving from UHoudiniPublicAPIInput and UHoudiniPublicAPICurveInputObject).

Additional Examples

The Content/Examples directory of the plug-in contains a number of example HDAs, Python scripts and an Editor Utility Widget and Editor Utility Actor.

The following C++ example is available:

  • CurveInputExample.h and CurveInputExample.cpp: The ACurveInputExample discussed in this documentation.

The following Blueprint examples are available:

  • EUA_CurveInputExample: The blueprint example from this documentation.
  • EUW_APIExample: An example editor utility widget with various options for instantiating and HDA and setting parameters.

The following Python examples are available:

  • asset_input_example.py: An example script using asset inputs.
  • bake_all_outputs_example.py: An example script that bakes all outputs of the HDA after a cook.
  • bake_output_object_example.py: An example script that bakes a specific output object to the content browser after a cook.
  • curve_input_example.py: Similar to the example built in this documentation: using an input curve.
  • eua_curve_input_example.py: The example Python script from this documentation.
  • geo_input_example.py: An example scirpt for using geometry inputs.
  • instances_example.py: Setting a parameter, cooking an HDA with instances as output.
  • landscape_input_example.py: Using landscape inputs.
  • outputs_example.py: Iterating over the outputs after cooking an HDA.
  • pdg_example.py: Cooking and baking PDG output.
  • process_hda_example.py: Using the ProcessHDA class to simplify delegate handling.
  • ramp_parameter_example.py: Using the helper functions in the API for setting ramp parameter points.
  • start_session_example.py: A simple example that just starts the Houdini Engine session.
  • world_input_example.py: Using world inputs.