Fix T103984: USD exports pass usdchecker

These changes were authored by Michael B Johnson (drwave).

The default Blender USD export currently produces files that trigger
errors in the usdchecker that ships with USD 22.11.

The changes are:

- Set the defaultPrim if no defaultPrim is set. This sets it to the
first prim in the hierarchy which matches the behaviour of Pixar's
referencing (where referencing a USD layer without a defaultPrim will
pick the first prim) as well as matches the logic in Pixar's Maya USD
exporter code.

- Applies the MaterialBindingAPI to prims with material binding
attributes. This is a relatively new requirement for USD as it will
help for efficiency with upcoming changes to Hydra.

- Removes the preview scope in the USD shader hierarchy, because it
is no longer valid for shaders to have any non-container ancestors in
their hierarchy up until the enclosing Material prim.

Reviewed by: Michael Kowalski

Differential Revision: https://developer.blender.org/D17041
This commit is contained in:
Michael Kowalski 2023-01-27 10:29:58 -05:00
parent 4635dd6aed
commit b67b84bd5d
Notes: blender-bot 2023-02-14 06:42:53 +01:00
Referenced by issue #103984, fixes to have exported USD pass usdchecker
4 changed files with 31 additions and 13 deletions

View File

@ -7,6 +7,8 @@
#include <pxr/base/plug/registry.h>
#include <pxr/pxr.h>
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/primRange.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usdGeom/tokens.h>
@ -138,6 +140,17 @@ static void export_startjob(void *customdata,
}
iter.release_writers();
/* Set the default prim if it doesn't exist */
if (!usd_stage->GetDefaultPrim()) {
/* Use TraverseAll since it's guaranteed to be depth first and will get the first top level
* prim, and is less verbose than getting the PseudoRoot + iterating its children.*/
for (auto prim : usd_stage->TraverseAll()) {
usd_stage->SetDefaultPrim(prim);
break;
}
}
usd_stage->GetRootLayer()->Save();
/* Finish up by going back to the keyframe that was current before we started. */

View File

@ -60,7 +60,6 @@ static const pxr::TfToken out("out", pxr::TfToken::Immortal);
static const pxr::TfToken normal("normal", pxr::TfToken::Immortal);
static const pxr::TfToken ior("ior", pxr::TfToken::Immortal);
static const pxr::TfToken file("file", pxr::TfToken::Immortal);
static const pxr::TfToken preview("preview", pxr::TfToken::Immortal);
static const pxr::TfToken raw("raw", pxr::TfToken::Immortal);
static const pxr::TfToken sRGB("sRGB", pxr::TfToken::Immortal);
static const pxr::TfToken sourceColorSpace("sourceColorSpace", pxr::TfToken::Immortal);
@ -124,10 +123,6 @@ void create_usd_preview_surface_material(const USDExporterContext &usd_export_co
return;
}
/* Define a 'preview' scope beneath the material which will contain the preview shaders. */
pxr::UsdGeomScope::Define(usd_export_context.stage,
usd_material.GetPath().AppendChild(usdtokens::preview));
/* Default map when creating UV primvar reader shaders. */
pxr::TfToken default_uv_sampler = default_uv.empty() ? cyclestokens::UVMap :
pxr::TfToken(default_uv);
@ -470,9 +465,8 @@ static pxr::UsdShadeShader create_usd_preview_shader(const USDExporterContext &u
const char *name,
const int type)
{
pxr::SdfPath shader_path = material.GetPath()
.AppendChild(usdtokens::preview)
.AppendChild(pxr::TfToken(pxr::TfMakeValidIdentifier(name)));
pxr::SdfPath shader_path = material.GetPath().AppendChild(
pxr::TfToken(pxr::TfMakeValidIdentifier(name)));
pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(usd_export_context.stage, shader_path);
switch (type) {

View File

@ -350,7 +350,8 @@ void USDGenericMeshWriter::assign_materials(const HierarchyContext &context,
* which is why we always bind the first material to the entire mesh. See
* https://github.com/PixarAnimationStudios/USD/issues/542 for more info. */
bool mesh_material_bound = false;
pxr::UsdShadeMaterialBindingAPI material_binding_api(usd_mesh.GetPrim());
auto mesh_prim = usd_mesh.GetPrim();
pxr::UsdShadeMaterialBindingAPI material_binding_api(mesh_prim);
for (int mat_num = 0; mat_num < context.object->totcol; mat_num++) {
Material *material = BKE_object_material_get(context.object, mat_num + 1);
if (material == nullptr) {
@ -369,7 +370,13 @@ void USDGenericMeshWriter::assign_materials(const HierarchyContext &context,
break;
}
if (!mesh_material_bound) {
if (mesh_material_bound) {
/* USD will require that prims with material bindings have the MaterialBindingAPI applied
* schema. While Bind() above will create the binding attribute, Apply() needs to be called as
* well to add the MaterialBindingAPI schema to the prim itself.*/
material_binding_api.Apply(mesh_prim);
}
else {
/* Blender defaults to double-sided, but USD to single-sided. */
usd_mesh.CreateDoubleSidedAttr(pxr::VtValue(true));
}
@ -396,7 +403,11 @@ void USDGenericMeshWriter::assign_materials(const HierarchyContext &context,
pxr::UsdGeomSubset usd_face_subset = material_binding_api.CreateMaterialBindSubset(
material_name, face_indices);
pxr::UsdShadeMaterialBindingAPI(usd_face_subset.GetPrim()).Bind(usd_material);
auto subset_prim = usd_face_subset.GetPrim();
auto subset_material_api = pxr::UsdShadeMaterialBindingAPI(subset_prim);
subset_material_api.Bind(usd_material);
/* Apply the MaterialBindingAPI applied schema, as required by USD.*/
subset_material_api.Apply(subset_prim);
}
}

View File

@ -294,7 +294,7 @@ TEST_F(UsdExportTest, usd_export_material)
const std::string prim_name = pxr::TfMakeValidIdentifier(bsdf_node->name);
const pxr::UsdPrim bsdf_prim = stage->GetPrimAtPath(
pxr::SdfPath("/_materials/Material/preview/" + prim_name));
pxr::SdfPath("/_materials/Material/" + prim_name));
compare_blender_node_to_usd_prim(bsdf_node, bsdf_prim);
@ -305,7 +305,7 @@ TEST_F(UsdExportTest, usd_export_material)
const std::string image_prim_name = pxr::TfMakeValidIdentifier(image_node->name);
const pxr::UsdPrim image_prim = stage->GetPrimAtPath(
pxr::SdfPath("/_materials/Material/preview/" + image_prim_name));
pxr::SdfPath("/_materials/Material/" + image_prim_name));
ASSERT_TRUE(bool(image_prim)) << "Unable to find Material prim from exported stage "
<< output_filename;