00001 /* 00002 * Copyright (c) 2012 00003 * Side Effects Software Inc. All rights reserved. 00004 * 00005 * Redistribution and use of Houdini Development Kit samples in source and 00006 * binary forms, with or without modification, are permitted provided that the 00007 * following conditions are met: 00008 * 1. Redistributions of source code must retain the above copyright notice, 00009 * this list of conditions and the following disclaimer. 00010 * 2. The name of Side Effects Software may not be used to endorse or 00011 * promote products derived from this software without specific prior 00012 * written permission. 00013 * 00014 * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 00015 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 00016 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 00017 * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 00018 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 00019 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 00020 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 00021 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 00022 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 00023 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 00024 * 00025 *---------------------------------------------------------------------------- 00026 */ 00027 00028 // This sample shows how to extend HOM using C++. It adds a 00029 // hou.ObjNode.setSelectable method that makes an object node selectable/ 00030 // unselectable in the viewport. 00031 00032 // Include Houdini's equivalent of python/Python.h. It creates wrappers of 00033 // Python's C API functions, so that Houdini can determine at runtime which 00034 // version of Python to use. This file needs to be included first to avoid 00035 // gcc warnings. See PY_CPythonAPI.h for more information about the API 00036 // wrapper. 00037 #include <PY/PY_CPythonAPI.h> 00038 00039 // This file is needed for all HDK plugins: 00040 #include <UT/UT_DSOVersion.h> 00041 00042 // We include this file only to get the forward declaration for 00043 // HOMextendLibrary(). See below for details. 00044 #include <HOM/HOM_Module.h> 00045 00046 // This file contains functions that will run arbitrary Python code: 00047 #include <PY/PY_Python.h> 00048 00049 // The class defined in this file acquires the GIL within Houdini's Python 00050 // runtime environment: 00051 #include <PY/PY_InterpreterAutoLock.h> 00052 00053 // This file defines a convenience class to automatically decrement Python 00054 // reference counts: 00055 #include <PY/PY_AutoObject.h> 00056 00057 // This class in this file is Houdini's internal representation of an object 00058 // node: 00059 #include <OBJ/OBJ_Node.h> 00060 00061 // This file defines OPgetDirector(), which returns the root ("/") node. 00062 #include <OP/OP_Director.h> 00063 00064 // This file defines a function to demangle a C++ class name from the result 00065 // returned by type_info::name(): 00066 #include <UT/UT_SysSpecific.h> 00067 00068 // This file lets us start a undo block. 00069 #include <UT/UT_UndoManager.h> 00070 00071 static void 00072 ObjNode_setSelectable(const char *node_path, bool selectable) 00073 throw(HOM_OperationFailed, HOM_PermissionError) 00074 { 00075 // This is the function that does the actual work. It takes a path 00076 // to the object node and a flag to say if it should be selectable. 00077 // 00078 // Look up the OBJ_Node for the path. 00079 OP_Node *op_node = OPgetDirector()->findNode(node_path); 00080 if (!op_node) 00081 throw HOM_OperationFailed("Internal error (could not find node)"); 00082 00083 OBJ_Node *obj_node = op_node->castToOBJNode(); 00084 if (!obj_node) 00085 throw HOM_OperationFailed("Internal error (node is not an object)"); 00086 00087 // Now that we have the OBJ_Node object, we can access anything exposed 00088 // by the HDK. For this example, we simply need to call setPickable(). 00089 // First, though, we create an undo block, so that any operations called 00090 // by setPickable will be put into this block. 00091 UT_AutoUndoBlock undo_block("Setting selectable flag", ANYLEVEL); 00092 00093 // If this node is inside a locked asset, we want to raise a Python 00094 // hou.PermissionError exception. To do that, we simply throw an instance 00095 // of the C++ HOM_PermissionError class. 00096 if (!obj_node->canAccessFlag(PRM_WRITE_OK, OP_PICK_FLAG)) 00097 throw HOM_PermissionError(); 00098 00099 obj_node->setPickable(selectable); 00100 } 00101 00102 static PY_PyObject * 00103 createHouException( 00104 const char *exception_class_name, const char *instance_message, 00105 PY_PyObject *&exception_class) 00106 { 00107 // Create an instance of the given exception class from the hou 00108 // module, passing the instance message into the exeption class's 00109 // __init__ method. This function returns a new exception instance, or 00110 // NULL if an error occurred. The class is returned in exception_class 00111 // and is a borrowed reference. 00112 exception_class = NULL; 00113 00114 // Note that a PY_AutoObject class is just a wrapper around a 00115 // PY_PyObject pointer that will call PY_Py_XDECREF when the it's destroyed. 00116 // We use it for Python API functions that return new object instances. 00117 // Because this HDK extension is installed after the hou module is 00118 // imported, we can be sure that we can be sure hou_module won't be null. 00119 PY_AutoObject hou_module(PY_PyImport_ImportModule("hou")); 00120 00121 // Look up the exception by name in the module's dictionary. Note that 00122 // PY_PyModule_GetDict returns a borrowed reference and that it never 00123 // returns NULL. PY_PyDict_GetItemString also returns a borrowed 00124 // reference. 00125 PY_PyObject *hou_module_dict = PY_PyModule_GetDict(hou_module); 00126 exception_class = PY_PyDict_GetItemString( 00127 hou_module_dict, exception_class_name); 00128 00129 // PY_PyDict_GetItemString doesn't set a Python exception, so we are careful 00130 // to set it ourselves if the class name isn't valid. 00131 if (!exception_class) 00132 { 00133 PY_PyErr_SetString( 00134 PY_PyExc_RuntimeError(), 00135 "Could not find exception class in hou module"); 00136 return NULL; 00137 } 00138 00139 // Create an instance of the exception. First create a tuple containing 00140 // the arguments to __init__. 00141 PY_AutoObject args(PY_Py_BuildValue("(s)", instance_message)); 00142 if (!args) 00143 return NULL; 00144 00145 return PY_PyObject_Call(exception_class, args, /*kwargs=*/NULL); 00146 } 00147 00148 static PY_PyObject * 00149 ObjNode_setSelectable_Wrapper(PY_PyObject *self, PY_PyObject *args) 00150 { 00151 // This is a wrapper that is called from the Python runtime engine. It 00152 // translates the Python arguments to C/C++ ones, calls a function to do 00153 // the actual work, and converts exceptions raised by that function into 00154 // Python exceptions. 00155 // 00156 // Note that you could also use swig to automatically generate wrapper 00157 // functions like this. 00158 // 00159 // Since this function is called from the Python runtime engine, we 00160 // don't need to manually acquire the Python global interpreter lock (GIL). 00161 00162 // First extract the arguments: a string and an integer (bool). 00163 const char *node_path; 00164 int selectable; 00165 if (!PY_PyArg_ParseTuple(args, "si", &node_path, &selectable)) 00166 return NULL; 00167 00168 // Now call ObjNode_setSelectable to do the actual work, taking care 00169 // of the locking and exception handling here. 00170 try 00171 { 00172 // If this code is called from a thread other than the main thread, 00173 // creating a HOM_AutoLock instance will lock, waiting until Houdini 00174 // is sitting idle in its event loop. This way, we can be sure that 00175 // any code that accesses Houdini's internal state is threadsafe. 00176 HOM_AutoLock hom_lock; 00177 00178 // Call the wrapped function to do the actual work. 00179 ObjNode_setSelectable(node_path, (bool)selectable); 00180 00181 // Return PY_Py_None to indicate that no error occurred. If your 00182 // wrapped function returns a value, you'll need to convert it into 00183 // a Python object here. 00184 return PY_Py_None(); 00185 } 00186 catch (HOM_Error &error) 00187 { 00188 // The exceptions used by the hou module are subclasses of HOM_Error 00189 // (and can be found in HOM_Errors.h). We use RTTI to get the class 00190 // name, remove the "HOM_" prefix, and look up the corresponding 00191 // exception class in the hou Python module. 00192 std::string exception_class_name = UTunmangleClassNameFromTypeIdName( 00193 typeid(error).name()); 00194 if (exception_class_name.find("HOM_") == 0) 00195 exception_class_name = exception_class_name.substr(4); 00196 00197 // Note that a PY_AutoObject class is just a wrapper around a 00198 // PY_PyObject pointer that will call PY_Py_XDECREF when the it's 00199 // destroyed. 00200 PY_PyObject *exception_class; 00201 PY_AutoObject exception_instance(createHouException( 00202 exception_class_name.c_str(), error.instanceMessage().c_str(), 00203 exception_class)); 00204 if (!exception_instance) 00205 return NULL; 00206 00207 // Set the exception and return NULL so Python knows an exception was 00208 // raised. 00209 PY_PyErr_SetObject(exception_class, exception_instance); 00210 return NULL; 00211 } 00212 } 00213 00214 void 00215 HOMextendLibrary() 00216 { 00217 // This function installs the C++ HOM extension. When the hou module is 00218 // first imported, Houdini will call functions named HOMextendLibrary in 00219 // HDK dso's. This function is declared in an extern "C" in HOM_Module.h. 00220 00221 { 00222 // A PY_InterpreterAutoLock will grab the Python global interpreter 00223 // lock (GIL). It's important that we have the GIL before making 00224 // any calls into the Python API. 00225 PY_InterpreterAutoLock interpreter_auto_lock; 00226 00227 // We'll create a new module named "_hom_extensions", and add functions 00228 // to it. We don't give a docstring here because it's given in the 00229 // Python implementation below. 00230 static PY_PyMethodDef hom_extension_methods[] = { 00231 {"ObjNode_setSelectable", ObjNode_setSelectable_Wrapper, 00232 PY_METH_VARARGS(), ""}, 00233 { NULL, NULL, 0, NULL } 00234 }; 00235 00236 PY_Py_InitModule("_hom_extensions", hom_extension_methods); 00237 } 00238 00239 // Run some Python code to add a method to the hou.ObjectNode class that 00240 // will call our custom function. We create a Python function 00241 // that takes in a hou.ObjNode instance. That function calls path() 00242 // on the instance to get the full path to the object node, and passes 00243 // that path and other arguments to the function in the _hom_extensions 00244 // module. Then that function is assigned to a method in hou.ObjNode 00245 // and the function's name is removed from the global dictionary. 00246 PY_Result result = PYrunPythonStatements( 00247 "def _setSelectable(self, selectable):\n" 00248 " '''Make this node selectable/unselectable in the viewport\n" 00249 " and network editor.'''\n" 00250 " import _hom_extensions\n" 00251 " _hom_extensions.ObjNode_setSelectable(self.path(), selectable)\n" 00252 "__import__('hou').ObjNode.setSelectable = _setSelectable\n" 00253 "del _setSelectable\n"); 00254 if (result.myResultType == PY_Result::ERR) 00255 cerr << "Could not install Python function:\n" 00256 << result.myDetailedErrValue << endl; 00257 } 00258
1.5.9