USD: basic support for on_import USD hooks

Added support for defining an on_import() function in
bpy.types.USDHook subclasses.  If on_import() is defined
on a given USD hook, it will be invoked in import_endjob().

The implementation closely follows the existing design of
export hooks.  USDHook.on_import() takes as an argument
an instance of an internally defined USDSceneImportContext
class which provides an accessor to the USD stage.

Also updated the USDHook documentation with an example
on_import() callback implementation.

Pull Request: https://projects.blender.org/blender/blender/pulls/117822
This commit is contained in:
Michael Kowalski 2024-02-05 14:26:02 +01:00 committed by Michael Kowalski
parent a3183fb95f
commit ecbf3385c5
5 changed files with 118 additions and 11 deletions

@ -3,9 +3,9 @@ USD Hook Example
++++++++++++++++
This example shows an implementation of ``USDHook`` to extend USD
export functionalty.
export and import functionalty.
One may optionally define one or both of the following callback functions
One may optionally define any or all of the following callback functions
in the ``USDHook`` subclass.
Hook function ``on_export()`` is called before the USD export finalizes,
@ -31,13 +31,21 @@ 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
Hook function ``on_import()`` is called after the USD import finalizes. This function takes
as an argument an instance of an internally defined class ``USDSceneImportContext`` which provides the
following accessors to the scene data:
- ``get_stage()`` returns the USD stage which was imported.
The hook functions should return ``True`` on success or ``False`` if the operation was bypassed 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.
The ``USDHookExample`` class in this example impements the fllowing functions:
- ``on_export()`` function to add custom data to the stage's root layer.
- ``on_material_export()`` function to create a simple ``MaterialX`` shader on the givne USD material.
- ``on_import()`` function to create a text object to display the stage's custom layer data.
"""
@ -96,6 +104,45 @@ class USDHookExample(bpy.types.USDHook):
return True
@staticmethod
def on_import(import_context):
""" Create a text object to display the stage's custom data.
"""
stage = import_context.get_stage()
if stage is None:
return False
# Get the custom data.
rootLayer = stage.GetRootLayer()
customData = rootLayer.customLayerData
# Create a text object to display the stage path
# and custom data dictionary entries.
bpy.ops.object.text_add()
ob = bpy.context.view_layer.objects.active
if (ob is None) or (ob.data is None):
return False
ob.name = "layer_data"
ob.data.name = "layer_data"
# The stage root path is the first line.
text = rootLayer.realPath
# Append key/value strings, enforcing text wrapping.
for item in customData.items():
print(item)
text += '\n'
line = str(item[0]) + ': ' + str(item[1])
text += textwrap.fill(line, width=80)
ob.data.body = text
return True
def register():
bpy.utils.register_class(USDHookExample)

@ -255,8 +255,8 @@ pxr::UsdStageRefPtr export_to_stage(const USDExportParams &params,
/* 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();
/* Ensure Python types for invoking hooks are registered. */
register_hook_converters();
usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, pxr::VtValue(pxr::UsdGeomTokens->z));
ensure_root_prim(usd_stage, params);

@ -5,6 +5,7 @@
#include "IO_types.hh"
#include "usd.h"
#include "usd_hierarchy_iterator.h"
#include "usd_hook.h"
#include "usd_reader_geom.h"
#include "usd_reader_prim.h"
#include "usd_reader_stage.h"
@ -451,6 +452,11 @@ static void import_endjob(void *customdata)
if (data->params.import_materials && data->params.import_all_materials) {
data->archive->fake_users_for_unused_materials();
}
/* Ensure Python types for invoking hooks are registered. */
register_hook_converters();
call_import_hooks(data->archive->stage(), data->params.worker_status->reports);
}
WM_set_locked_interface(data->wm, false);

@ -102,6 +102,21 @@ struct USDSceneExportContext {
PointerRNA depsgraph_ptr;
};
/* Encapsulate arguments for scene import. */
struct USDSceneImportContext {
USDSceneImportContext() {}
USDSceneImportContext(pxr::UsdStageRefPtr in_stage) : stage(in_stage) {}
pxr::UsdStageRefPtr get_stage()
{
return stage;
}
pxr::UsdStageRefPtr stage;
};
/* Encapsulate arguments for material export. */
struct USDMaterialExportContext {
USDMaterialExportContext() {}
@ -116,7 +131,7 @@ struct USDMaterialExportContext {
pxr::UsdStageRefPtr stage;
};
void register_export_hook_converters()
void register_hook_converters()
{
static bool registered = false;
@ -150,6 +165,9 @@ void register_export_hook_converters()
python::class_<USDMaterialExportContext>("USDMaterialExportContext")
.def("get_stage", &USDMaterialExportContext::get_stage);
python::class_<USDSceneImportContext>("USDSceneImportContext")
.def("get_stage", &USDSceneImportContext::get_stage);
PyGILState_Release(gilstate);
}
@ -287,6 +305,28 @@ class OnMaterialExportInvoker : public USDHookInvoker {
}
};
class OnImportInvoker : public USDHookInvoker {
private:
USDSceneImportContext hook_context_;
public:
OnImportInvoker(pxr::UsdStageRefPtr stage, ReportList *reports) : hook_context_(stage)
{
reports_ = reports;
}
protected:
const char *function_name() const override
{
return "on_import";
}
void call_hook(PyObject *hook_obj) const override
{
python::call_method<bool>(hook_obj, function_name(), hook_context_);
}
};
void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports)
{
if (g_usd_hooks.empty()) {
@ -310,4 +350,14 @@ void call_material_export_hooks(pxr::UsdStageRefPtr stage,
on_material_export.call();
}
void call_import_hooks(pxr::UsdStageRefPtr stage, ReportList *reports)
{
if (g_usd_hooks.empty()) {
return;
}
OnImportInvoker on_import(stage, reports);
on_import.call();
}
} // namespace blender::io::usd

@ -16,8 +16,9 @@ struct USDExportParams;
namespace blender::io::usd {
/** Ensure classes and type converters necessary for invoking export hook are registered. */
void register_export_hook_converters();
/** Ensure classes and type converters necessary for invoking import and export hooks
* are registered. */
void register_hook_converters();
/** Call the 'on_export' chaser function defined in the registered USDHook classes. */
void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports);
@ -28,4 +29,7 @@ void call_material_export_hooks(pxr::UsdStageRefPtr stage,
pxr::UsdShadeMaterial &usd_material,
ReportList *reports);
/** Call the 'on_import' chaser function defined in the registered USDHook classes. */
void call_import_hooks(pxr::UsdStageRefPtr stage, ReportList *reports);
} // namespace blender::io::usd