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) 2024
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 the Python header for PyMODINIT_FUNC.
33 // The Python developers recommend that Python.h be included before
34 // any other header files.
35 #include <Python.h>
36 
37 // Include Houdini's equivalent of python/Python.h. It creates wrappers of
38 // Python's C API functions, so that Houdini can determine at runtime which
39 // version of Python to use. This file needs to be included first to avoid
40 // gcc warnings. See PY_CPythonAPI.h for more information about the API
41 // wrapper. Note that you are limited to the Python API functions wrapped by
42 // PY_CPythonAPI.h.
43 #include <PY/PY_CPythonAPI.h>
44 
45 // We include this file for HOM_Error and other HOM declarations.
46 #include <HOM/HOM_Module.h>
47 
48 // This file contains functions that will run arbitrary Python code:
49 #include <PY/PY_Python.h>
50 
51 // The class defined in this file acquires the GIL within Houdini's Python
52 // runtime environment:
54 
55 // This file defines a convenience class to automatically decrement Python
56 // reference counts:
57 #include <PY/PY_AutoObject.h>
58 
59 // This class in this file is Houdini's internal representation of an object
60 // node:
61 #include <OBJ/OBJ_Node.h>
62 
63 // This file defines OPgetDirector(), which returns the root ("/") node.
64 #include <OP/OP_Director.h>
65 
66 // This file defines a function to demangle a C++ class name from the result
67 // returned by type_info::name():
68 #include <UT/UT_SysSpecific.h>
69 
70 // This file lets us start a undo block.
71 #include <UT/UT_UndoManager.h>
72 
73 static void
74 ObjNode_setSelectable(const char *node_path, bool selectable)
75 {
76  // This is the function that does the actual work. It takes a path
77  // to the object node and a flag to say if it should be selectable.
78  //
79  // Look up the OBJ_Node for the path.
80  OP_Node *op_node = OPgetDirector()->findNode(node_path);
81  if (!op_node)
82  throw HOM_OperationFailed("Internal error (could not find node)");
83 
84  OBJ_Node *obj_node = op_node->castToOBJNode();
85  if (!obj_node)
86  throw HOM_OperationFailed("Internal error (node is not an object)");
87 
88  // Now that we have the OBJ_Node object, we can access anything exposed
89  // by the HDK. For this example, we simply need to call setPickable().
90  // First, though, we create an undo block, so that any operations called
91  // by setPickable will be put into this block.
92  UT_AutoUndoBlock undo_block("Setting selectable flag", ANYLEVEL);
93 
94  // If this node is inside a locked asset, we want to raise a Python
95  // hou.PermissionError exception. To do that, we simply throw an instance
96  // of the C++ HOM_PermissionError class.
97  if (!obj_node->canAccess(PRM_WRITE_OK))
98  throw HOM_PermissionError();
99 
100  obj_node->setPickable(selectable);
101 }
102 
103 static PY_PyObject *
104 createHouException(
105  const char *exception_class_name, const char *instance_message,
106  PY_PyObject *&exception_class)
107 {
108  // Create an instance of the given exception class from the hou
109  // module, passing the instance message into the exeption class's
110  // __init__ method. This function returns a new exception instance, or
111  // NULL if an error occurred. The class is returned in exception_class
112  // and is a borrowed reference.
113  exception_class = NULL;
114 
115  // Note that a PY_AutoObject class is just a wrapper around a
116  // PY_PyObject pointer that will call PY_Py_XDECREF when the it's destroyed.
117  // We use it for Python API functions that return new object instances.
118  // Because this HDK extension is installed after the hou module is
119  // imported, we can be sure that we can be sure hou_module won't be null.
120  PY_AutoObject hou_module(PY_PyImport_ImportModule("hou"));
121 
122  // Look up the exception by name in the module's dictionary. Note that
123  // PY_PyModule_GetDict returns a borrowed reference and that it never
124  // returns NULL. PY_PyDict_GetItemString also returns a borrowed
125  // reference.
126  PY_PyObject *hou_module_dict = PY_PyModule_GetDict(hou_module);
127  exception_class = PY_PyDict_GetItemString(
128  hou_module_dict, exception_class_name);
129 
130  // PY_PyDict_GetItemString doesn't set a Python exception, so we are careful
131  // to set it ourselves if the class name isn't valid.
132  if (!exception_class)
133  {
134  PY_PyErr_SetString(
135  PY_PyExc_RuntimeError(),
136  "Could not find exception class in hou module");
137  return NULL;
138  }
139 
140  // Create an instance of the exception. First create a tuple containing
141  // the arguments to __init__.
142  PY_AutoObject args(PY_Py_BuildValue("(s)", instance_message));
143  if (!args)
144  return NULL;
145 
146  return PY_PyObject_Call(exception_class, args, /*kwargs=*/NULL);
147 }
148 
149 static PY_PyObject *
150 ObjNode_setSelectable_Wrapper(PY_PyObject *self, PY_PyObject *args)
151 {
152  // This is a wrapper that is called from the Python runtime engine. It
153  // translates the Python arguments to C/C++ ones, calls a function to do
154  // the actual work, and converts exceptions raised by that function into
155  // Python exceptions.
156  //
157  // Note that you could also use swig to automatically generate wrapper
158  // functions like this.
159  //
160  // Since this function is called from the Python runtime engine, we
161  // don't need to manually acquire the Python global interpreter lock (GIL).
162 
163  // First extract the arguments: a string and an integer (bool).
164  const char *node_path;
165  int selectable;
166  if (!PY_PyArg_ParseTuple(args, "si", &node_path, &selectable))
167  return NULL;
168 
169  // Now call ObjNode_setSelectable to do the actual work, taking care
170  // of the locking and exception handling here.
171  try
172  {
173  // If this code is called from a thread other than the main thread,
174  // creating a HOM_AutoLock instance will lock, waiting until Houdini
175  // is sitting idle in its event loop. This way, we can be sure that
176  // any code that accesses Houdini's internal state is threadsafe.
177  HOM_AutoLock hom_lock;
178 
179  // Call the wrapped function to do the actual work.
180  ObjNode_setSelectable(node_path, (bool)selectable);
181 
182  // Return PY_Py_None to indicate that no error occurred. If your
183  // wrapped function returns a value, you'll need to convert it into
184  // a Python object here.
185  return PY_Py_None();
186  }
187  catch (HOM_Error &error)
188  {
189  // The exceptions used by the hou module are subclasses of HOM_Error
190  // (and can be found in HOM_Errors.h). We use RTTI to get the class
191  // name, remove the "HOM_" prefix, and look up the corresponding
192  // exception class in the hou Python module.
193  std::string exception_class_name = UTunmangleClassNameFromTypeIdName(
194  typeid(error).name());
195  if (exception_class_name.find("HOM_") == 0)
196  exception_class_name = exception_class_name.substr(4);
197 
198  // Note that a PY_AutoObject class is just a wrapper around a
199  // PY_PyObject pointer that will call PY_Py_XDECREF when the it's
200  // destroyed.
201  PY_PyObject *exception_class;
202  PY_AutoObject exception_instance(createHouException(
203  exception_class_name.c_str(), error.instanceMessage().c_str(),
204  exception_class));
205  if (!exception_instance)
206  return NULL;
207 
208  // Set the exception and return NULL so Python knows an exception was
209  // raised.
210  PY_PyErr_SetObject(exception_class, exception_instance);
211  return NULL;
212  }
213 }
214 
215 #if defined(WIN32)
216 PyMODINIT_FUNC
217 #else
218 // This is a replacement of PyMODINIT_FUNC but with the default visibility
219 // attribute declaration injected in the middle.
220 extern "C" __attribute__((visibility("default"))) PyObject*
221 #endif
222 
223 PyInit__hdk_sample_hom_extensions(void)
224 {
225  //
226  // This is the initialization function that Python calls when
227  // importing the extension module.
228  //
229 
230  PY_PyObject *module = nullptr;
231 
232  {
233  // A PY_InterpreterAutoLock will grab the Python global interpreter
234  // lock (GIL). It's important that we have the GIL before making
235  // any calls into the Python API.
236  PY_InterpreterAutoLock interpreter_auto_lock;
237 
238  // We'll create a new module named "_hom_extensions", and add functions
239  // to it. We don't give a docstring here because it's given in the
240  // Python implementation below.
241  static PY_PyMethodDef hom_extension_methods[] = {
242  {"ObjNode_setSelectable", ObjNode_setSelectable_Wrapper,
243  PY_METH_VARARGS(), ""},
244  { NULL, NULL, 0, NULL }
245  };
246 
247  module = PY_Py_InitModule(
248  "_hdk_sample_hom_extensions", hom_extension_methods);
249  }
250 
251  return reinterpret_cast<PyObject *>(module);
252 }
253 
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
GLsizei const GLchar *const * string
Definition: glcorearb.h:814
PY_API PY_PyObject * PY_Py_BuildValue(const char *format,...)
< returns > If no error
Definition: snippets.dox:2
__attribute__((visibility("default")))
#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:609
OBJ_Node * castToOBJNode() const
Definition: OP_Node.h:582