Blender V4.3
usd_hook.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "usd.hh"
6
7#include "usd_hook.hh"
8
9#include <boost/python/call_method.hpp>
10#include <boost/python/class.hpp>
11#include <boost/python/import.hpp>
12#include <boost/python/return_value_policy.hpp>
13#include <boost/python/to_python_converter.hpp>
14
15#include "BLI_utildefines.h"
16
17#include "BKE_report.hh"
18
20
21#include "RNA_access.hh"
22#include "RNA_prototypes.hh"
23#include "RNA_types.hh"
24#include "bpy_rna.hh"
25
26#include <list>
27#include <memory>
28
29using namespace boost;
30
31namespace blender::io::usd {
32
33using USDHookList = std::list<std::unique_ptr<USDHook>>;
34
35/* USD hook type declarations */
37{
38 static USDHookList hooks{};
39 return hooks;
40}
41
42void USD_register_hook(std::unique_ptr<USDHook> hook)
43{
44 if (USD_find_hook_name(hook->idname)) {
45 /* The hook is already in the list. */
46 return;
47 }
48
49 /* Add hook type to the list. */
50 hook_list().push_back(std::move(hook));
51}
52
54{
55 hook_list().remove_if(
56 [hook](const std::unique_ptr<USDHook> &item) { return item.get() == hook; });
57}
58
59USDHook *USD_find_hook_name(const char idname[])
60{
61 /* sanity checks */
62 if (hook_list().empty() || (idname == nullptr) || (idname[0] == 0)) {
63 return nullptr;
64 }
65
66 USDHookList::iterator hook_iter = std::find_if(
67 hook_list().begin(), hook_list().end(), [idname](const std::unique_ptr<USDHook> &item) {
68 return STREQ(item->idname, idname);
69 });
70
71 return (hook_iter == hook_list().end()) ? nullptr : hook_iter->get();
72}
73
74/* Convert PointerRNA to a PyObject*. */
76
77 /* We pass the argument by value because we need
78 * to obtain a non-const pointer to it. */
79 static PyObject *convert(PointerRNA ptr)
80 {
82 }
83};
84
85/* Encapsulate arguments for scene export. */
87
89
90 USDSceneExportContext(pxr::UsdStageRefPtr in_stage, Depsgraph *depsgraph) : stage(in_stage)
91 {
92 depsgraph_ptr = RNA_pointer_create(nullptr, &RNA_Depsgraph, depsgraph);
93 }
94
95 pxr::UsdStageRefPtr get_stage() const
96 {
97 return stage;
98 }
99
101 {
102 return depsgraph_ptr;
103 }
104
105 pxr::UsdStageRefPtr stage;
107};
108
109/* Encapsulate arguments for scene import. */
112
113 USDSceneImportContext(pxr::UsdStageRefPtr in_stage) : stage(in_stage) {}
114
115 pxr::UsdStageRefPtr get_stage() const
116 {
117 return stage;
118 }
119
120 pxr::UsdStageRefPtr stage;
121};
122
123/* Encapsulate arguments for material export. */
126
127 USDMaterialExportContext(pxr::UsdStageRefPtr in_stage) : stage(in_stage) {}
128
129 pxr::UsdStageRefPtr get_stage() const
130 {
131 return stage;
132 }
133
134 pxr::UsdStageRefPtr stage;
135};
136
138{
139 static bool registered = false;
140
141 /* No need to register if there are no hooks. */
142 if (hook_list().empty()) {
143 return;
144 }
145
146 if (registered) {
147 return;
148 }
149
150 registered = true;
151
152 PyGILState_STATE gilstate = PyGILState_Ensure();
153
154 /* We must import these modules for the USD type converters to work. */
155 python::import("pxr.Usd");
156 python::import("pxr.UsdShade");
157
158 /* Register converter from PoinerRNA to a PyObject*. */
159 python::to_python_converter<PointerRNA, PointerRNAToPython>();
160
161 /* Register context class converters. */
162 python::class_<USDSceneExportContext>("USDSceneExportContext")
163 .def("get_stage", &USDSceneExportContext::get_stage)
164 .def("get_depsgraph",
166 python::return_value_policy<python::return_by_value>());
167
168 python::class_<USDMaterialExportContext>("USDMaterialExportContext")
169 .def("get_stage", &USDMaterialExportContext::get_stage);
170
171 python::class_<USDSceneImportContext>("USDSceneImportContext")
172 .def("get_stage", &USDSceneImportContext::get_stage);
173
174 PyGILState_Release(gilstate);
175}
176
177/* Retrieve and report the current Python error. */
178static void handle_python_error(USDHook *hook, ReportList *reports)
179{
180 if (!PyErr_Occurred()) {
181 return;
182 }
183
184 PyErr_Print();
185
186 BKE_reportf(reports,
187 RPT_ERROR,
188 "An exception occurred invoking USD hook '%s'. Please see the console for details",
189 hook->name);
190}
191
192/* Abstract base class to facilitate calling a function with a given
193 * signature defined by the registered USDHook classes. Subclasses
194 * override virtual methods to specify the hook function name and to
195 * call the hook with the required arguments.
196 */
198 public:
199 /* Attempt to call the function, if defined by the registered hooks. */
200 void call() const
201 {
202 if (hook_list().empty()) {
203 return;
204 }
205
206 PyGILState_STATE gilstate = PyGILState_Ensure();
207
208 /* Iterate over the hooks and invoke the hook function, if it's defined. */
209 USDHookList::const_iterator hook_iter = hook_list().begin();
210 while (hook_iter != hook_list().end()) {
211
212 /* XXX: Not sure if this is necessary:
213 * Advance the iterator before invoking the callback, to guard
214 * against the unlikely error where the hook is de-registered in
215 * the callback. This would prevent a crash due to the iterator
216 * getting invalidated. */
217 USDHook *hook = hook_iter->get();
218 ++hook_iter;
219
220 if (!hook->rna_ext.data) {
221 continue;
222 }
223
224 try {
225 PyObject *hook_obj = static_cast<PyObject *>(hook->rna_ext.data);
226
227 if (!PyObject_HasAttrString(hook_obj, function_name())) {
228 continue;
229 }
230
231 call_hook(hook_obj);
232 }
233 catch (python::error_already_set const &) {
235 }
236 catch (...) {
238 reports_, RPT_ERROR, "An exception occurred invoking USD hook '%s'", hook->name);
239 }
240 }
241
242 PyGILState_Release(gilstate);
243 }
244
245 protected:
246 /* Override to specify the name of the function to be called. */
247 virtual const char *function_name() const = 0;
248 /* Override to call the function of the given object with the
249 * required arguments, e.g.,
250 *
251 * python::call_method<void>(hook_obj, function_name(), arg1, arg2); */
252 virtual void call_hook(PyObject *hook_obj) const = 0;
253
254 /* Reports list provided when constructing the subclass, used by #call() to store reports. */
256};
257
259 private:
260 USDSceneExportContext hook_context_;
261
262 public:
263 OnExportInvoker(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports)
264 : hook_context_(stage, depsgraph)
265 {
266 reports_ = reports;
267 }
268
269 protected:
270 const char *function_name() const override
271 {
272 return "on_export";
273 }
274
275 void call_hook(PyObject *hook_obj) const override
276 {
277 python::call_method<bool>(hook_obj, function_name(), ref(hook_context_));
278 }
279};
280
282 private:
283 USDMaterialExportContext hook_context_;
284 pxr::UsdShadeMaterial usd_material_;
285 PointerRNA material_ptr_;
286
287 public:
288 OnMaterialExportInvoker(pxr::UsdStageRefPtr stage,
289 Material *material,
290 const pxr::UsdShadeMaterial &usd_material,
291 ReportList *reports)
292 : hook_context_(stage), usd_material_(usd_material)
293 {
294 material_ptr_ = RNA_pointer_create(nullptr, &RNA_Material, material);
295 reports_ = reports;
296 }
297
298 protected:
299 const char *function_name() const override
300 {
301 return "on_material_export";
302 }
303
304 void call_hook(PyObject *hook_obj) const override
305 {
306 python::call_method<bool>(
307 hook_obj, function_name(), ref(hook_context_), material_ptr_, usd_material_);
308 }
309};
310
312 private:
313 USDSceneImportContext hook_context_;
314
315 public:
316 OnImportInvoker(pxr::UsdStageRefPtr stage, ReportList *reports) : hook_context_(stage)
317 {
318 reports_ = reports;
319 }
320
321 protected:
322 const char *function_name() const override
323 {
324 return "on_import";
325 }
326
327 void call_hook(PyObject *hook_obj) const override
328 {
329 python::call_method<bool>(hook_obj, function_name(), ref(hook_context_));
330 }
331};
332
333void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports)
334{
335 if (hook_list().empty()) {
336 return;
337 }
338
339 OnExportInvoker on_export(stage, depsgraph, reports);
340 on_export.call();
341}
342
343void call_material_export_hooks(pxr::UsdStageRefPtr stage,
344 Material *material,
345 const pxr::UsdShadeMaterial &usd_material,
346 ReportList *reports)
347{
348 if (hook_list().empty()) {
349 return;
350 }
351
352 OnMaterialExportInvoker on_material_export(stage, material, usd_material, reports);
353 on_material_export.call();
354}
355
356void call_import_hooks(pxr::UsdStageRefPtr stage, ReportList *reports)
357{
358 if (hook_list().empty()) {
359 return;
360 }
361
362 OnImportInvoker on_import(stage, reports);
363 on_import.call();
364}
365
366} // namespace blender::io::usd
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
#define STREQ(a, b)
PyObject * pyrna_struct_CreatePyObject(PointerRNA *ptr)
Definition bpy_rna.cc:7694
OnExportInvoker(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports)
Definition usd_hook.cc:263
void call_hook(PyObject *hook_obj) const override
Definition usd_hook.cc:275
const char * function_name() const override
Definition usd_hook.cc:270
void call_hook(PyObject *hook_obj) const override
Definition usd_hook.cc:327
const char * function_name() const override
Definition usd_hook.cc:322
OnImportInvoker(pxr::UsdStageRefPtr stage, ReportList *reports)
Definition usd_hook.cc:316
const char * function_name() const override
Definition usd_hook.cc:299
OnMaterialExportInvoker(pxr::UsdStageRefPtr stage, Material *material, const pxr::UsdShadeMaterial &usd_material, ReportList *reports)
Definition usd_hook.cc:288
void call_hook(PyObject *hook_obj) const override
Definition usd_hook.cc:304
virtual const char * function_name() const =0
virtual void call_hook(PyObject *hook_obj) const =0
EvaluationStage stage
Definition deg_eval.cc:83
const Depsgraph * depsgraph
void call_material_export_hooks(pxr::UsdStageRefPtr stage, Material *material, const pxr::UsdShadeMaterial &usd_material, ReportList *reports)
Definition usd_hook.cc:343
void USD_unregister_hook(USDHook *hook)
Definition usd_hook.cc:53
void USD_register_hook(std::unique_ptr< USDHook > hook)
Definition usd_hook.cc:42
static USDHookList & hook_list()
Definition usd_hook.cc:36
std::list< std::unique_ptr< USDHook > > USDHookList
Definition usd_hook.cc:33
void call_import_hooks(pxr::UsdStageRefPtr stage, ReportList *reports)
Definition usd_hook.cc:356
void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports)
Definition usd_hook.cc:333
USDHook * USD_find_hook_name(const char idname[])
Definition usd_hook.cc:59
static void handle_python_error(USDHook *hook, ReportList *reports)
Definition usd_hook.cc:178
void register_hook_converters()
Definition usd_hook.cc:137
PointerRNA RNA_pointer_create(ID *id, StructRNA *type, void *data)
static PyObject * convert(PointerRNA ptr)
Definition usd_hook.cc:79
ExtensionRNA rna_ext
Definition usd.hh:310
pxr::UsdStageRefPtr get_stage() const
Definition usd_hook.cc:129
USDMaterialExportContext(pxr::UsdStageRefPtr in_stage)
Definition usd_hook.cc:127
pxr::UsdStageRefPtr get_stage() const
Definition usd_hook.cc:95
USDSceneExportContext(pxr::UsdStageRefPtr in_stage, Depsgraph *depsgraph)
Definition usd_hook.cc:90
const PointerRNA & get_depsgraph() const
Definition usd_hook.cc:100
pxr::UsdStageRefPtr get_stage() const
Definition usd_hook.cc:115
USDSceneImportContext(pxr::UsdStageRefPtr in_stage)
Definition usd_hook.cc:113
PointerRNA * ptr
Definition wm_files.cc:4126