HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
HOM/_hdk_sample_hom_extensions.C
/*
* Copyright (c) 2024
* Side Effects Software Inc. All rights reserved.
*
* Redistribution and use of Houdini Development Kit samples in source and
* binary forms, with or without modification, are permitted provided that the
* following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. The name of Side Effects Software may not be used to endorse or
* promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*----------------------------------------------------------------------------
*/
// This file contains the source for a Python C extension module
// that is used by the ObjNode_setSelectable.C HDK sample.
// The module declares one function, ObjNode_setSelectable.
// Include the Python header for PyMODINIT_FUNC.
// The Python developers recommend that Python.h be included before
// any other header files.
#include <Python.h>
// Include Houdini's equivalent of python/Python.h. It creates wrappers of
// Python's C API functions, so that Houdini can determine at runtime which
// version of Python to use. This file needs to be included first to avoid
// gcc warnings. See PY_CPythonAPI.h for more information about the API
// wrapper. Note that you are limited to the Python API functions wrapped by
// PY_CPythonAPI.h.
// We include this file for HOM_Error and other HOM declarations.
#include <HOM/HOM_Module.h>
// This file contains functions that will run arbitrary Python code:
#include <PY/PY_Python.h>
// The class defined in this file acquires the GIL within Houdini's Python
// runtime environment:
// This file defines a convenience class to automatically decrement Python
// reference counts:
// This class in this file is Houdini's internal representation of an object
// node:
#include <OBJ/OBJ_Node.h>
// This file defines OPgetDirector(), which returns the root ("/") node.
#include <OP/OP_Director.h>
// This file defines a function to demangle a C++ class name from the result
// returned by type_info::name():
// This file lets us start a undo block.
static void
ObjNode_setSelectable(const char *node_path, bool selectable)
{
// This is the function that does the actual work. It takes a path
// to the object node and a flag to say if it should be selectable.
//
// Look up the OBJ_Node for the path.
OP_Node *op_node = OPgetDirector()->findNode(node_path);
if (!op_node)
throw HOM_OperationFailed("Internal error (could not find node)");
OBJ_Node *obj_node = op_node->castToOBJNode();
if (!obj_node)
throw HOM_OperationFailed("Internal error (node is not an object)");
// Now that we have the OBJ_Node object, we can access anything exposed
// by the HDK. For this example, we simply need to call setPickable().
// First, though, we create an undo block, so that any operations called
// by setPickable will be put into this block.
UT_AutoUndoBlock undo_block("Setting selectable flag", ANYLEVEL);
// If this node is inside a locked asset, we want to raise a Python
// hou.PermissionError exception. To do that, we simply throw an instance
// of the C++ HOM_PermissionError class.
if (!obj_node->canAccess(PRM_WRITE_OK))
throw HOM_PermissionError();
obj_node->setPickable(selectable);
}
static PY_PyObject *
createHouException(
const char *exception_class_name, const char *instance_message,
PY_PyObject *&exception_class)
{
// Create an instance of the given exception class from the hou
// module, passing the instance message into the exeption class's
// __init__ method. This function returns a new exception instance, or
// NULL if an error occurred. The class is returned in exception_class
// and is a borrowed reference.
exception_class = NULL;
// Note that a PY_AutoObject class is just a wrapper around a
// PY_PyObject pointer that will call PY_Py_XDECREF when the it's destroyed.
// We use it for Python API functions that return new object instances.
// Because this HDK extension is installed after the hou module is
// imported, we can be sure that we can be sure hou_module won't be null.
PY_AutoObject hou_module(PY_PyImport_ImportModule("hou"));
// Look up the exception by name in the module's dictionary. Note that
// PY_PyModule_GetDict returns a borrowed reference and that it never
// returns NULL. PY_PyDict_GetItemString also returns a borrowed
// reference.
PY_PyObject *hou_module_dict = PY_PyModule_GetDict(hou_module);
exception_class = PY_PyDict_GetItemString(
hou_module_dict, exception_class_name);
// PY_PyDict_GetItemString doesn't set a Python exception, so we are careful
// to set it ourselves if the class name isn't valid.
if (!exception_class)
{
PY_PyErr_SetString(
PY_PyExc_RuntimeError(),
"Could not find exception class in hou module");
return NULL;
}
// Create an instance of the exception. First create a tuple containing
// the arguments to __init__.
PY_AutoObject args(PY_Py_BuildValue("(s)", instance_message));
if (!args)
return NULL;
return PY_PyObject_Call(exception_class, args, /*kwargs=*/NULL);
}
static PY_PyObject *
ObjNode_setSelectable_Wrapper(PY_PyObject *self, PY_PyObject *args)
{
// This is a wrapper that is called from the Python runtime engine. It
// translates the Python arguments to C/C++ ones, calls a function to do
// the actual work, and converts exceptions raised by that function into
// Python exceptions.
//
// Note that you could also use swig to automatically generate wrapper
// functions like this.
//
// Since this function is called from the Python runtime engine, we
// don't need to manually acquire the Python global interpreter lock (GIL).
// First extract the arguments: a string and an integer (bool).
const char *node_path;
int selectable;
if (!PY_PyArg_ParseTuple(args, "si", &node_path, &selectable))
return NULL;
// Now call ObjNode_setSelectable to do the actual work, taking care
// of the locking and exception handling here.
try
{
// If this code is called from a thread other than the main thread,
// creating a HOM_AutoLock instance will lock, waiting until Houdini
// is sitting idle in its event loop. This way, we can be sure that
// any code that accesses Houdini's internal state is threadsafe.
HOM_AutoLock hom_lock;
// Call the wrapped function to do the actual work.
ObjNode_setSelectable(node_path, (bool)selectable);
// Return PY_Py_None to indicate that no error occurred. If your
// wrapped function returns a value, you'll need to convert it into
// a Python object here.
return PY_Py_None();
}
catch (HOM_Error &error)
{
// The exceptions used by the hou module are subclasses of HOM_Error
// (and can be found in HOM_Errors.h). We use RTTI to get the class
// name, remove the "HOM_" prefix, and look up the corresponding
// exception class in the hou Python module.
typeid(error).name());
if (exception_class_name.find("HOM_") == 0)
exception_class_name = exception_class_name.substr(4);
// Note that a PY_AutoObject class is just a wrapper around a
// PY_PyObject pointer that will call PY_Py_XDECREF when the it's
// destroyed.
PY_PyObject *exception_class;
PY_AutoObject exception_instance(createHouException(
exception_class_name.c_str(), error.instanceMessage().c_str(),
exception_class));
if (!exception_instance)
return NULL;
// Set the exception and return NULL so Python knows an exception was
// raised.
PY_PyErr_SetObject(exception_class, exception_instance);
return NULL;
}
}
#if defined(WIN32)
PyMODINIT_FUNC
#else
// This is a replacement of PyMODINIT_FUNC but with the default visibility
// attribute declaration injected in the middle.
extern "C" __attribute__((visibility("default"))) PyObject*
#endif
PyInit__hdk_sample_hom_extensions(void)
{
//
// This is the initialization function that Python calls when
// importing the extension module.
//
PY_PyObject *module = nullptr;
{
// A PY_InterpreterAutoLock will grab the Python global interpreter
// lock (GIL). It's important that we have the GIL before making
// any calls into the Python API.
PY_InterpreterAutoLock interpreter_auto_lock;
// We'll create a new module named "_hom_extensions", and add functions
// to it. We don't give a docstring here because it's given in the
// Python implementation below.
static PY_PyMethodDef hom_extension_methods[] = {
{"ObjNode_setSelectable", ObjNode_setSelectable_Wrapper,
PY_METH_VARARGS(), ""},
{ NULL, NULL, 0, NULL }
};
module = PY_Py_InitModule(
"_hdk_sample_hom_extensions", hom_extension_methods);
}
return reinterpret_cast<PyObject *>(module);
}