Houdini Engine 1.9
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
Assets

Asset Library Files

Loading the Library File

The most common way to load assets is by first loading an asset library file (one of: .otl, .otllc, .hda, or .hdalc). This library file can contain multiple Houdini assets (HDAs).

The first thing to do is call HAPI_LoadAssetLibraryFromFile(). This will give you back a HAPI_AssetLibraryId which is a handle to the library that was just loaded. Keep it safe. You can also use the equivalent memory based function, HAPI_LoadAssetLibraryFromMemory(). Keep in mind that this memory variant will still produce a file on disk somewhere so the performance benefits are minor at this time. It is purely here as a convenience.

Note
There is some functional difference between HAPI_LoadAssetLibraryFromFile() and HAPI_LoadAssetLibraryFromMemory() regarding the saving of HIP scene files. See Saving a HIP File for details but basically if you use HAPI_LoadAssetLibraryFromFile() the OTL will be referenced by the HIP file by absolute path while if you use HAPI_LoadAssetLibraryFromMemory() the OTL will be contained within the HIP file. It's only relevant for debugging purposes though.
You can use the allow_overwrite parameter on either HAPI_LoadAssetLibraryFromFile() or HAPI_LoadAssetLibraryFromMemory() to control whether you want to allow overwriting asset definitions that have already been loaded from a different asset library file. If this flag is true and a clash is detected the function will return with a HAPI_RESULT_ASSET_DEF_ALREADY_LOADED result code.
Both the file-based and memory-based load library functions will try to checkout a license. See Licensing.

Query Assets in Library File

Next, query how many assets there are in the just-loaded library using HAPI_GetAvailableAssetCount(). Use the count to allocate an array of HAPI_StringHandle's and feed that into HAPI_GetAvailableAssets() to get the actual asset names.

Instantiation

Finally, feed any asset's name into HAPI_InstantiateAsset(), exactly as given by HAPI_GetAvailableAssets(), to actually instantiate your asset in the underlying Houdini scene.

Note
HAPI_InstantiateAsset() will try to checkout a license. See Licensing.

Once HAPI_InstantiateAsset() returns successfully, you will have a HAPI_AssetId (a typedefed int) which is a handle to the underlying asset. Hang on to this handle as it's the only way to talk to Houdini Engine about the asset you just loaded. It will be required in almost all subsequent calls into Houdini Engine.

If you’ve chosen to set the cook_on_load flag to true, HAPI_InstantiateAsset() will return immediately if you're in threaded mode (use_cooking_thread set to true in HAPI_Initialize()). Houdini Engine will load the asset into memory and "cook" it so that it is ready to use. Assume this is as if you also called HAPI_CookAsset() and see Cooking on how to check progress.

Note
If in threaded mode, HAPI_InstantiateAsset() will always return HAPI_RESULT_SUCCESS immediately. You'll need to see Cooking and check the progress to find out if the asset instantiated and cooked successfully.

If you choose to set cook_on_load to false you’ll need to call HAPI_CookAsset() first, before attempting to use the asset - see Cooking. However, you can still get some information about the instantiated asset via HAPI_GetAssetInfo() and you can use the HAPI_AssetInfo::nodeId to change parameters on the asset before the first cook. To see if an asset has ever been cooked there’s a flag, HAPI_AssetInfo::hasEverCooked.

This HAPI_GetAssetInfo() function takes an HAPI_AssetInfo::id from the call to HAPI_InstantiateAsset(), and fills in a HAPI_AssetInfo struct on successful return. The HAPI_AssetInfo gives you basic, high level information about your asset such as the name of the asset, the asset label as defined in Houdini, the number of objects, handles, and the inputs, if any, on the asset and so on.

One notable point is the concept of the HAPI_AssetInfo::validationId. The idea here is that in saving a session in the host application, one might just serialize all the data structures pertaining to HAPI, such as the HAPI_AssetInfo struct. On reloading of the saved file, there might not be a session of Houdini Engine currently running, or, even if there is, it might have different things loaded in it. We then need to establish whether the asset that has just been reloaded has a corresponding valid entry on the Houdini Engine side. The HAPI_AssetInfo::id is not sufficient in this case, as it may clash with other assets across different sessions. Here is where the HAPI_AssetInfo::validationId comes in handy - whenever any asset is instantiated, Houdini Engine will also pass back a HAPI_AssetInfo::validationId, calculated based on the current system time. This HAPI_AssetInfo::validationId is unique across sessions of Houdini and it can be used later, such as on file load, to determine whether the saved asset has a valid entry on the Houdini Engine side. If not, then asset needs to be re-instantiated.

Cooking

Once an asset is loaded, it needs to be "cooked" before its results are ready to be used. This is done by calling HAPI_CookAsset().

