HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
_hdk_sample_hom_extensions.C
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2025
3  * Side Effects Software Inc. All rights reserved.
4  *
5  * Redistribution and use of Houdini Development Kit samples in source and
6  * binary forms, with or without modification, are permitted provided that the
7  * following conditions are met:
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  * 2. The name of Side Effects Software may not be used to endorse or
11  * promote products derived from this software without specific prior
12  * written permission.
13  *
14  * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS
15  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
17  * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
19  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
20  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
21  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
23  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  *
25  *----------------------------------------------------------------------------
26  */
27 
28 // This file contains the source for a Python C extension module
29 // that is used by the ObjNode_setSelectable.C HDK sample.
30 // The module declares one function, ObjNode_setSelectable.
31 
32 // Include Houdini's equivalent of python/Python.h. It creates wrappers of
33 // Python's C API functions, so that Houdini can determine at runtime which
34 // version of Python to use. This file needs to be included first to avoid
35 // gcc warnings. See PY_CPythonAPI.h for more information about the API
36 // wrapper. Note that you are limited to the Python API functions wrapped by
37 // PY_CPythonAPI.h.
38 #include <PY/PY_CPythonAPI.h>
39 
40 // We include this file for HOM_Error and other HOM declarations.
41 #include <HOM/HOM_Module.h>
42 
43 // This file contains functions that will run arbitrary Python code:
44 #include <PY/PY_Python.h>
45 
46 // The class defined in this file acquires the GIL within Houdini's Python
47 // runtime environment:
49 
50 // This file defines a convenience class to automatically decrement Python
51 // reference counts:
52 #include <PY/PY_AutoObject.h>
53 
54 // This class in this file is Houdini's internal representation of an object
55 // node:
56 #include <OBJ/OBJ_Node.h>
57 
58 // This file defines OPgetDirector(), which returns the root ("/") node.
59 #include <OP/OP_Director.h>
60 
61 // This file defines a function to demangle a C++ class name from the result
62 // returned by type_info::name():
63 #include <UT/UT_SysSpecific.h>
64 
65 // This file lets us start a undo block.
66 #include <UT/UT_UndoManager.h>
67 
68 static void
69 ObjNode_setSelectable(const char *node_path, bool selectable)
70 {
71  // This is the function that does the actual work. It takes a path
72  // to the object node and a flag to say if it should be selectable.
73  //
74  // Look up the OBJ_Node for the path.
75  OP_Node *op_node = OPgetDirector()->findNode(node_path);
76  if (!op_node)
77  throw HOM_OperationFailed("Internal error (could not find node)");
78 
79  OBJ_Node *obj_node = op_node->castToOBJNode();
80  if (!obj_node)
81  throw HOM_OperationFailed("Internal error (node is not an object)");
82 
83  // Now that we have the OBJ_Node object, we can access anything exposed
84  // by the HDK. For this example, we simply need to call setPickable().
85  // First, though, we create an undo block, so that any operations called
86  // by setPickable will be put into this block.
87  UT_AutoUndoBlock undo_block("Setting selectable flag", ANYLEVEL);
88 
89  // If this node is inside a locked asset, we want to raise a Python
90  // hou.PermissionError exception. To do that, we simply throw an instance
91  // of the C++ HOM_PermissionError class.
92  if (!obj_node->canAccess(PRM_WRITE_OK))
93  throw HOM_PermissionError();
94 
95  obj_node->setPickable(selectable);
96 }
97 
98 static PY_PyObject *
99 createHouException(
100  const char *exception_class_name, const char *instance_message,
101  PY_PyObject *&exception_class)
102 {
103  // Create an instance of the given exception class from the hou
104  // module, passing the instance message into the exeption class's
105  // __init__ method. This function returns a new exception instance, or
106  // NULL if an error occurred. The class is returned in exception_class
107  // and is a borrowed reference.
108  exception_class = NULL;
109 
110  // Note that a PY_AutoObject class is just a wrapper around a
111  // PY_PyObject pointer that will call PY_Py_XDECREF when the it's destroyed.
112  // We use it for Python API functions that return new object instances.
113  // Because this HDK extension is installed after the hou module is
114  // imported, we can be sure that we can be sure hou_module won't be null.
115  PY_AutoObject hou_module(PY_PyImport_ImportModule("hou"));
116 
117  // Look up the exception by name in the module's dictionary. Note that
118  // PY_PyModule_GetDict returns a borrowed reference and that it never
119  // returns NULL. PY_PyDict_GetItemString also returns a borrowed
120  // reference.
121  PY_PyObject *hou_module_dict = PY_PyModule_GetDict(hou_module);
122  exception_class = PY_PyDict_GetItemString(
123  hou_module_dict, exception_class_name);
124 
125  // PY_PyDict_GetItemString doesn't set a Python exception, so we are careful
126  // to set it ourselves if the class name isn't valid.
127  if (!exception_class)
128  {
129  PY_PyErr_SetString(
130  PY_PyExc_RuntimeError(),
131  "Could not find exception class in hou module");
132  return NULL;
133  }
134 
135  // Create an instance of the exception. First create a tuple containing
136  // the arguments to __init__.
137  PY_AutoObject args(PY_Py_BuildValue("(s)", instance_message));
138  if (!args)
139  return NULL;
140 
141  return PY_PyObject_Call(exception_class, args, /*kwargs=*/NULL);
142 }
143 
144 static PY_PyObject *
145 ObjNode_setSelectable_Wrapper(PY_PyObject *self, PY_PyObject *args)
146 {
147  // This is a wrapper that is called from the Python runtime engine. It
148  // translates the Python arguments to C/C++ ones, calls a function to do
149  // the actual work, and converts exceptions raised by that function into
150  // Python exceptions.
151  //
152  // Note that you could also use swig to automatically generate wrapper
153  // functions like this.
154  //
155  // Since this function is called from the Python runtime engine, we
156  // don't need to manually acquire the Python global interpreter lock (GIL).
157 
158  // First extract the arguments: a string and an integer (bool).
159  const char *node_path;
160  int selectable;
161  if (!PY_PyArg_ParseTuple(args, "si", &node_path, &selectable))
162  return NULL;
163 
164  // Now call ObjNode_setSelectable to do the actual work, taking care
165  // of the locking and exception handling here.
166  try
167  {
168  // If this code is called from a thread other than the main thread,
169  // creating a HOM_AutoLock instance will lock, waiting until Houdini
170  // is sitting idle in its event loop. This way, we can be sure that
171  // any code that accesses Houdini's internal state is threadsafe.
172  HOM_AutoLock hom_lock;
173 
174  // Call the wrapped function to do the actual work.
175  ObjNode_setSelectable(node_path, (bool)selectable);
176 
177  // Return PY_Py_None to indicate that no error occurred. If your
178  // wrapped function returns a value, you'll need to convert it into
179  // a Python object here.
180  return PY_Py_None();
181  }
182  catch (HOM_Error &error)
183  {
184  // The exceptions used by the hou module are subclasses of HOM_Error
185  // (and can be found in HOM_Errors.h). We use RTTI to get the class
186  // name, remove the "HOM_" prefix, and look up the corresponding
187  // exception class in the hou Python module.
188  std::string exception_class_name = UTunmangleClassNameFromTypeIdName(
189  typeid(error).name());
190  if (exception_class_name.find("HOM_") == 0)
191  exception_class_name = exception_class_name.substr(4);
192 
193  // Note that a PY_AutoObject class is just a wrapper around a
194  // PY_PyObject pointer that will call PY_Py_XDECREF when the it's
195  // destroyed.
196  PY_PyObject *exception_class;
197  PY_AutoObject exception_instance(createHouException(
198  exception_class_name.c_str(), error.instanceMessage().c_str(),
199  exception_class));
200  if (!exception_instance)
201  return NULL;
202 
203  // Set the exception and return NULL so Python knows an exception was
204  // raised.
205  PY_PyErr_SetObject(exception_class, exception_instance);
206  return NULL;
207  }
208 }
209 
212 {
213  //
214  // This is the initialization function that Python calls when
215  // importing the extension module.
216  //
217 
218  PY_PyObject *module = nullptr;
219 
220  {
221  // A PY_InterpreterAutoLock will grab the Python global interpreter
222  // lock (GIL). It's important that we have the GIL before making
223  // any calls into the Python API.
224  PY_InterpreterAutoLock interpreter_auto_lock;
225 
226  // We'll create a new module named "_hom_extensions", and add functions
227  // to it. We don't give a docstring here because it's given in the
228  // Python implementation below.
229  static PY_PyMethodDef hom_extension_methods[] = {
230  {"ObjNode_setSelectable", ObjNode_setSelectable_Wrapper,
231  PY_METH_VARARGS(), ""},
232  { NULL, NULL, 0, NULL }
233  };
234 
235  module = PY_Py_InitModule(
236  "_hdk_sample_hom_extensions", hom_extension_methods);
237  }
238 
239  return module;
240 }
241 
OP_Node * findNode(const char *path, OTLSyncMode syncmode=OTLSYNC_DOSYNC) const
Uses the path (eg. "/obj/geo1") to find a node in our hierarchy.
PY_API int PY_PyArg_ParseTuple(PY_PyObject *args, const char *format,...)
bool setPickable(bool onoff) override
PY_PyMODINIT_FUNC PyInit__hdk_sample_hom_extensions(void)
PY_API PY_PyObject * PY_Py_BuildValue(const char *format,...)
< returns > If no error
Definition: snippets.dox:2
#define PY_Py_None()
UT_API std::string UTunmangleClassNameFromTypeIdName(const std::string &name)
PY_PyObject
virtual std::string instanceMessage()
Definition: HOM_Errors.h:62
GLuint const GLchar * name
Definition: glcorearb.h:786
int canAccess(unsigned mask) const
OP_API OP_Director * OPgetDirector()
PY_API PY_PyObject * PY_Py_InitModule(const char *name, PY_PyMethodDef *methods)
**If you just want to fire and args
Definition: thread.h:618
OBJ_Node * castToOBJNode() const
Definition: OP_Node.h:608
#define PY_PyMODINIT_FUNC