Blender V5.0
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_hook.hh"
6
7#include "usd.hh"
8#include "usd_asset_utils.hh"
9#include "usd_hash_types.hh"
10#include "usd_reader_prim.hh"
11#include "usd_reader_stage.hh"
13
14#include "BLI_map.hh"
15#include "BLI_utildefines.h"
16#include "BLI_vector.hh"
17
18#include "BKE_report.hh"
19
20#include "DNA_material_types.h"
22
23#include "RNA_access.hh"
24#include "RNA_prototypes.hh"
25#include "RNA_types.hh"
26#include "bpy_rna.hh"
27
28#include <list>
29#include <memory>
30#include <string>
31
32#if PXR_VERSION >= 2411
33# include <pxr/external/boost/python/call_method.hpp>
34# include <pxr/external/boost/python/class.hpp>
35# include <pxr/external/boost/python/dict.hpp>
36# include <pxr/external/boost/python/import.hpp>
37# include <pxr/external/boost/python/list.hpp>
38# include <pxr/external/boost/python/ref.hpp>
39# include <pxr/external/boost/python/return_value_policy.hpp>
40# include <pxr/external/boost/python/to_python_converter.hpp>
41# include <pxr/external/boost/python/tuple.hpp>
42# define PYTHON_NS pxr::pxr_boost::python
43# define REF pxr::pxr_boost::python::ref
44
45using namespace pxr::pxr_boost;
46#else
47# include <boost/python/call_method.hpp>
48# include <boost/python/class.hpp>
49# include <boost/python/import.hpp>
50# include <boost/python/return_value_policy.hpp>
51# include <boost/python/to_python_converter.hpp>
52# include <boost/python/tuple.hpp>
53# define PYTHON_NS boost::python
54# define REF boost::ref
55
56using namespace boost;
57#endif
58
59namespace blender::io::usd {
60
61using USDHookList = std::list<std::unique_ptr<USDHook>>;
63
64/* USD hook type declarations */
66{
67 static USDHookList hooks{};
68 return hooks;
69}
70
71void USD_register_hook(std::unique_ptr<USDHook> hook)
72{
73 if (USD_find_hook_name(hook->idname)) {
74 /* The hook is already in the list. */
75 return;
76 }
77
78 /* Add hook type to the list. */
79 hook_list().push_back(std::move(hook));
80}
81
83{
84 hook_list().remove_if(
85 [hook](const std::unique_ptr<USDHook> &item) { return item.get() == hook; });
86}
87
88USDHook *USD_find_hook_name(const char idname[])
89{
90 /* sanity checks */
91 if (hook_list().empty() || (idname == nullptr) || (idname[0] == 0)) {
92 return nullptr;
93 }
94
95 USDHookList::iterator hook_iter = std::find_if(
96 hook_list().begin(), hook_list().end(), [idname](const std::unique_ptr<USDHook> &item) {
97 return STREQ(item->idname, idname);
98 });
99
100 return (hook_iter == hook_list().end()) ? nullptr : hook_iter->get();
101}
102
103/* Convert PointerRNA to a PyObject*. */
105
106 /* We pass the argument by value because we need
107 * to obtain a non-const pointer to it. */
108 static PyObject *convert(PointerRNA ptr)
109 {
111 }
112};
113
114/* Encapsulate arguments for scene export. */
116 private:
117 pxr::UsdStageRefPtr stage_;
118 PointerRNA depsgraph_ptr_;
119
120 public:
122
123 USDSceneExportContext(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph) : stage_(stage)
124 {
125 depsgraph_ptr_ = RNA_pointer_create_discrete(nullptr, &RNA_Depsgraph, depsgraph);
126 }
127
128 pxr::UsdStageRefPtr get_stage() const
129 {
130 return stage_;
131 }
132
134 {
135 return depsgraph_ptr_;
136 }
137};
138
139/* Encapsulate arguments for scene import. */
141 private:
142 pxr::UsdStageRefPtr stage_;
143 ImportedPrimMap prim_map_;
144 PYTHON_NS::dict *prim_map_dict_ = nullptr;
145
146 public:
148
149 USDSceneImportContext(pxr::UsdStageRefPtr in_stage, const ImportedPrimMap &in_prim_map)
150 : stage_(in_stage), prim_map_(in_prim_map)
151 {
152 }
153
154 void release()
155 {
156 delete prim_map_dict_;
157 }
158
159 pxr::UsdStageRefPtr get_stage() const
160 {
161 return stage_;
162 }
163
164 PYTHON_NS::dict get_prim_map()
165 {
166 if (!prim_map_dict_) {
167 prim_map_dict_ = new PYTHON_NS::dict;
168
169 prim_map_.foreach_item([&](const pxr::SdfPath &path, const Vector<PointerRNA> &ids) {
170 if (!prim_map_dict_->has_key(path)) {
171 (*prim_map_dict_)[path] = PYTHON_NS::list();
172 }
173
174 PYTHON_NS::list list = PYTHON_NS::extract<PYTHON_NS::list>((*prim_map_dict_)[path]);
175 for (const auto &ptr_rna : ids) {
176 list.append(ptr_rna);
177 }
178 });
179 }
180
181 return *prim_map_dict_;
182 }
183};
184
185/* Encapsulate arguments for material export. */
187 private:
188 pxr::UsdStageRefPtr stage_;
189 USDExportParams params_ = {};
190 ReportList *reports_ = nullptr;
191
192 public:
194
195 USDMaterialExportContext(pxr::UsdStageRefPtr stage,
196 const USDExportParams &params,
197 ReportList *reports)
198 : stage_(stage), params_(params), reports_(reports)
199 {
200 }
201
202 pxr::UsdStageRefPtr get_stage() const
203 {
204 return stage_;
205 }
206
212 std::string export_texture(PYTHON_NS::object obj) const
213 {
214 ID *id;
215 if (!pyrna_id_FromPyObject(obj.ptr(), &id)) {
216 return "";
217 }
218
219 if (!id) {
220 return "";
221 }
222
223 if (GS(id->name) != ID_IM) {
224 return "";
225 }
226
227 Image *ima = reinterpret_cast<Image *>(id);
228
229 std::string asset_path = get_tex_image_asset_filepath(ima, stage_, params_);
230
231 if (params_.export_textures) {
232 blender::io::usd::export_texture(ima, stage_, params_.overwrite_textures, reports_);
233 }
234
235 return asset_path;
236 }
237};
238
239/* Encapsulate arguments for material import. */
241 private:
242 pxr::UsdStageRefPtr stage_;
243 USDImportParams params_ = {};
244 ReportList *reports_ = nullptr;
245
246 public:
248
249 USDMaterialImportContext(pxr::UsdStageRefPtr stage,
250 const USDImportParams &params,
251 ReportList *reports)
252 : stage_(stage), params_(params), reports_(reports)
253 {
254 }
255
256 pxr::UsdStageRefPtr get_stage() const
257 {
258 return stage_;
259 }
260
269 PYTHON_NS::tuple import_texture(const std::string &asset_path) const
270 {
271 if (!should_import_asset(asset_path)) {
272 /* This path does not need to be imported, so return it unchanged. */
273 return PYTHON_NS::make_tuple(asset_path, false);
274 }
275
276 const char *textures_dir = params_.import_textures_mode == USD_TEX_IMPORT_PACK ?
278 params_.import_textures_dir;
279
280 const eUSDTexNameCollisionMode name_collision_mode = params_.import_textures_mode ==
283 params_.tex_name_collision_mode;
284
285 std::string import_path = import_asset(
286 asset_path, textures_dir, name_collision_mode, reports_);
287
288 if (import_path == asset_path) {
289 /* Path is unchanged. */
290 return PYTHON_NS::make_tuple(asset_path, false);
291 }
292
293 const bool is_temporary = params_.import_textures_mode == USD_TEX_IMPORT_PACK;
294 return PYTHON_NS::make_tuple(import_path, is_temporary);
295 }
296};
297
299{
300 static bool registered = false;
301
302 /* No need to register if there are no hooks. */
303 if (hook_list().empty()) {
304 return;
305 }
306
307 if (registered) {
308 return;
309 }
310
311 registered = true;
312
313 PyGILState_STATE gilstate = PyGILState_Ensure();
314
315 /* We must import these modules for the USD type converters to work. */
316 python::import("pxr.Usd");
317 python::import("pxr.UsdShade");
318
319 /* Register converter from PoinerRNA to a PyObject*. */
320 python::to_python_converter<PointerRNA, PointerRNAToPython>();
321
322 /* Register context class converters. */
323 python::class_<USDSceneExportContext>("USDSceneExportContext")
324 .def("get_stage", &USDSceneExportContext::get_stage)
325 .def("get_depsgraph",
327 python::return_value_policy<python::return_by_value>());
328
329 python::class_<USDMaterialExportContext>("USDMaterialExportContext")
330 .def("get_stage", &USDMaterialExportContext::get_stage)
331 .def("export_texture", &USDMaterialExportContext::export_texture);
332
333 python::class_<USDSceneImportContext>("USDSceneImportContext")
334 .def("get_stage", &USDSceneImportContext::get_stage)
335 .def("get_prim_map", &USDSceneImportContext::get_prim_map);
336
337 python::class_<USDMaterialImportContext>("USDMaterialImportContext")
338 .def("get_stage", &USDMaterialImportContext::get_stage)
339 .def("import_texture", &USDMaterialImportContext::import_texture);
340
341 PyGILState_Release(gilstate);
342}
343
344/* Retrieve and report the current Python error. */
345static void handle_python_error(USDHook *hook, ReportList *reports)
346{
347 if (!PyErr_Occurred()) {
348 return;
349 }
350
351 PyErr_Print();
352
353 BKE_reportf(reports,
354 RPT_ERROR,
355 "An exception occurred invoking USD hook '%s'. Please see the console for details",
356 hook->name);
357}
358
359/* Abstract base class to facilitate calling a function with a given
360 * signature defined by the registered USDHook classes. Subclasses
361 * override virtual methods to specify the hook function name and to
362 * call the hook with the required arguments.
363 */
365 private:
366 ReportList *reports_;
367
368 public:
369 explicit USDHookInvoker(ReportList *reports) : reports_(reports) {}
370 virtual ~USDHookInvoker() = default;
371
372 /* Attempt to call the function, if defined by the registered hooks. */
373 void call()
374 {
375 if (hook_list().empty()) {
376 return;
377 }
378
379 PyGILState_STATE gilstate = PyGILState_Ensure();
380 init_in_gil();
381
382 /* Iterate over the hooks and invoke the hook function, if it's defined. */
383 USDHookList::const_iterator hook_iter = hook_list().begin();
384 while (hook_iter != hook_list().end()) {
385
386 /* XXX: Not sure if this is necessary:
387 * Advance the iterator before invoking the callback, to guard
388 * against the unlikely error where the hook is de-registered in
389 * the callback. This would prevent a crash due to the iterator
390 * getting invalidated. */
391 USDHook *hook = hook_iter->get();
392 ++hook_iter;
393
394 if (!hook->rna_ext.data) {
395 continue;
396 }
397
398 try {
399 PyObject *hook_obj = static_cast<PyObject *>(hook->rna_ext.data);
400
401 if (!PyObject_HasAttrString(hook_obj, function_name())) {
402 continue;
403 }
404
405 call_hook(hook_obj);
406 }
407 catch (python::error_already_set const &) {
408 handle_python_error(hook, reports_);
409 }
410 catch (...) {
412 reports_, RPT_ERROR, "An exception occurred invoking USD hook '%s'", hook->name);
413 }
414 }
415
417 PyGILState_Release(gilstate);
418 }
419
420 protected:
421 /* Override to specify the name of the function to be called. */
422 virtual const char *function_name() const = 0;
423 /* Override to call the function of the given object with the
424 * required arguments, e.g.,
425 *
426 * python::call_method<void>(hook_obj, function_name(), arg1, arg2); */
427 virtual void call_hook(PyObject *hook_obj) = 0;
428
429 virtual void init_in_gil() {};
430 virtual void release_in_gil() {};
431};
432
434 private:
435 USDSceneExportContext hook_context_;
436
437 public:
438 OnExportInvoker(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports)
439 : USDHookInvoker(reports), hook_context_(stage, depsgraph)
440 {
441 }
442
443 private:
444 const char *function_name() const override
445 {
446 return "on_export";
447 }
448
449 void call_hook(PyObject *hook_obj) override
450 {
451 python::call_method<bool>(hook_obj, function_name(), REF(hook_context_));
452 }
453};
454
456 private:
457 USDMaterialExportContext hook_context_;
458 pxr::UsdShadeMaterial usd_material_;
459 PointerRNA material_ptr_;
460
461 public:
462 OnMaterialExportInvoker(pxr::UsdStageRefPtr stage,
463 Material *material,
464 const pxr::UsdShadeMaterial &usd_material,
465 const USDExportParams &export_params,
466 ReportList *reports)
467 : USDHookInvoker(reports),
468 hook_context_(stage, export_params, reports),
469 usd_material_(usd_material)
470 {
471 material_ptr_ = RNA_pointer_create_discrete(nullptr, &RNA_Material, material);
472 }
473
474 private:
475 const char *function_name() const override
476 {
477 return "on_material_export";
478 }
479
480 void call_hook(PyObject *hook_obj) override
481 {
482 python::call_method<bool>(
483 hook_obj, function_name(), REF(hook_context_), material_ptr_, usd_material_);
484 }
485};
486
488 private:
489 USDSceneImportContext hook_context_;
490
491 public:
492 OnImportInvoker(pxr::UsdStageRefPtr stage, const ImportedPrimMap &prim_map, ReportList *reports)
493 : USDHookInvoker(reports), hook_context_(stage, prim_map)
494 {
495 }
496
497 private:
498 const char *function_name() const override
499 {
500 return "on_import";
501 }
502
503 void call_hook(PyObject *hook_obj) override
504 {
505 python::call_method<bool>(hook_obj, function_name(), REF(hook_context_));
506 }
507
508 void release_in_gil() override
509 {
510 hook_context_.release();
511 }
512};
513
515 private:
516 USDMaterialImportContext hook_context_;
517 pxr::UsdShadeMaterial usd_material_;
518 bool result_ = false;
519
520 public:
521 MaterialImportPollInvoker(pxr::UsdStageRefPtr stage,
522 const pxr::UsdShadeMaterial &usd_material,
523 const USDImportParams &import_params,
524 ReportList *reports)
525 : USDHookInvoker(reports),
526 hook_context_(stage, import_params, reports),
527 usd_material_(usd_material)
528 {
529 }
530
531 bool result() const
532 {
533 return result_;
534 }
535
536 private:
537 const char *function_name() const override
538 {
539 return "material_import_poll";
540 }
541
542 void call_hook(PyObject *hook_obj) override
543 {
544 /* If we already know that one of the registered hook classes can import the material
545 * because it returned true in a previous invocation of the callback, we skip the call. */
546 if (!result_) {
547 result_ = python::call_method<bool>(
548 hook_obj, function_name(), REF(hook_context_), usd_material_);
549 }
550 }
551};
552
554 private:
555 USDMaterialImportContext hook_context_;
556 pxr::UsdShadeMaterial usd_material_;
557 PointerRNA material_ptr_;
558 bool result_ = false;
559
560 public:
561 OnMaterialImportInvoker(pxr::UsdStageRefPtr stage,
562 Material *material,
563 const pxr::UsdShadeMaterial &usd_material,
564 const USDImportParams &import_params,
565 ReportList *reports)
566 : USDHookInvoker(reports),
567 hook_context_(stage, import_params, reports),
568 usd_material_(usd_material)
569 {
570 material_ptr_ = RNA_pointer_create_discrete(nullptr, &RNA_Material, material);
571 }
572
573 bool result() const
574 {
575 return result_;
576 }
577
578 private:
579 const char *function_name() const override
580 {
581 return "on_material_import";
582 }
583
584 void call_hook(PyObject *hook_obj) override
585 {
586 result_ |= python::call_method<bool>(
587 hook_obj, function_name(), REF(hook_context_), material_ptr_, usd_material_);
588 }
589};
590
591void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports)
592{
593 if (hook_list().empty()) {
594 return;
595 }
596
597 OnExportInvoker on_export(stage, depsgraph, reports);
598 on_export.call();
599}
600
601void call_material_export_hooks(pxr::UsdStageRefPtr stage,
602 Material *material,
603 const pxr::UsdShadeMaterial &usd_material,
604 const USDExportParams &export_params,
605 ReportList *reports)
606{
607 if (hook_list().empty()) {
608 return;
609 }
610
611 OnMaterialExportInvoker on_material_export(
612 stage, material, usd_material, export_params, reports);
613 on_material_export.call();
614}
615
617{
618 if (hook_list().empty()) {
619 return;
620 }
621
622 const Vector<USDPrimReader *> &readers = archive->readers();
623 const ImportSettings &settings = archive->settings();
624 ImportedPrimMap prim_map;
625
626 /* Resize based on the typical scenario where there will be both Object and Data entries
627 * in the map in addition to each material. */
628 prim_map.reserve((readers.size() * 2) + settings.usd_path_to_mat.size());
629
630 for (const USDPrimReader *reader : readers) {
631 if (!reader) {
632 continue;
633 }
634
635 Object *ob = reader->object();
636
637 prim_map.lookup_or_add_default(reader->object_prim_path())
639 if (ob->data) {
640 prim_map.lookup_or_add_default(reader->data_prim_path())
641 .append(RNA_id_pointer_create(static_cast<ID *>(ob->data)));
642 }
643 }
644
645 settings.usd_path_to_mat.foreach_item([&prim_map](const pxr::SdfPath &path, Material *mat) {
647 });
648
649 OnImportInvoker on_import(archive->stage(), prim_map, reports);
650 on_import.call();
651}
652
653bool have_material_import_hook(pxr::UsdStageRefPtr stage,
654 const pxr::UsdShadeMaterial &usd_material,
655 const USDImportParams &import_params,
656 ReportList *reports)
657{
658 if (hook_list().empty()) {
659 return false;
660 }
661
662 MaterialImportPollInvoker poll(stage, usd_material, import_params, reports);
663 poll.call();
664
665 return poll.result();
666}
667
668bool call_material_import_hooks(pxr::UsdStageRefPtr stage,
669 Material *material,
670 const pxr::UsdShadeMaterial &usd_material,
671 const USDImportParams &import_params,
672 ReportList *reports)
673{
674 if (hook_list().empty()) {
675 return false;
676 }
677
678 OnMaterialImportInvoker on_material_import(
679 stage, material, usd_material, import_params, reports);
680 on_material_import.call();
681 return on_material_import.result();
682}
683
684} // namespace blender::io::usd
685
686#undef REF
687#undef PYTHON_NS
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
@ RPT_ERROR
Definition BKE_report.hh:39
#define final(a, b, c)
Definition BLI_hash.h:19
#define STREQ(a, b)
@ ID_IM
iter begin(iter)
BPy_StructRNA * depsgraph
PyObject * pyrna_struct_CreatePyObject(PointerRNA *ptr)
Definition bpy_rna.cc:8496
void foreach_item(const FuncT &func) const
Definition BLI_map.hh:687
int64_t size() const
Definition BLI_map.hh:976
void append(const T &value)
Value & lookup_or_add_default(const Key &key)
Definition BLI_map.hh:639
void reserve(int64_t n)
Definition BLI_map.hh:1028
int64_t size() const
MaterialImportPollInvoker(pxr::UsdStageRefPtr stage, const pxr::UsdShadeMaterial &usd_material, const USDImportParams &import_params, ReportList *reports)
Definition usd_hook.cc:521
OnExportInvoker(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports)
Definition usd_hook.cc:438
OnImportInvoker(pxr::UsdStageRefPtr stage, const ImportedPrimMap &prim_map, ReportList *reports)
Definition usd_hook.cc:492
OnMaterialExportInvoker(pxr::UsdStageRefPtr stage, Material *material, const pxr::UsdShadeMaterial &usd_material, const USDExportParams &export_params, ReportList *reports)
Definition usd_hook.cc:462
OnMaterialImportInvoker(pxr::UsdStageRefPtr stage, Material *material, const pxr::UsdShadeMaterial &usd_material, const USDImportParams &import_params, ReportList *reports)
Definition usd_hook.cc:561
USDHookInvoker(ReportList *reports)
Definition usd_hook.cc:369
virtual ~USDHookInvoker()=default
virtual const char * function_name() const =0
virtual void call_hook(PyObject *hook_obj)=0
pxr::UsdStageRefPtr get_stage() const
Definition usd_hook.cc:202
USDMaterialExportContext(pxr::UsdStageRefPtr stage, const USDExportParams &params, ReportList *reports)
Definition usd_hook.cc:195
std::string export_texture(PYTHON_NS::object obj) const
Definition usd_hook.cc:212
pxr::UsdStageRefPtr get_stage() const
Definition usd_hook.cc:256
USDMaterialImportContext(pxr::UsdStageRefPtr stage, const USDImportParams &params, ReportList *reports)
Definition usd_hook.cc:249
PYTHON_NS::tuple import_texture(const std::string &asset_path) const
Definition usd_hook.cc:269
USDSceneExportContext(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph)
Definition usd_hook.cc:123
pxr::UsdStageRefPtr get_stage() const
Definition usd_hook.cc:128
const PointerRNA & get_depsgraph() const
Definition usd_hook.cc:133
USDSceneImportContext(pxr::UsdStageRefPtr in_stage, const ImportedPrimMap &in_prim_map)
Definition usd_hook.cc:149
pxr::UsdStageRefPtr get_stage() const
Definition usd_hook.cc:159
const ImportSettings & settings() const
const blender::Vector< USDPrimReader * > & readers() const
#define GS(x)
bool pyrna_id_FromPyObject(PyObject *obj, ID **id)
Definition bpy_rna.cc:8611
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
const char * temp_textures_dir()
void USD_unregister_hook(const USDHook *hook)
Definition usd_hook.cc:82
bool should_import_asset(const std::string &path)
bool have_material_import_hook(pxr::UsdStageRefPtr stage, const pxr::UsdShadeMaterial &usd_material, const USDImportParams &import_params, ReportList *reports)
Definition usd_hook.cc:653
static void export_texture(const USDExporterContext &usd_export_context, bNode *node)
@ USD_TEX_IMPORT_PACK
Definition usd.hh:66
void USD_register_hook(std::unique_ptr< USDHook > hook)
Definition usd_hook.cc:71
static USDHookList & hook_list()
Definition usd_hook.cc:65
eUSDTexNameCollisionMode
Definition usd.hh:74
@ USD_TEX_NAME_COLLISION_OVERWRITE
Definition usd.hh:76
std::list< std::unique_ptr< USDHook > > USDHookList
Definition usd_hook.cc:61
static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_export_context, bNode *node)
void call_import_hooks(USDStageReader *archive, ReportList *reports)
Definition usd_hook.cc:616
Map< pxr::SdfPath, Vector< PointerRNA > > ImportedPrimMap
Definition usd_hook.cc:62
bool call_material_import_hooks(pxr::UsdStageRefPtr stage, Material *material, const pxr::UsdShadeMaterial &usd_material, const USDImportParams &import_params, ReportList *reports)
Definition usd_hook.cc:668
void call_material_export_hooks(pxr::UsdStageRefPtr stage, Material *material, const pxr::UsdShadeMaterial &usd_material, const USDExportParams &export_params, ReportList *reports)
Definition usd_hook.cc:601
void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports)
Definition usd_hook.cc:591
USDHook * USD_find_hook_name(const char idname[])
Definition usd_hook.cc:88
static void handle_python_error(USDHook *hook, ReportList *reports)
Definition usd_hook.cc:345
std::string import_asset(const std::string &src, const char *import_dir, eUSDTexNameCollisionMode name_collision_mode, ReportList *reports)
void register_hook_converters()
Definition usd_hook.cc:298
PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
PointerRNA RNA_id_pointer_create(ID *id)
Definition DNA_ID.h:414
char name[258]
Definition DNA_ID.h:432
blender::Map< pxr::SdfPath, Material * > usd_path_to_mat
static PyObject * convert(PointerRNA ptr)
Definition usd_hook.cc:108
ExtensionRNA rna_ext
Definition usd.hh:327
#define REF
Definition usd_hook.cc:54
PointerRNA * ptr
Definition wm_files.cc:4238