In threaded mode, this cook happens asynchronously. Your next call should be to HAPI_GetStatus(), with HAPI_STATUS_COOK_STATE as the status_type, which will return to you the current state of that cook. Keep calling HAPI_GetStatus() until it returns one of the READY states - a state less than or equal to HAPI_STATE_MAX_READY_STATE. Here's what the different READY states mean:

  • HAPI_STATE_READY: Everything cooked successfully without errors.
  • HAPI_STATE_READY_WITH_FATAL_ERRORS: Something really bad happened and the entire cook was halted as a result. For example, the asset library file (OTL) is currupt and the asset cannot even be instantiated. You should halt immidiatly in this case and not continue with the usual post-cook info queries.
  • HAPI_STATE_READY_WITH_COOK_ERRORS: One or more of the objects in the asset failed to generate its geometry (its display SOP node has no geometry data). In such cases, the errors from each problem object will be concatenated together but the cook will continue, trying to cook all objects. You should display these errors as warnings because they are likely not fatal. For example, the asset is still waiting for an external input to be connected.

You can get even better information on cook progress using HAPI_GetStatusStringBufLength() followed by HAPI_GetStatusString() for current cook step descriptions and HAPI_GetCookingCurrentCount() / HAPI_GetCookingTotalCount() for an idea of percentage completion.

Here is a code snippet to show the status update loop in threaded mode:

ENSURE_SUCCESS( HAPI_CookAsset( asset_id, NULL ) );
int status;
do
{
int statusBufSize = 0;
&statusBufSize ) );
char * statusBuf = NULL;
if ( statusBufSize > 0 )
{
statusBuf = new char[ statusBufSize ];
ENSURE_SUCCESS( HAPI_GetStatusString(
HAPI_STATUS_COOK_STATE, statusBuf ) );
}
if ( statusBuf )
{
// Display the cook status to the user in your host
delete[] statusBuf;
}
// Add in sleep here to avoid printing the status too many times
} while ( status > HAPI_STATE_MAX_READY_STATE );
ENSURE_COOK_SUCCESS( status );

Where each of ENSURE_SUCCESS and ENSURE_COOK_SUCCESS are macros:

#define ENSURE_SUCCESS( result ) \
if ( (result) != HAPI_RESULT_SUCCESS ) \
{ \
cout << "failure at " << __FILE__ << ":" << __LINE__ << endl; \
cout << get_last_error() << endl; \
exit( 1 ); \
}
#define ENSURE_COOK_SUCCESS( result ) \
if ( (result) != HAPI_STATE_READY ) \
{ \
cout << "failure at " << __FILE__ << ":" << __LINE__ << endl; \
cout << get_last_cook_error() << endl; \
exit( 1 ); \
}
static std::string get_last_error()
{
int buffer_length;
char * buf = new char[ buffer_length ];
std::string result( buf );
delete[] buf;
return result;
}
static std::string get_last_cook_error()
{
int buffer_length;
char * buf = new char[ buffer_length ];
std::string result( buf );
delete[] buf;
return result;
}

In single threaded mode, the cook happens inside of HAPI_InstantiateAsset(), and HAPI_GetStatus() will immediately return HAPI_STATE_READY.

In both cases, even if you completely omit calling HAPI_GetStatus(), the next API method that you call that relies on the cooked results will block until the cook is finished. Therefore, you should structure your code the same way, regardless of whether you're in threaded mode or not, so you can flip between threaded and non-threaded mode with a change to a single variable and all the rest of the code can remain unchanged.

It is generally important to check the return codes and error messages of all API calls but with HAPI_CookAsset it is especially important. This is because if anything bad happened during the cook it means most if not all proceeding API calls on this asset will fail or cause other problems. See Return Codes and Error Strings for details.

When the cook is finished without fatal errors, and you're ready to find out some information about the asset you just loaded, you should call HAPI_GetAssetInfo(). The most important field in this structure is HAPI_AssetInfo::objectCount, which tells you the number of objects this asset contains, and allows you to populate your asset with content. Please see the next section on Objects.

Transforms

For all assets there are two sets of transforms. First, there is the transform in the host environment, describing where asset sits in the host. Underneath the covers, however, there is another transform, which indicates where this asset sits inside Houdini Engine (the underlying Houdini scene). Usually, these won't match up. For example a user could drag the spaceship that he/she brought in to any location in the host but not have it move in the Houdini scene. For most asset types, it doesn't matter that these two transforms don't match up.

However, for some asset types such as a physics simulation the Houdini scene transform, not the host application transform, drives the output of the asset. In these cases, it is important to make sure that the two sets of transforms - the one in the host and the corresponding transform in the Houdini scene - match up. What we need to make sure of in this case is that when an asset such as a collision plane gets moved around in the host environment, the collision plane is being moved in exactly the same way in the Houdini scene. To this end, we have two functions:

The first of these queries where an asset is in the Houdini scene and the second sets the location of the asset in the Houdini scene. When using these functions, please remember to account for the differences in axis orientation and handedness between your host application and Houdini. See Utility Functions.