using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Runtime.InteropServices; // This is a sample program to show how to write a C# program to // use the Houdini Engine API (HAPI) on Windows x64. // It first finds the Houdini or Houdini Engine install // location, then adds to the /bin path where the HAPI libraries // are located to the program's environment. // Then it creates an out of process pipe session then closes it. // This program can be tested by creating a new console C# application // project in Visual Studio. The sample code assumes that the program will be // built as a 64-bit application, so in the Visual Stuio project settings, // set Build -> Platform target to x64. // NOTE: Places marked with TODO should be modified before running the program. namespace HAPICSharp { using HAPI_SessionId = System.Int64; using HAPI_Int64 = System.Int64; using HAPI_StringHandle = System.Int32; using HAPI_ErrorCodeBits = System.Int32; using HAPI_AssetLibraryId = System.Int32; using HAPI_NodeId = System.Int32; using HAPI_NodeTypeBits = System.Int32; using HAPI_NodeFlagsBits = System.Int32; using HAPI_ParmId = System.Int32; using HAPI_PartId = System.Int32; class Program { // HOUDINI SETTINGS ------------------------------------------------------------------------------------------- // TODO: Change these numbers to the local installed Houdini version. // You can find these at: /toolkit/include/HAPI/HAPI_Version.h public const int HOUDINI_MAJOR = 16; public const int HOUDINI_MINOR = 5; public const int HOUDINI_BUILD = 607; public const int HOUDINI_PATCH = 0; // TODO: Change these numbers to the local installed Houdini version. // You can find these at: /toolkit/include/HAPI/HAPI_Version.h public const int HOUDINI_ENGINE_MAJOR = 3; public const int HOUDINI_ENGINE_MINOR = 2; public const int HOUDINI_ENGINE_API = 27; public const string HAPI_BIN_PATH = "/bin"; public const string HAPI_LIBRARY = "libHAPIL"; public const string SIDEFX_SOFTWARE_REGISTRY = "SOFTWARE\\Side Effects Software\\"; // Environment variable for setting Houdini or Houdini Engine install path // TODO: Optionally, instead of relying on the registry, you can simply set environment variable: // e.g.: HAPI_PATH=C:\PathToHoudiniInstall public const string HAPI_PATH = "HAPI_PATH"; // SAMPLE HAPI C# PROGRAM ------------------------------------------------------------------------------------- /// /// Main sample code. Starts and stops a Houdini Engine session. /// /// static void Main(string[] args) { // First find the HAPI libraries and add to our program's environment path // This ensure the HAPI calls below will work. if (!FindAddHAPILibraryToPath()) { return; } // We found the Houdini or Houdini Engine libraries. // The DLLImports should now work properly. // Now start an out of process pipe session HAPI_ThriftServerOptions serverOptions = new HAPI_ThriftServerOptions(); serverOptions.autoClose = true; serverOptions.timeoutMs = 2000f; string pipeName = "hapi"; int processID = 0; // First we start the pipe server HAPI_Result result = HAPI_StartThriftNamedPipeServer(ref serverOptions, pipeName, out processID); if (result != HAPI_Result.HAPI_RESULT_SUCCESS) { ErrorMsg(string.Format("Unable to start RPC pipe server.\nError Code: {0}", result)); return; } HAPI_Session hapiSession = new HAPI_Session(); hapiSession.type = HAPI_SessionType.HAPI_SESSION_THRIFT; // Then we connect to it result = HAPI_CreateThriftNamedPipeSession(out hapiSession, pipeName); if (result != HAPI_Result.HAPI_RESULT_SUCCESS) { ErrorMsg(string.Format("Unable to create RPC pipe server.\nError Code: {0}", result)); return; } HAPI_CookOptions cookOptions = new HAPI_CookOptions(); cookOptions.splitGeosByGroup = false; cookOptions.splitPointsByVertexAttributes = false; cookOptions.cookTemplatedGeos = true; cookOptions.maxVerticesPerPrimitive = 3; cookOptions.refineCurveToLinear = true; cookOptions.curveRefineLOD = 8f; cookOptions.packedPrimInstancingMode = HAPI_PackedPrimInstancingMode.HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT; cookOptions.handleBoxPartTypes = false; cookOptions.handleSpherePartTypes = false; // Initialize session result = HAPI_Initialize(ref hapiSession, ref cookOptions, true, -1, "", "", "", "", ""); if (result != HAPI_Result.HAPI_RESULT_SUCCESS) { ErrorMsg(string.Format("Unable to intialize session.\nError Code: {0}", result)); return; } // Clean up the session result = HAPI_Cleanup(ref hapiSession); if (result != HAPI_Result.HAPI_RESULT_SUCCESS) { ErrorMsg(string.Format("Unable to cleanup session.\nError Code: {0}", result)); return; } // Close the session result = HAPI_CloseSession(ref hapiSession); if (result != HAPI_Result.HAPI_RESULT_SUCCESS) { ErrorMsg(string.Format("Unable to close session.\nError Code: {0}", result)); return; } Console.WriteLine("Houdini Engine is working well!"); } // LOAD HAPI LIBRARY ------------------------------------------------------------------------------------------ public enum RegSAM { QueryValue = 0x0001, SetValue = 0x0002, CreateSubKey = 0x0004, EnumerateSubKeys = 0x0008, Notify = 0x0010, CreateLink = 0x0020, WOW64_32Key = 0x0200, WOW64_64Key = 0x0100, WOW64_Res = 0x0300, Read = 0x00020019, Write = 0x00020006, Execute = 0x00020019, AllAccess = 0x000f003f } public static UIntPtr HKEY_LOCAL_MACHINE = new UIntPtr(0x80000002u); public static UIntPtr HKEY_CURRENT_USER = new UIntPtr(0x80000001u); [DllImport("Advapi32.dll")] static extern uint RegOpenKeyEx( UIntPtr hKey, string lpSubKey, uint ulOptions, int samDesired, out int phkResult); [DllImport("advapi32.dll", EntryPoint = "RegQueryValueEx")] public static extern int RegQueryValueEx( int hKey, string lpValueName, int lpReserved, ref uint lpType, System.Text.StringBuilder lpData, ref uint lpcbData); [DllImport("Advapi32.dll")] static extern uint RegCloseKey(int hKey); /// /// Returns the value of the specified registry key. /// /// Handle to registry key. /// Name of the registry subkey to be queried. /// Specify 32 or 64 bit value to query /// Name of the registry value. /// Value of specified registry key. public static string GetRegistryKeyValue(UIntPtr rootKey, string keyName, RegSAM is32or64Key, string inPropertyName) { int phkResult = 0; try { uint lResult = RegOpenKeyEx(rootKey, keyName, 0, (int)RegSAM.QueryValue | (int)is32or64Key, out phkResult); if (lResult != 0) { return null; } uint lpType = 0; uint lpcData = 1024; StringBuilder valueBuffer = new StringBuilder(1024); RegQueryValueEx(phkResult, inPropertyName, 0, ref lpType, valueBuffer, ref lpcData); return valueBuffer.ToString(); } finally { if (phkResult != 0) { RegCloseKey(phkResult); } } } /// /// Returns the value of the specified registry key (32-bit app). /// /// Handle to registry key. /// Name of the registry subkey to be queried. /// Name of the registry value. /// Value of specified registry key. public static string GetRegistryKeyvalue_x86(UIntPtr rootKey, string keyName, string inPropertyName) { return GetRegistryKeyValue(rootKey, keyName, RegSAM.WOW64_32Key, inPropertyName); } /// /// Returns the value of the specified registry key (64-bit app). /// /// Handle to registry key. /// Name of the registry subkey to be queried. /// Name of the registry value. /// Value of specified registry key. public static string GetRegistryKeyvalue_x64(UIntPtr rootKey, string keyName, string inPropertyName) { return GetRegistryKeyValue(rootKey, keyName, RegSAM.WOW64_64Key, inPropertyName); } static void ErrorMsg(string msg) { Console.WriteLine("ERROR: " + msg); } /// /// Find the HAPI libraries and add to program environment path. /// /// Returns true if path is found static bool FindAddHAPILibraryToPath() { string appPath = GetHoudiniInstallPath(); if(string.IsNullOrEmpty(appPath)) { ErrorMsg("Houdini or Houdini Engine install path not found. Make sure it is installed. You can also set it via " + HAPI_PATH + " environment variable."); return false; } string binPath = appPath + HAPI_BIN_PATH; // Add path to system path if not already in there string systemPath = System.Environment.GetEnvironmentVariable("PATH", System.EnvironmentVariableTarget.Machine); if (systemPath != "" && !systemPath.Contains(binPath)) { if (systemPath.Length == 0) { systemPath = binPath; } else { systemPath = binPath + ";" + systemPath; } System.Environment.SetEnvironmentVariable("PATH", systemPath, System.EnvironmentVariableTarget.Process); } // Check that HAPI_LIBRARY.dll exists in path bool bFoundLib = false; foreach (string path in systemPath.Split(';')) { if (!System.IO.Directory.Exists(path)) { continue; } string libPath = string.Format("{0}/{1}.dll", path, HAPI_LIBRARY); if (System.IO.File.Exists(libPath)) { bFoundLib = true; } } if(!bFoundLib) { ErrorMsg(HAPI_PATH + " not found!"); } return bFoundLib; } /// /// Returns the Houdini or Houdini Engine installation path. /// Checks both environment and system registry. /// /// Returns path to Houdini installation, or null if not found static string GetHoudiniInstallPath() { string HAPIPath = null; // Look up in environment variable for HAPIPath = System.Environment.GetEnvironmentVariable(HAPI_PATH, System.EnvironmentVariableTarget.Machine); if (HAPIPath == null || HAPIPath.Length == 0) { HAPIPath = System.Environment.GetEnvironmentVariable(HAPI_PATH, System.EnvironmentVariableTarget.User); } if (HAPIPath == null || HAPIPath.Length == 0) { HAPIPath = System.Environment.GetEnvironmentVariable(HAPI_PATH, System.EnvironmentVariableTarget.Process); } if (HAPIPath == null || HAPIPath.Length == 0) { // HAPI_PATH not set. Look in registry. string[] houdiniAppNames = { "Houdini Engine", "Houdini" }; foreach (string appName in houdiniAppNames) { HAPIPath = GetHoudiniRegistryPath(appName); if(!string.IsNullOrEmpty(HAPIPath)) { break; } } } return HAPIPath; } /// /// Get the given application's path from registry, if available. Otherwise rturns null. /// /// /// Returns path to Houdini installation in registry or null if not found. static string GetHoudiniRegistryPath(string appName) { // Note the extra 0 for the "minor-minor" version that's needed here. string versionKey = HOUDINI_MAJOR + "." + HOUDINI_MINOR + "." + "0" + "." + HOUDINI_BUILD; string appPath = GetRegistryKeyvalue_x64(HKEY_LOCAL_MACHINE, SIDEFX_SOFTWARE_REGISTRY + appName, versionKey); if (appPath == null || appPath.Length == 0) { // Try 32-bit entry (for Steam builds) appPath = GetRegistryKeyvalue_x86(HKEY_LOCAL_MACHINE, SIDEFX_SOFTWARE_REGISTRY + appName, versionKey); if (appPath == null || appPath.Length == 0) { return null; } } if (appPath.EndsWith("\\") || appPath.EndsWith("/")) { appPath = appPath.Remove(appPath.Length - 1); } return appPath; } // HAPI DATA STRUCTURES --------------------------------------------------------------------------------------- // Redefine the HAPI data structures in C#. // Note that these might need updating whenever HOUDINI_ENGINE_MAJOR, HOUDINI_ENGINE_MINOR, HOUDINI_ENGINE_API change. // TODO: Add more data structures as needed from: /toolkit/include/HAPI/HAPI_Common.h public enum HAPI_License { HAPI_LICENSE_NONE, HAPI_LICENSE_HOUDINI_ENGINE, HAPI_LICENSE_HOUDINI, HAPI_LICENSE_HOUDINI_FX, HAPI_LICENSE_HOUDINI_ENGINE_INDIE, HAPI_LICENSE_HOUDINI_INDIE, HAPI_LICENSE_MAX }; public enum HAPI_StatusType { HAPI_STATUS_CALL_RESULT, HAPI_STATUS_COOK_RESULT, HAPI_STATUS_COOK_STATE, HAPI_STATUS_MAX }; public enum HAPI_StatusVerbosity { HAPI_STATUSVERBOSITY_0, HAPI_STATUSVERBOSITY_1, HAPI_STATUSVERBOSITY_2, HAPI_STATUSVERBOSITY_ALL = HAPI_STATUSVERBOSITY_2, /// Used for Results. /// @{ HAPI_STATUSVERBOSITY_ERRORS = HAPI_STATUSVERBOSITY_0, HAPI_STATUSVERBOSITY_WARNINGS = HAPI_STATUSVERBOSITY_1, HAPI_STATUSVERBOSITY_MESSAGES = HAPI_STATUSVERBOSITY_2 /// @} }; public enum HAPI_Result { HAPI_RESULT_SUCCESS = 0, HAPI_RESULT_FAILURE = 1, HAPI_RESULT_ALREADY_INITIALIZED = 2, HAPI_RESULT_NOT_INITIALIZED = 3, HAPI_RESULT_CANT_LOADFILE = 4, HAPI_RESULT_PARM_SET_FAILED = 5, HAPI_RESULT_INVALID_ARGUMENT = 6, HAPI_RESULT_CANT_LOAD_GEO = 7, HAPI_RESULT_CANT_GENERATE_PRESET = 8, HAPI_RESULT_CANT_LOAD_PRESET = 9, HAPI_RESULT_ASSET_DEF_ALREADY_LOADED = 10, HAPI_RESULT_NO_LICENSE_FOUND = 110, HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND = 120, HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_C_LICENSE = 130, HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_LC_LICENSE = 140, HAPI_RESULT_DISALLOWED_LC_ASSET_WITH_C_LICENSE = 150, HAPI_RESULT_DISALLOWED_HENGINEINDIE_W_3PARTY_PLUGIN = 160, HAPI_RESULT_ASSET_INVALID = 200, HAPI_RESULT_NODE_INVALID = 210, HAPI_RESULT_USER_INTERRUPTED = 300, HAPI_RESULT_INVALID_SESSION = 400 }; [Flags] public enum HAPI_ErrorCode { HAPI_ERRORCODE_ASSET_DEF_NOT_FOUND = 1 << 0, HAPI_ERRORCODE_PYTHON_NODE_ERROR = 1 << 1 }; public enum HAPI_SessionType { HAPI_SESSION_INPROCESS, HAPI_SESSION_THRIFT, HAPI_SESSION_CUSTOM1, HAPI_SESSION_CUSTOM2, HAPI_SESSION_CUSTOM3, HAPI_SESSION_MAX }; public enum HAPI_State { HAPI_STATE_READY, HAPI_STATE_READY_WITH_FATAL_ERRORS, HAPI_STATE_READY_WITH_COOK_ERRORS, HAPI_STATE_STARTING_COOK, HAPI_STATE_COOKING, HAPI_STATE_STARTING_LOAD, HAPI_STATE_LOADING, HAPI_STATE_MAX, HAPI_STATE_MAX_READY_STATE = HAPI_STATE_READY_WITH_COOK_ERRORS }; public enum HAPI_PackedPrimInstancingMode { HAPI_PACKEDPRIM_INSTANCING_MODE_INVALID = -1, HAPI_PACKEDPRIM_INSTANCING_MODE_DISABLED, HAPI_PACKEDPRIM_INSTANCING_MODE_HIERARCHY, HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT, HAPI_PACKEDPRIM_INSTANCING_MODE_MAX }; [StructLayout(LayoutKind.Sequential)] [Serializable] public struct HAPI_Session { /// The type of session detemines the which implementation will be /// used to communicate with the Houdini Engine library. public HAPI_SessionType type; /// Some session types support multiple simultanous sessions. This means /// that each session needs to have a unique identified. public HAPI_SessionId id; }; /// Options to configure a Thrift server being started from HARC. public struct HAPI_ThriftServerOptions { /// Close the server automatically when all clients disconnect from it. [MarshalAs(UnmanagedType.U1)] public bool autoClose; /// Timeout in milliseconds for waiting on the server to /// signal that it's ready to serve. If the server fails /// to signal within this time interval, the start server call fails /// and the server process is terminated. [MarshalAs(UnmanagedType.R4)] public float timeoutMs; }; [StructLayout(LayoutKind.Sequential)] public struct HAPI_CookOptions { /// Normally, geos are split into parts in two different ways. First it /// is split by group and within each group it is split by primitive type. /// /// For example, if you have a geo with group1 covering half of the mesh /// and volume1 and group2 covering the other half of the mesh, all of /// curve1, and volume2 you will end up with 5 parts. First two parts /// will be for the half-mesh of group1 and volume1, and the last three /// will cover group2. /// /// This toggle lets you disable the splitting by group and just have /// the geo be split by primitive type alone. By default, this is true /// and therefore geos will be split by group and primitive type. If /// set to false, geos will only be split by primtive type. [MarshalAs(UnmanagedType.U1)] public bool splitGeosByGroup; /// For meshes only, this is enforced by convexing the mesh. Use -1 /// to avoid convexing at all and get some performance boost. public int maxVerticesPerPrimitive; // Curves [MarshalAs(UnmanagedType.U1)] public bool refineCurveToLinear; public float curveRefineLOD; /// If this option is turned on, then we will recursively clear the /// errors and warnings (and messages) of all nodes before performing /// the cook. [MarshalAs(UnmanagedType.U1)] public bool clearErrorsAndWarnings; /// Decide whether to recursively cook all templated geos or not. [MarshalAs(UnmanagedType.U1)] public bool cookTemplatedGeos; /// Decide whether to split points by vertex attributes. This takes /// all vertex attributes and tries to copy them to their respective /// points. If two vertices have any difference in their attribute values, /// the corresponding point is split into two points. This is repeated /// until all the vertex attributes have been copied to the points. /// /// With this option enabled, you can reduce the total number of vertices /// on a game engine side as sharing of attributes (like UVs) is optimized. /// To make full use of this feature, you have to think of Houdini points /// as game engine vertices (sharable). With this option OFF (or before /// this feature existed) you had to map Houdini vertices to game engine /// vertices, to make sure all attribute values are accounted for. [MarshalAs(UnmanagedType.U1)] public bool splitPointsByVertexAttributes; /// Choose how you want the cook to handle packed primitives. public HAPI_PackedPrimInstancingMode packedPrimInstancingMode; /// Choose which special part types should be handled. Unhandled special /// part types will just be refined to ::HAPI_PARTTYPE_MESH. [MarshalAs(UnmanagedType.U1)] public bool handleBoxPartTypes; [MarshalAs(UnmanagedType.U1)] public bool handleSpherePartTypes; /// If enabled, sets the ::HAPI_PartInfo::hasChanged member during the /// cook. If disabled, the member will always be true. Checking for /// part changes can be expensive, so there is a potential performance /// gain when disabled. [MarshalAs(UnmanagedType.U1)] public bool checkPartChanges; /// For internal use only. :) public int extraFlags; } // HAPI DLLIMPORTS -------------------------------------------------------------------------------------------------- // Import required HAPI functions // Note that these might need updating whenever HOUDINI_ENGINE_MAJOR, HOUDINI_ENGINE_MINOR, HOUDINI_ENGINE_API change. // TODO: Add more imports as needed from: /toolkit/include/HAPI/HAPI.h [DllImport(HAPI_LIBRARY, CallingConvention = CallingConvention.Cdecl)] public static extern HAPI_Result HAPI_StartThriftNamedPipeServer(ref HAPI_ThriftServerOptions options, string pipe_name, out int process_id); [DllImport(HAPI_LIBRARY, CallingConvention = CallingConvention.Cdecl)] public static extern HAPI_Result HAPI_CreateThriftNamedPipeSession(out HAPI_Session session, string pipe_name); [DllImport(HAPI_LIBRARY, CallingConvention = CallingConvention.Cdecl)] public static extern HAPI_Result HAPI_IsSessionValid(ref HAPI_Session session); [DllImport(HAPI_LIBRARY, CallingConvention = CallingConvention.Cdecl)] public static extern HAPI_Result HAPI_CloseSession(ref HAPI_Session session); [DllImport(HAPI_LIBRARY, CallingConvention = CallingConvention.Cdecl)] public static extern HAPI_Result HAPI_IsInitialized(ref HAPI_Session session); [DllImport(HAPI_LIBRARY, CallingConvention = CallingConvention.Cdecl)] public static extern HAPI_Result HAPI_Initialize( ref HAPI_Session session, ref HAPI_CookOptions cook_options, [MarshalAs(UnmanagedType.U1)] bool use_cooking_thread, int cooking_thread_stack_size, string houdini_environment_files, string otl_search_path, string dso_search_path, string image_dso_search_path, string audio_dso_search_path); [DllImport(HAPI_LIBRARY, CallingConvention = CallingConvention.Cdecl)] public static extern HAPI_Result HAPI_Cleanup(ref HAPI_Session session); } }