USD export: Skel Root validation.

Added function for verifying that skinned prims
and skeletons are properly grouped under a common
SkelRoot.  Also added a Fix Skel Root export
option to attempt to fix the hierarchy if the Skel
Root is invalid.
This commit is contained in:
Michael Kowalski 2021-12-07 12:42:42 -05:00
parent 8ef0925c83
commit 8ca67ef025
5 changed files with 134 additions and 7 deletions

View File

@ -313,6 +313,8 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
const eUSDXformOpMode xform_op_mode = RNA_enum_get(op->ptr, "xform_op_mode");
const bool fix_skel_root = RNA_boolean_get(op->ptr, "fix_skel_root");;
struct USDExportParams params = {RNA_int_get(op->ptr, "start"),
RNA_int_get(op->ptr, "end"),
export_animation,
@ -366,7 +368,8 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
convert_world_material,
generate_cycles_shaders,
export_armatures,
xform_op_mode};
xform_op_mode,
fix_skel_root};
/* Take some defaults from the scene, if not specified explicitly. */
Scene *scene = CTX_data_scene(C);
@ -513,6 +516,7 @@ static void wm_usd_export_draw(bContext *C, wmOperator *op)
box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Experimental:"), ICON_NONE);
uiItemR(box, ptr, "use_instancing", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "fix_skel_root", 0, NULL, ICON_NONE);
}
void WM_OT_usd_export(struct wmOperatorType *ot)
@ -648,6 +652,13 @@ void WM_OT_usd_export(struct wmOperatorType *ot)
"When checked, instanced objects are exported as references in USD. "
"When unchecked, instanced objects are exported as real objects");
RNA_def_boolean(ot->srna,
"fix_skel_root",
false,
"Fix Skel Root",
"If exporting armatures, attempt to automatically "
"correct invalid USD Skel Root hierarchies");
RNA_def_enum(ot->srna,
"evaluation_mode",
rna_enum_usd_export_evaluation_mode_items,

View File

@ -23,6 +23,7 @@
#include "usd_light_convert.h"
#include "usd_umm.h"
#include "usd_writer_material.h"
#include "usd_writer_skel_root.h"
#include <pxr/base/plug/registry.h>
#include <pxr/pxr.h>
@ -336,6 +337,10 @@ static void export_startjob(void *customdata,
iter.release_writers();
if (data->params.export_armatures) {
validate_skel_roots(usd_stage, data->params);
}
// Set Stage Default Prim Path
if (strlen(data->params.default_prim_path) > 0) {
std::string valid_default_prim_path = pxr::TfMakeValidIdentifier(

View File

@ -18,8 +18,14 @@
*/
#include "usd_writer_skel_root.h"
#include "WM_api.h"
#include <pxr/usd/usd/primRange.h>
#include <pxr/usd/usdSkel/bindingAPI.h>
#include <pxr/usd/usdSkel/root.h>
#include <iostream>
namespace blender::io::usd {
bool USDSkelRootWriter::is_under_skel_root() const
@ -34,14 +40,13 @@ bool USDSkelRootWriter::is_under_skel_root() const
pxr::UsdPrim prim = usd_export_context_.stage->GetPrimAtPath(parent_path);
while (prim.IsValid()) {
if (prim.IsA<pxr::UsdSkelRoot>()) {
return true;
}
prim = prim.GetParent();
if (!prim.IsValid()) {
return false;
}
return false;
pxr::UsdSkelRoot root = pxr::UsdSkelRoot::Find(prim);
return static_cast<bool>(root);
}
pxr::UsdGeomXformable USDSkelRootWriter::create_xformable() const
@ -67,4 +72,107 @@ pxr::UsdGeomXformable USDSkelRootWriter::create_xformable() const
return root;
}
static pxr::UsdGeomXform get_xform_ancestor(const pxr::UsdPrim &prim1,
const pxr::UsdPrim &prim2)
{
if (!prim1 || !prim2) {
return pxr::UsdGeomXform();
}
pxr::SdfPath prefix = prim1.GetPath().GetCommonPrefix(prim2.GetPath());
if (prefix.IsEmpty()) {
return pxr::UsdGeomXform();
}
pxr::UsdPrim ancestor = prim1.GetStage()->GetPrimAtPath(prefix);
if (!ancestor.IsA<pxr::UsdGeomXform>()) {
ancestor = ancestor.GetParent();
}
if (ancestor.IsA<pxr::UsdGeomXform>()) {
return pxr::UsdGeomXform(ancestor);
}
return pxr::UsdGeomXform();
}
void validate_skel_roots(pxr::UsdStageRefPtr stage, const USDExportParams &params)
{
if (!params.export_armatures || !stage) {
return;
}
bool created_skel_root = false;
pxr::UsdPrimRange it = stage->Traverse();
for (pxr::UsdPrim prim : it) {
if (prim.HasAPI<pxr::UsdSkelBindingAPI>() && !prim.IsA<pxr::UsdSkelSkeleton>()) {
pxr::UsdSkelBindingAPI skel_bind_api(prim);
if (skel_bind_api) {
pxr::UsdSkelSkeleton skel;
if (skel_bind_api.GetSkeleton(&skel)) {
if (!skel.GetPrim().IsValid()) {
std::cout << "WARNING in validate_skel_roots(): invalid skeleton for prim " << prim.GetPath() << std::endl;
continue;
}
pxr::UsdSkelRoot prim_root = pxr::UsdSkelRoot::Find(prim);
pxr::UsdSkelRoot arm_root = pxr::UsdSkelRoot::Find(skel.GetPrim());
bool common_root = false;
if (prim_root && arm_root && prim_root.GetPath() == arm_root.GetPath()) {
common_root = true;
}
if (!common_root) {
WM_reportf(RPT_WARNING, "USD Export: skinned prim %s and skeleton %s do not share a common SkelRoot and may not bind correctly. See the documentation for possible solutions.\n",
prim.GetPath().GetAsString().c_str(), skel.GetPrim().GetPath().GetAsString().c_str());
std::cout << "WARNING: skinned prim " << prim.GetPath() << " and skeleton " << skel.GetPrim().GetPath()
<< " do not share a common SkelRoot and may not bind correctly. See the documentation for possible solutions." << std::endl;
if (params.fix_skel_root) {
std::cout << "Attempting to fix the Skel Root hierarchy." << std::endl;
WM_reportf(RPT_WARNING, "Attempting to fix the Skel Root hierarchy. See the console for information");
if (pxr::UsdGeomXform xf = get_xform_ancestor(prim, skel.GetPrim())) {
/* Enable skeletal processing by setting the type to UsdSkelRoot. */
std::cout << "Converting Xform prim " << xf.GetPath() << " to a SkelRoot" << std::endl;
pxr::UsdSkelRoot::Define(stage, xf.GetPath());
created_skel_root = true;
}
else {
std::cout << "Couldn't find a commone Xform ancestor for skinned prim " << prim.GetPath()
<< " and skeleton " << skel.GetPrim().GetPath() << " to convert to a USDSkelRoot\n";
std::cout << "You might wish to group these objects under an Empty in the Blender scene.\n";
}
}
}
}
}
}
}
if (!created_skel_root) {
return;
}
it = stage->Traverse();
for (pxr::UsdPrim prim : it) {
if (prim.IsA<pxr::UsdSkelRoot>()) {
if (pxr::UsdSkelRoot root = pxr::UsdSkelRoot::Find(prim.GetParent())) {
/* This is a nested SkelRoot, so convert it to an Xform. */
std::cout << "Converting nested SkelRoot " << prim.GetPath() << " to an Xform." << std::endl;
pxr::UsdGeomXform::Define(stage, prim.GetPath());
}
}
}
}
} // namespace blender::io::usd

View File

@ -24,6 +24,8 @@
namespace blender::io::usd {
void validate_skel_roots(pxr::UsdStageRefPtr stage, const USDExportParams &params);
class USDSkelRootWriter : public USDTransformWriter {
public:

View File

@ -132,6 +132,7 @@ struct USDExportParams {
bool generate_cycles_shaders;
bool export_armatures;
eUSDXformOpMode xform_op_mode;
bool fix_skel_root;
};
struct USDImportParams {