USD export: prototype invoking Python chasers #108823

Merged
Michael Kowalski merged 35 commits from makowalski/blender:test-usd-export-chaser into main 2023-08-07 23:02:54 +02:00
3 changed files with 74 additions and 20 deletions
Showing only changes of commit 808ca68414 - Show all commits

View File

@ -297,6 +297,7 @@ static void export_startjob(void *customdata,
}
}
register_export_hook_converters();
call_export_hooks(usd_stage, data->depsgraph);
usd_stage->GetRootLayer()->Save();

View File

@ -6,6 +6,9 @@
#include <boost/python/object.hpp>
#include <boost/python/call_method.hpp>
#include <boost/python/class.hpp>
#include <boost/python/return_value_policy.hpp>
#include <boost/python/to_python_converter.hpp>
#include "BLI_listbase.h"
@ -57,6 +60,71 @@ USDHook *USD_find_hook_name(const char name[])
namespace blender::io::usd {
/* Convert PointerRNA to a PyObject*. */
struct PointerRNAToPython {
static PyObject *convert(const PointerRNA &pRNA)
{
return pyrna_struct_CreatePyObject(const_cast<PointerRNA*>(&pRNA));
}
};
/* 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 GetStage()
{
return stage;
}
const PointerRNA &GetDepsgraph()
{
return depsgraph_ptr;
}
pxr::UsdStageRefPtr stage;
PointerRNA depsgraph_ptr;
};
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;
makowalski marked this conversation as resolved Outdated

Would it make sense to pass in a struct or object with the stage as a field instead of the stage directly?
I'm not sure how much work it would be, but it would have the advantage that when you add extra parameters, they could simply be added as fields on the struct/object.

Maya does something similar where chasers are passed a factoryContext , and you do factoryContext.GetStage() to get the stage.

The reason I suggest that is because if in the future we add more arguments, it changes the function signature, thereby potentially breaking existing add-ons unless they also specify *args, **kwargs to capture unknowns. Whereas if they simply accept a singular object as the argument, then new parameters can be passed in on that object, keeping the signature of the function intact.

Would it make sense to pass in a struct or object with the stage as a field instead of the stage directly? I'm not sure how much work it would be, but it would have the advantage that when you add extra parameters, they could simply be added as fields on the struct/object. Maya does something similar where chasers are passed a `factoryContext` , and you do `factoryContext.GetStage()` to get the stage. The reason I suggest that is because if in the future we add more arguments, it changes the function signature, thereby potentially breaking existing add-ons unless they also specify `*args, **kwargs` to capture unknowns. Whereas if they simply accept a singular object as the argument, then new parameters can be passed in on that object, keeping the signature of the function intact.

Thanks for the suggestion, Dhruv. What you propose definitely makes sense. I'm guessing falling back on *args, **kwargs for arbitrary arguments would be the least amount of work, but a custom struct would be a much cleaner API. We can discuss further. I'm currently finishing up the changes requested by Brecht as well as a simple example of material conversion. I should be done with this in the next couple of days. In the meantime, I'll give some thought to this issue.

Thanks for the suggestion, Dhruv. What you propose definitely makes sense. I'm guessing falling back on `*args, **kwargs` for arbitrary arguments would be the least amount of work, but a custom struct would be a much cleaner API. We can discuss further. I'm currently finishing up the changes requested by Brecht as well as a simple example of material conversion. I should be done with this in the next couple of days. In the meantime, I'll give some thought to this issue.

Dhruv, I've determined that providing a struct as an argument isn't much extra work, so I'll update the prototype to use this approach in the next day or so. Thanks, again, for suggesting this.

Dhruv, I've determined that providing a struct as an argument isn't much extra work, so I'll update the prototype to use this approach in the next day or so. Thanks, again, for suggesting this.

That's great to hear, and thank you for doing that. I think it'll help long term with API stability.

That's great to hear, and thank you for doing that. I think it'll help long term with API stability.

That's great to hear, and thank you for doing that. I think it'll help long term with API stability.

That's great to hear, and thank you for doing that. I think it'll help long term with API stability.
PyGILState_STATE gilstate = PyGILState_Ensure();
python::to_python_converter<PointerRNA, PointerRNAToPython>();
python::class_<USDSceneExportContext>("USDSceneExportContext")
.def("GetStage", &USDSceneExportContext::GetStage)
.def("GetDepsgraph",
&USDSceneExportContext::GetDepsgraph,
python::return_value_policy<python::return_by_value>())
;
PyGILState_Release(gilstate);
}
void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph)
{
@ -69,9 +137,6 @@ void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph)
/* The chaser function name. */
const char *func_name = "on_export";
PointerRNA depsgraph_ptr;
PyObject *depsgraph_obj = nullptr;
/* 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()) {
@ -95,22 +160,11 @@ void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph)
continue;
}
if (!depsgraph_obj) {
RNA_pointer_create(NULL, &RNA_Depsgraph, depsgraph, &depsgraph_ptr);
depsgraph_obj = pyrna_struct_CreatePyObject(&depsgraph_ptr);
}
USDSceneExportContext hook_context(stage, depsgraph);
if (!depsgraph_obj) {
continue;
}
/* Invoke the chaser. Additional arguments could be
* provided, e.g., a dictionary mapping Blender objects
* to USD prims. */
python::call_method<void>(hook_obj,
func_name,
python::object(python::handle<>(depsgraph_obj)),
stage);
hook_context);
}
catch (...) {
if (PyErr_Occurred()) {
@ -119,10 +173,6 @@ void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph)
}
}
if (depsgraph_obj) {
Py_DECREF(depsgraph_obj);
}
PyGILState_Release(gilstate);
}

View File

@ -11,6 +11,9 @@ 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);