From ecb3262bf04992c62a1837dd1f2d541de8709cf2 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Mon, 7 Aug 2023 23:02:47 +0200 Subject: [PATCH] USD export: prototype invoking Python chasers This commit allows invoking user-defined Python 'hook' functions to extend the USD export functionality. Added support for registering subclasses of a new bpy.types.USDHook type which may implement the hooks as member functions. Supported hook functions are on_export() and on_material_export(). Also added definitions and Python registration for USDSceneExportContext and USDMaterialExportContext structs that encapsulate arguments to these functions. Pull Request: https://projects.blender.org/blender/blender/pulls/108823 --- doc/python_api/examples/bpy.types.USDHook.py | 106 +++++++ scripts/modules/bpy_types.py | 4 + source/blender/io/usd/CMakeLists.txt | 2 + .../blender/io/usd/intern/usd_capi_export.cc | 6 + source/blender/io/usd/intern/usd_hook.cc | 300 ++++++++++++++++++ source/blender/io/usd/intern/usd_hook.h | 27 ++ .../io/usd/intern/usd_writer_abstract.cc | 1 + .../io/usd/intern/usd_writer_material.cc | 3 + source/blender/io/usd/usd.h | 25 ++ source/blender/makesrna/intern/CMakeLists.txt | 8 + source/blender/makesrna/intern/makesrna.cc | 3 + source/blender/makesrna/intern/rna_internal.h | 1 + source/blender/makesrna/intern/rna_usd.cc | 163 ++++++++++ 13 files changed, 649 insertions(+) create mode 100644 doc/python_api/examples/bpy.types.USDHook.py create mode 100644 source/blender/io/usd/intern/usd_hook.cc create mode 100644 source/blender/io/usd/intern/usd_hook.h create mode 100644 source/blender/makesrna/intern/rna_usd.cc diff --git a/doc/python_api/examples/bpy.types.USDHook.py b/doc/python_api/examples/bpy.types.USDHook.py new file mode 100644 index 00000000000..d38a853d433 --- /dev/null +++ b/doc/python_api/examples/bpy.types.USDHook.py @@ -0,0 +1,106 @@ +""" +USD Hook Example +++++++++++++++++ + +This example shows an implementation of ``USDHook`` to extend USD +export functionalty. + +One may optionally define one or both of the following callback functions +in the ``USDHook`` subclass. + +Hook function ``on_export()`` is called before the USD export finalizes, +allowing modifications to the USD stage immediately before it is +saved. This function takes as an argument an instance of an +internally defined class ``USDSceneExportContext`` which provides the +following accessors to the scene data: + +- ``get_stage()`` returns the USD stage to be saved. +- ``get_depsgraph()`` returns the Blender scene dependency graph. + +Hook function ``on_material_export()`` is called for each material that is exported, +allowing modifications to the USD material, such as shader generation. +It is called with three arguments: + +-``export_context``: An instance of the internally defined type ``USDMaterialExportContext``. +-``bl_material``: The source Blender material. +-``usd_material``: The target USD material to be exported. + +``USDMaterialExportContext`` implements a ``get_stage()`` function which returns the +USD stage to be saved. + +Note that the target USD material might already have connected shaders created by the USD exporter or +by other material export hooks. + +The hook functions should return ``True`` on success or ``False`` if the operation was bypasssed or +otherwise failed to complete. Exceptions raised by these functions will be reported in Blender, with +the exception details printed to the console. + +The ``USDHookExample`` class in this example impements an ``on_export()`` function to add custom data to +the stage's root layer and an ``on_material_export()`` function to create a simple ``MaterialX`` shader +on the USD material. + +""" + +import bpy +import bpy.types +import pxr.Gf as Gf +import pxr.Sdf as Sdf +import pxr.Usd as Usd +import pxr.UsdShade as UsdShade + +class USDHookExample(bpy.types.USDHook): + bl_idname = "usd_hook_example" + bl_label = "Example" + bl_description = "Example implementation of USD IO hooks" + + @staticmethod + def on_export(export_context): + """ Include the Blender filepath in the root layer custom data. + """ + + stage = export_context.get_stage() + + if stage is None: + return False + data = bpy.data + if data is None: + return False + + # Set the custom data. + rootLayer = stage.GetRootLayer() + customData = rootLayer.customLayerData + customData["blenderFilepath"] = data.filepath + rootLayer.customLayerData = customData + + return True + + @staticmethod + def on_material_export(export_context, bl_material, usd_material): + """ Create a simple MaterialX shader on the exported material. + """ + + stage = export_context.get_stage() + + # Create a MaterialX standard surface shader + mtl_path = usd_material.GetPrim().GetPath() + shader = UsdShade.Shader.Define(stage, mtl_path.AppendPath("mtlxstandard_surface")) + shader.CreateIdAttr("ND_standard_surface_surfaceshader") + + # Connect the shader. MaterialX materials use "mtlx" renderContext + usd_material.CreateSurfaceOutput("mtlx").ConnectToSource(shader.ConnectableAPI(), "out") + + # Set the color to the Blender material's viewport display color. + col = bl_material.diffuse_color + shader.CreateInput("base_color", Sdf.ValueTypeNames.Color3f).Set(Gf.Vec3f(col[0], col[1], col[2])) + + return True + + +def register(): + bpy.utils.register_class(USDHookExample) + +def unregister(): + bpy.utils.unregister_class(USDHookExample) + +if __name__ == "__main__": + register() diff --git a/scripts/modules/bpy_types.py b/scripts/modules/bpy_types.py index e15a76719d8..49d5bc2fbe9 100644 --- a/scripts/modules/bpy_types.py +++ b/scripts/modules/bpy_types.py @@ -942,6 +942,10 @@ class KeyingSetInfo(StructRNA, metaclass=RNAMeta): __slots__ = () +class USDHook(StructRNA, metaclass=RNAMeta): + __slots__ = () + + class AddonPreferences(StructRNA, metaclass=RNAMeta): __slots__ = () diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt index 22e9aad0a75..25acf4dfd91 100644 --- a/source/blender/io/usd/CMakeLists.txt +++ b/source/blender/io/usd/CMakeLists.txt @@ -86,6 +86,7 @@ set(SRC intern/usd_capi_export.cc intern/usd_capi_import.cc intern/usd_hierarchy_iterator.cc + intern/usd_hook.cc intern/usd_writer_abstract.cc intern/usd_writer_camera.cc intern/usd_writer_curves.cc @@ -116,6 +117,7 @@ set(SRC intern/usd_asset_utils.h intern/usd_exporter_context.h intern/usd_hierarchy_iterator.h + intern/usd_hook.h intern/usd_writer_abstract.h intern/usd_writer_camera.h intern/usd_writer_curves.h diff --git a/source/blender/io/usd/intern/usd_capi_export.cc b/source/blender/io/usd/intern/usd_capi_export.cc index fe474cab428..3f6db4f1b4f 100644 --- a/source/blender/io/usd/intern/usd_capi_export.cc +++ b/source/blender/io/usd/intern/usd_capi_export.cc @@ -5,6 +5,7 @@ #include "usd.h" #include "usd.hh" #include "usd_hierarchy_iterator.h" +#include "usd_hook.h" #include #include @@ -228,6 +229,9 @@ static pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms, /* For restoring the current frame after exporting animation is done. */ const int orig_frame = scene->r.cfra; + /* Ensure Python types for invoking export hooks are registered. */ + register_export_hook_converters(); + usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, pxr::VtValue(pxr::UsdGeomTokens->z)); ensure_root_prim(usd_stage, params); @@ -275,6 +279,8 @@ static pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms, } } + call_export_hooks(usd_stage, depsgraph); + /* Finish up by going back to the keyframe that was current before we started. */ if (scene->r.cfra != orig_frame) { scene->r.cfra = orig_frame; diff --git a/source/blender/io/usd/intern/usd_hook.cc b/source/blender/io/usd/intern/usd_hook.cc new file mode 100644 index 00000000000..9b8913fec8e --- /dev/null +++ b/source/blender/io/usd/intern/usd_hook.cc @@ -0,0 +1,300 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "usd.h" + +#include "usd_hook.h" + +#include +#include +#include +#include +#include +#include + +#include "BLI_listbase.h" + +#include "RNA_access.h" +#include "RNA_prototypes.h" +#include "RNA_types.h" +#include "bpy_rna.h" + +#include "WM_api.hh" +#include "WM_types.hh" + +#include + +using namespace boost; + +using USDHookList = std::list; + +/* USD hook type declarations */ +static USDHookList g_usd_hooks; + +void USD_register_hook(struct USDHook *hook) +{ + if (std::find(g_usd_hooks.begin(), g_usd_hooks.end(), hook) != g_usd_hooks.end()) { + /* The hook is already in the list. */ + return; + } + + /* Add hook type to the list. */ + g_usd_hooks.push_back(hook); +} + +void USD_unregister_hook(struct USDHook *hook) +{ + g_usd_hooks.remove(hook); +} + +USDHook *USD_find_hook_name(const char name[]) +{ + /* sanity checks */ + if (g_usd_hooks.empty() || (name == NULL) || (name[0] == 0)) { + return NULL; + } + + USDHookList::iterator hook_iter = std::find_if( + g_usd_hooks.begin(), g_usd_hooks.end(), [name](USDHook *hook) { + return strcmp(hook->idname, name) == 0; + }); + + return (hook_iter == g_usd_hooks.end()) ? NULL : *hook_iter; +} + +namespace blender::io::usd { + +/* Convert PointerRNA to a PyObject*. */ +struct PointerRNAToPython { + + /* We pass the argument by value because we need + * to obtain a non-const pointer to it. */ + static PyObject *convert(PointerRNA ptr) + { + return pyrna_struct_CreatePyObject(&ptr); + } +}; + +/* Encapsulate arguments for scene export. */ +struct USDSceneExportContext { + + USDSceneExportContext() : depsgraph_ptr({}) {} + + USDSceneExportContext(pxr::UsdStageRefPtr in_stage, Depsgraph *depsgraph) : stage(in_stage) + { + RNA_pointer_create(NULL, &RNA_Depsgraph, depsgraph, &depsgraph_ptr); + } + + pxr::UsdStageRefPtr get_stage() + { + return stage; + } + + const PointerRNA &get_depsgraph() const + { + return depsgraph_ptr; + } + + pxr::UsdStageRefPtr stage; + PointerRNA depsgraph_ptr; +}; + +/* Encapsulate arguments for material export. */ +struct USDMaterialExportContext { + USDMaterialExportContext() {} + + USDMaterialExportContext(pxr::UsdStageRefPtr in_stage) : stage(in_stage) {} + + pxr::UsdStageRefPtr get_stage() + { + return stage; + } + + pxr::UsdStageRefPtr stage; +}; + +void register_export_hook_converters() +{ + static bool registered = false; + + /* No need to register if there are no hooks. */ + if (g_usd_hooks.empty()) { + return; + } + + if (registered) { + return; + } + + registered = true; + + PyGILState_STATE gilstate = PyGILState_Ensure(); + + /* We must import these modules for the USD type converters to work. */ + python::import("pxr.Usd"); + python::import("pxr.UsdShade"); + + /* Register converter from PoinerRNA to a PyObject*. */ + python::to_python_converter(); + + /* Register context class converters. */ + python::class_("USDSceneExportContext") + .def("get_stage", &USDSceneExportContext::get_stage) + .def("get_depsgraph", + &USDSceneExportContext::get_depsgraph, + python::return_value_policy()); + + python::class_("USDMaterialExportContext") + .def("get_stage", &USDMaterialExportContext::get_stage); + + PyGILState_Release(gilstate); +} + +/* Retrieve and report the current Python error. */ +static void handle_python_error(USDHook *hook) +{ + if (!PyErr_Occurred()) { + return; + } + + PyErr_Print(); + + WM_reportf(RPT_ERROR, + "An exception occurred invoking USD hook '%s'. Please see the console for details", + hook->name); +} + +/* Abstract base class to facilitate calling a function with a given + * signature defined by the registered USDHook classes. Subclasses + * override virtual methods to specify the hook function name and to + * call the hook with the required arguments. + */ +class USDHookInvoker { + public: + /* Attempt to call the function, if defined by the registered hooks. */ + void call() const + { + if (g_usd_hooks.empty()) { + return; + } + + PyGILState_STATE gilstate = PyGILState_Ensure(); + + /* Iterate over the hooks and invoke the hook function, if it's defined. */ + USDHookList::const_iterator hook_iter = g_usd_hooks.begin(); + while (hook_iter != g_usd_hooks.end()) { + + /* XXX: Not sure if this is necessary: + * Advance the iterator before invoking the callback, to guard + * against the unlikely error where the hook is deregistered in + * the callback. This would prevent a crash due to the iterator + * getting invalidated. */ + USDHook *hook = *hook_iter; + ++hook_iter; + + if (!hook->rna_ext.data) { + continue; + } + + try { + PyObject *hook_obj = static_cast(hook->rna_ext.data); + + if (!PyObject_HasAttrString(hook_obj, function_name())) { + continue; + } + + call_hook(hook_obj); + } + catch (python::error_already_set const &) { + handle_python_error(hook); + } + catch (...) { + WM_reportf(RPT_ERROR, "An exception occurred invoking USD hook '%s'", hook->name); + } + } + + PyGILState_Release(gilstate); + } + + protected: + /* Override to specify the name of the function to be called. */ + virtual const char *function_name() const = 0; + /* Override to call the function of the given object with the + * required arguments, e.g., + * + * python::call_method(hook_obj, function_name(), arg1, arg2); */ + virtual void call_hook(PyObject *hook_obj) const = 0; +}; + +class OnExportInvoker : public USDHookInvoker { + private: + USDSceneExportContext hook_context_; + + public: + OnExportInvoker(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph) + : hook_context_(stage, depsgraph) + { + } + + protected: + const char *function_name() const override + { + return "on_export"; + } + + void call_hook(PyObject *hook_obj) const override + { + python::call_method(hook_obj, function_name(), hook_context_); + } +}; + +class OnMaterialExportInvoker : public USDHookInvoker { + private: + USDMaterialExportContext hook_context_; + pxr::UsdShadeMaterial usd_material_; + PointerRNA material_ptr_; + + public: + OnMaterialExportInvoker(pxr::UsdStageRefPtr stage, + Material *material, + pxr::UsdShadeMaterial &usd_material) + : hook_context_(stage), usd_material_(usd_material) + { + RNA_pointer_create(NULL, &RNA_Material, material, &material_ptr_); + } + + protected: + const char *function_name() const override + { + return "on_material_export"; + } + + void call_hook(PyObject *hook_obj) const override + { + python::call_method( + hook_obj, function_name(), hook_context_, material_ptr_, usd_material_); + } +}; + +void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph) +{ + if (g_usd_hooks.empty()) { + return; + } + + OnExportInvoker on_export(stage, depsgraph); + on_export.call(); +} + +void call_material_export_hooks(pxr::UsdStageRefPtr stage, + Material *material, + pxr::UsdShadeMaterial &usd_material) +{ + if (g_usd_hooks.empty()) { + return; + } + + OnMaterialExportInvoker on_material_export(stage, material, usd_material); + on_material_export.call(); +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_hook.h b/source/blender/io/usd/intern/usd_hook.h new file mode 100644 index 00000000000..66990dea728 --- /dev/null +++ b/source/blender/io/usd/intern/usd_hook.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma once + +#include +#include + +#include + +struct Depsgraph; +struct ExportJobData; +struct Material; +struct USDExportParams; + +namespace blender::io::usd { + +/* Ensure classes and type converters necessary for invoking export hook are registered. */ +void register_export_hook_converters(); + +/* Call the 'on_export' chaser function defined in the registred USDHook classes. */ +void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph); + +/* Call the 'on_material_export' hook functions defined in the registered USDHook classes. */ +void call_material_export_hooks(pxr::UsdStageRefPtr stage, + Material *material, + pxr::UsdShadeMaterial &usd_material); + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_writer_abstract.cc b/source/blender/io/usd/intern/usd_writer_abstract.cc index 50c027e69d0..b41aa8054ad 100644 --- a/source/blender/io/usd/intern/usd_writer_abstract.cc +++ b/source/blender/io/usd/intern/usd_writer_abstract.cc @@ -113,6 +113,7 @@ pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyCont if (usd_material) { return usd_material; } + std::string active_uv = get_mesh_active_uvlayer_name(context.object); return create_usd_material(usd_export_context_, usd_path, material, active_uv); } diff --git a/source/blender/io/usd/intern/usd_writer_material.cc b/source/blender/io/usd/intern/usd_writer_material.cc index 7049390dbe3..8389d6f0201 100644 --- a/source/blender/io/usd/intern/usd_writer_material.cc +++ b/source/blender/io/usd/intern/usd_writer_material.cc @@ -6,6 +6,7 @@ #include "usd.h" #include "usd_exporter_context.h" +#include "usd_hook.h" #include "BKE_image.h" #include "BKE_image_format.h" @@ -841,6 +842,8 @@ pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_c create_usd_viewport_material(usd_export_context, material, usd_material); } + call_material_export_hooks(usd_export_context.stage, material, usd_material); + return usd_material; } diff --git a/source/blender/io/usd/usd.h b/source/blender/io/usd/usd.h index 61974b252fd..d634553d4e1 100644 --- a/source/blender/io/usd/usd.h +++ b/source/blender/io/usd/usd.h @@ -6,6 +6,8 @@ #include "DEG_depsgraph.h" +#include "RNA_types.h" + #ifdef __cplusplus extern "C" { #endif @@ -149,6 +151,29 @@ struct CacheReader *CacheReader_open_usd_object(struct CacheArchiveHandle *handl void USD_CacheReader_incref(struct CacheReader *reader); void USD_CacheReader_free(struct CacheReader *reader); + +/* Data for registering USD IO hooks. */ +typedef struct USDHook { + + /* Identifier used for class name. */ + char idname[64]; + /* Identifier used as label. */ + char name[64]; + /* Short help/description. */ + char description[1024]; /* #RNA_DYN_DESCR_MAX */ + + /* rna_ext.data points to the USDHook class PyObject. */ + struct ExtensionRNA rna_ext; +} USDHook; + +void USD_register_hook(struct USDHook *hook); +/* Remove the given entry from the list of registered hooks. + * Note that this does not free the allocated memory for the + * hook instance, so a separate call to MEM_freeN(hook) is + * required. */ +void USD_unregister_hook(struct USDHook *hook); +USDHook *USD_find_hook_name(const char name[]); + #ifdef __cplusplus } #endif diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt index 166714d45d6..6e3b77cbf57 100644 --- a/source/blender/makesrna/intern/CMakeLists.txt +++ b/source/blender/makesrna/intern/CMakeLists.txt @@ -90,6 +90,13 @@ set(DEFSRC rna_xr.cc ) +if(WITH_USD) + list(APPEND DEFSRC + rna_usd.cc + ) + add_definitions(-DWITH_USD) +endif() + if(WITH_EXPERIMENTAL_FEATURES) add_definitions(-DWITH_SIMULATION_DATABLOCK) add_definitions(-DWITH_GREASE_PENCIL_V3) @@ -229,6 +236,7 @@ set(INC ../../gpu ../../ikplugin ../../imbuf + ../../io/usd ../../modifiers ../../nodes ../../sequencer diff --git a/source/blender/makesrna/intern/makesrna.cc b/source/blender/makesrna/intern/makesrna.cc index f313938374a..2d68e38c486 100644 --- a/source/blender/makesrna/intern/makesrna.cc +++ b/source/blender/makesrna/intern/makesrna.cc @@ -4750,6 +4750,9 @@ static RNAProcessItem PROCESS_ITEMS[] = { {"rna_timeline.cc", nullptr, RNA_def_timeline_marker}, {"rna_sound.cc", "rna_sound_api.cc", RNA_def_sound}, {"rna_ui.cc", "rna_ui_api.cc", RNA_def_ui}, +#ifdef WITH_USD + {"rna_usd.cc", NULL, RNA_def_usd}, +#endif {"rna_userdef.cc", nullptr, RNA_def_userdef}, {"rna_vfont.cc", "rna_vfont_api.cc", RNA_def_vfont}, {"rna_volume.cc", nullptr, RNA_def_volume}, diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h index baad472cc94..b75ad11ffcf 100644 --- a/source/blender/makesrna/intern/rna_internal.h +++ b/source/blender/makesrna/intern/rna_internal.h @@ -202,6 +202,7 @@ void RNA_def_texture(struct BlenderRNA *brna); void RNA_def_timeline_marker(struct BlenderRNA *brna); void RNA_def_sound(struct BlenderRNA *brna); void RNA_def_ui(struct BlenderRNA *brna); +void RNA_def_usd(struct BlenderRNA *brna); void RNA_def_userdef(struct BlenderRNA *brna); void RNA_def_vfont(struct BlenderRNA *brna); void RNA_def_volume(struct BlenderRNA *brna); diff --git a/source/blender/makesrna/intern/rna_usd.cc b/source/blender/makesrna/intern/rna_usd.cc new file mode 100644 index 00000000000..92ff67ae2f9 --- /dev/null +++ b/source/blender/makesrna/intern/rna_usd.cc @@ -0,0 +1,163 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup RNA + */ + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "rna_internal.h" + +#include "WM_types.hh" + +#include "usd.h" + +#ifdef RNA_RUNTIME + +# include "DNA_object_types.h" +# include "WM_api.hh" + +static StructRNA *rna_USDHook_refine(PointerRNA *ptr) +{ + USDHook *hook = (USDHook *)ptr->data; + return (hook->rna_ext.srna) ? hook->rna_ext.srna : &RNA_USDHook; +} + +static bool rna_USDHook_unregister(Main *bmain, StructRNA *type) +{ + USDHook *hook = static_cast(RNA_struct_blender_type_get(type)); + + if (hook == NULL) { + return false; + } + + /* free RNA data referencing this */ + RNA_struct_free_extension(type, &hook->rna_ext); + RNA_struct_free(&BLENDER_RNA, type); + + WM_main_add_notifier(NC_WINDOW, NULL); + + /* unlink Blender-side data */ + USD_unregister_hook(hook); + + MEM_freeN(hook); + + return true; +} + +static StructRNA *rna_USDHook_register(Main *bmain, + ReportList *reports, + void *data, + const char *identifier, + StructValidateFunc validate, + StructCallbackFunc call, + StructFreeFunc free) +{ + const char *error_prefix = "Registering USD hook class:"; + USDHook dummy_hook = {NULL}; + USDHook *hook; + PointerRNA dummy_hook_ptr = {NULL}; + + /* setup dummy type info to store static properties in */ + RNA_pointer_create(NULL, &RNA_USDHook, &dummy_hook, &dummy_hook_ptr); + + /* validate the python class */ + if (validate(&dummy_hook_ptr, data, NULL) != 0) { + return NULL; + } + + if (strlen(identifier) >= sizeof(dummy_hook.idname)) { + BKE_reportf(reports, + RPT_ERROR, + "%s '%s' is too long, maximum length is %d", + error_prefix, + identifier, + (int)sizeof(dummy_hook.idname)); + return NULL; + } + + /* check if we have registered this hook before, and remove it */ + hook = USD_find_hook_name(dummy_hook.idname); + if (hook) { + StructRNA *srna = hook->rna_ext.srna; + if (!rna_USDHook_unregister(bmain, srna)) { + BKE_reportf(reports, + RPT_ERROR, + "%s '%s', bl_idname '%s' %s", + error_prefix, + identifier, + dummy_hook.idname, + "could not be unregistered"); + return NULL; + } + } + + /* create a new KeyingSetInfo type */ + hook = static_cast(MEM_mallocN(sizeof(USDHook), "python USD hook")); + memcpy(hook, &dummy_hook, sizeof(USDHook)); + + /* set RNA-extensions info */ + hook->rna_ext.srna = RNA_def_struct_ptr(&BLENDER_RNA, hook->idname, &RNA_USDHook); + hook->rna_ext.data = data; + hook->rna_ext.call = call; + hook->rna_ext.free = free; + RNA_struct_blender_type_set(hook->rna_ext.srna, hook); + + /* add and register with other info as needed */ + USD_register_hook(hook); + + WM_main_add_notifier(NC_WINDOW, NULL); + + /* return the struct-rna added */ + return hook->rna_ext.srna; +} + +#else + +static void rna_def_usd_hook(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + FunctionRNA *func; + PropertyRNA *parm; + + srna = RNA_def_struct(brna, "USDHook", NULL); + RNA_def_struct_ui_text(srna, "USD Hook", "Defines callback functions to extend USD IO"); + RNA_def_struct_sdna(srna, "USDHook"); + RNA_def_struct_refine_func(srna, "rna_USDHook_refine"); + RNA_def_struct_register_funcs(srna, "rna_USDHook_register", "rna_USDHook_unregister", NULL); + + ///* Properties --------------------- */ + + RNA_define_verify_sdna(0); /* not in sdna */ + + prop = RNA_def_property(srna, "bl_idname", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "idname"); + RNA_def_property_flag(prop, PROP_REGISTER); + RNA_def_property_ui_text(prop, "ID Name", ""); + + prop = RNA_def_property(srna, "bl_label", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "name"); + RNA_def_property_ui_text(prop, "UI Name", ""); + RNA_def_struct_name_property(srna, prop); + RNA_def_property_flag(prop, PROP_REGISTER); + + prop = RNA_def_property(srna, "bl_description", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "description"); + RNA_def_property_string_maxlength(prop, RNA_DYN_DESCR_MAX); /* else it uses the pointer size! */ + RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL); + RNA_def_property_ui_text(prop, "Description", "A short description of the USD hook"); +} + +/* --- */ + +void RNA_def_usd(BlenderRNA *brna) +{ + rna_def_usd_hook(brna); +} + +#endif