Houdini Engine是Houdini的一个无图形界面简化版,可以通过houdini engine与houdini内核交互,完成houdini fx中的操作。官方有For Maya, For Max, For Unity, For Unreal四个插件,可以在这四个host程序中调用houdini engine。这是极好的,意味着开发的HDA资源可以直接应用在host程序中。
不过笔者并不满足于此,是否可以不用host环境直接调用houdini engine?当然可以,官方提供了一套C API . 只不过笔者看着它就头疼,主要两个问题
- 面向过程,不面向对象
- 语法繁琐
这也是笔者开发pyhapi的初衷,希望面向对象,简化语法。
文档请见 https://pyhapi.readthedocs.io
如何安装
已经发布到PyPI,安装直接使用pip即可
1 |
pip install pyhapi |
要求
- py>3.6, 因为用了asyncio异步库
- numpy>1.15
- Houdini17.5测试没问题,需要commercial license. Houdini18尚未测试
当然要注意的是,请确保houdini dll路径在PATH中
使用
目前支持的功能:
- 实例化节点,HDA
- 节点连接操作
- 节点参数设置
- 节点异步cook
- 传入/出 曲线,模型
还不支持的:
- 传入/出 体素
- PDG
举例来说,如果想实例化一个HDA,传入参数,cook,可以这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# -*- coding: utf-8 -*- """Example of getting/setting params of HDA asset Author : Maajor Email : hello_myd@126.com """ import pyhapi as ph def main(): session = ph.HSessionManager.get_or_create_default_session() #load hda asset and instantiate hda_asset = ph.HAsset(session, "hda/SideFX_spaceship.otl") asset_node = hda_asset.instantiate(node_name="Spaceship") #Set node's parameters asset_node.set_param_value("seed", 1.0) asset_node.set_param_value("rop_geometry1_sopoutput", "$HIP/spaceship.obj") #Press button asset_node.press_button("rop_geometry1_execute", status_report_interval=1.0) session.save_hip("spaceship.hip") if __name__ == "__main__": main() |
执行完以后就可以输出spaceship.obj模型啦
对比一下C API的官方案例Parameters.c两百多行其实就干了一件事:实例化一个HDA,打印出它所有参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
#include <HAPI/HAPI.h> #include <iostream> #include <string> #define ENSURE_SUCCESS( result ) \ if ( (result) != HAPI_RESULT_SUCCESS ) \ { \ std::cout << "Failure at " << __FILE__ << ": " << __LINE__ << std::endl; \ std::cout << getLastError() << std::endl; \ exit( 1 ); \ } #define ENSURE_COOK_SUCCESS( result ) \ if ( (result) != HAPI_RESULT_SUCCESS ) \ { \ std::cout << "Failure at " << __FILE__ << ": " << __LINE__ << std::endl; \ std::cout << getLastCookError() << std::endl; \ exit( 1 ); \ } static std::string getLastError(); static std::string getLastCookError(); static std::string getString( HAPI_StringHandle stringHandle ); int main( int argc, char ** argv ) { const char * hdaFile = argc == 2 ? argv[ 1 ] : "examples/SideFX_spaceship.otl"; HAPI_CookOptions cookOptions = HAPI_CookOptions_Create(); HAPI_Session session; HAPI_CreateInProcessSession( &session ); ENSURE_SUCCESS( HAPI_Initialize( &session, &cookOptions, true, -1, nullptr, nullptr, nullptr, nullptr, nullptr ) ); HAPI_AssetLibraryId assetLibId; ENSURE_SUCCESS( HAPI_LoadAssetLibraryFromFile( &session, hdaFile, true, &assetLibId ) ); int assetCount; ENSURE_SUCCESS( HAPI_GetAvailableAssetCount( &session, assetLibId, &assetCount ) ); if (assetCount > 1) { std::cout << "Should only be loading 1 asset here" << std::endl; exit ( 1 ); } HAPI_StringHandle assetSh; ENSURE_SUCCESS( HAPI_GetAvailableAssets( &session, assetLibId, &assetSh, assetCount ) ); std::string assetName = getString( assetSh ); HAPI_NodeId nodeId; ENSURE_SUCCESS( HAPI_CreateNode( &session, -1, assetName.c_str(), "AnAsset", false, &nodeId ) ); ENSURE_SUCCESS( HAPI_CookNode( &session, nodeId, &cookOptions ) ); int cookStatus; HAPI_Result cookResult; do { cookResult = HAPI_GetStatus( &session, HAPI_STATUS_COOK_STATE, &cookStatus ); } while (cookStatus > HAPI_STATE_MAX_READY_STATE && cookResult == HAPI_RESULT_SUCCESS); ENSURE_SUCCESS( cookResult ); ENSURE_COOK_SUCCESS( cookStatus ); HAPI_NodeInfo nodeInfo; ENSURE_SUCCESS( HAPI_GetNodeInfo( &session, nodeId, &nodeInfo ) ); HAPI_ParmInfo * parmInfos = new HAPI_ParmInfo[ nodeInfo.parmCount ]; ENSURE_SUCCESS( HAPI_GetParameters( &session, nodeId, parmInfos, 0, nodeInfo.parmCount ) ); // Print parameter info std::cout << "Parameters: " << std::endl; for( int i = 0; i < nodeInfo.parmCount; ++i ) { std::cout << "==========" << std::endl; std::cout << " Name: " << getString( parmInfos[ i ].nameSH ) << std::endl; std::cout << " Values: ("; if ( HAPI_ParmInfo_IsInt( &parmInfos[ i ] ) ) { int parmIntCount = HAPI_ParmInfo_GetIntValueCount( &parmInfos[ i ] ); int * parmIntValues = new int[ parmIntCount ]; ENSURE_SUCCESS( HAPI_GetParmIntValues( &session, nodeId, parmIntValues, parmInfos[ i ].intValuesIndex, parmIntCount ) ); for ( int v = 0; v < parmIntCount; ++v ) { if ( v != 0 ) std::cout << ", "; std::cout << parmIntValues[ v ]; } delete [] parmIntValues; } else if ( HAPI_ParmInfo_IsFloat( &parmInfos[ i ] ) ) { int parmFloatCount = HAPI_ParmInfo_GetFloatValueCount( &parmInfos[ i ] ); float * parmFloatValues = new float[ parmFloatCount ]; ENSURE_SUCCESS( HAPI_GetParmFloatValues( &session, nodeId, parmFloatValues, parmInfos[ i ].floatValuesIndex, parmFloatCount ) ); for ( int v = 0; v < parmFloatCount; ++v ) { if ( v != 0 ) std::cout << ", "; std::cout << parmFloatValues[ v ]; } delete [] parmFloatValues; } else if ( HAPI_ParmInfo_IsString( &parmInfos[ i ] ) ) { int parmStringCount = HAPI_ParmInfo_GetStringValueCount( &parmInfos[ i ] ); HAPI_StringHandle * parmSHValues = new HAPI_StringHandle[ parmStringCount ]; ENSURE_SUCCESS( HAPI_GetParmStringValues( &session, nodeId, true, parmSHValues, parmInfos[ i ].stringValuesIndex, parmStringCount ) ); for ( int v = 0; v < parmStringCount; ++v ) { if ( v != 0 ) std::cout << ", "; std::cout << getString( parmSHValues[ v ] ); } delete [] parmSHValues; } std::cout << ")" << std::endl; } delete [] parmInfos; char in; std::cout << "Press any key to exit" << std::endl; std::cin >> in; HAPI_Cleanup( &session ); return 0; } static std::string getLastError() { int bufferLength; HAPI_GetStatusStringBufLength( nullptr, HAPI_STATUS_CALL_RESULT, HAPI_STATUSVERBOSITY_ERRORS, &bufferLength ); char * buffer = new char[ bufferLength ]; HAPI_GetStatusString( nullptr, HAPI_STATUS_CALL_RESULT, buffer, bufferLength ); std::string result( buffer ); delete [] buffer; return result; } static std::string getLastCookError() { int bufferLength; HAPI_GetStatusStringBufLength( nullptr, HAPI_STATUS_COOK_RESULT, HAPI_STATUSVERBOSITY_ERRORS, &bufferLength ); char * buffer = new char[ bufferLength ]; HAPI_GetStatusString( nullptr, HAPI_STATUS_COOK_RESULT, buffer, bufferLength ); std::string result( buffer ); delete[] buffer; return result; } static std::string getString( HAPI_StringHandle stringHandle ) { if ( stringHandle == 0 ) { return ""; } int bufferLength; HAPI_GetStringBufLength( nullptr, stringHandle, &bufferLength ); char * buffer = new char[ bufferLength ]; HAPI_GetString ( nullptr, stringHandle, buffer, bufferLength ); std::string result( buffer ); delete [] buffer; return result; } |
用pyhapi的话十行以内
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import pyhapi as ph def main(): session = ph.HSessionManager.get_or_create_default_session() #load hda asset and instantiate hda_asset = ph.HAsset(session, "hda/SideFX_spaceship.otl") asset_node = hda_asset.instantiate(node_name="Spaceship") #Query node's parameters for name in asset_node.get_param_names(): print("Query param: {0} has value: {1}".format(name, asset_node.get_param_value(name))) if __name__ == "__main__": main() |
当然我们还可以把一个模型/曲线,传入/取出houdini engine,比如mesh_marshall_input.py这个例子,传进去一个方块,然后做了细分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# -*- coding: utf-8 -*- """Example of setting mesh data into hengine Author : Maajor Email : hello_myd@126.com """ import numpy as np import pyhapi as ph def main(): """Main """ session = ph.HSessionManager.get_or_create_default_session() #create an inputnode where you can set geometry geo_inputnode = ph.HInputNode(session, "Cube") #create a geomesh cube_geo = ph.HGeoMesh( vertices=np.array( [[0.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [0.0, 1.0, 1.0], [1.0, 0.0, 0.0], [1.0, 0.0, 1.0], [1.0, 1.0, 0.0], [1.0, 1.0, 1.0]], dtype=np.float32), faces=np.array( [[0, 2, 6, 4], [2, 3, 7, 6], [2, 0, 1, 3], [1, 5, 7, 3], [5, 4, 6, 7], [0, 4, 5, 1]], dtype=np.int32)) #set this geomesh as geometry of inputnode geo_inputnode.set_geometry(cube_geo) #create a node whose input is inputnode ph.HNode(session, "Sop/subdivide", "Cube Subdivider").connect_node_input(geo_inputnode) session.save_hip() if __name__ == "__main__": main() |
内部实现
Python可以和C很好地实现交互。类似其他几个平台:Unity 用的c#,dllimport就行;Unreal就是cpp,include进来就行
Python用ctypes就能加载dll
1 2 3 4 5 |
sys = platform.system() if sys == "Windows": HAPI_LIB = ctypes.cdll.LoadLibrary("libHAPIL") elif sys == "Linux": HAPI_LIB = ctypes.cdll.LoadLibrary("libHAPIL.so") |
这就直接可以调用libHAPIL的方法了
1 |
HAPI_LIB.HAPI_IsSessionValid(byref(session)) |
源码里hapi.py里全部是对libHAPIL的封装,hdata.py里全是houdini engine的struct
除此以外,hsession封装session对象,hnode封装node对象,hgeo封装geo对象,hasset封装HDA对象。总共就这六个文件
可以干什么
为了Houdini Pipeline咯
可以做:
- 资产自动化处理
- HDA自动化测试
- HDA管理
- etc…
最后, 祝读者春节快乐,百病不侵!
参考资料
Houdini Engine API Documentation
Houdini Engine API入門 日本人jyouryuusui写的,作者介绍了下API用法,写了个Qt UI调用Houdini Engine C API
Intro to Houdini Engine Scripting API 官网上2014年的教程,有点老,当时还是Houdini13

牛!!!