From 8f45639df8cc02cec81f25307e2972d418cbf8fe Mon Sep 17 00:00:00 2001 From: Jerry Gamache Date: Tue, 21 Dec 2021 17:00:19 -0500 Subject: [PATCH 1/3] MAYA-114979 MAYA-114731 Fix correctness issues This fixes #1834 for the last correctness problems mentioned - Use a separate varnameStr for MaterialX since the type is not compatible with the varname used in UsdPreviewSurface - Shared place2dTexture nodes are now shared on MaterialX export - NodeGraph is used instead of Scope to isolate shaders by type in multi-material export - NodeGraph boundaries are not skipped anymore by shader connections - Cleaned-up code producing empty NodeGraphs --- .../shading/shadingModeExporterContext.cpp | 35 +++-- .../fileio/shading/shadingModeUseRegistry.cpp | 12 ++ .../fileio/translators/translatorMaterial.cpp | 12 +- lib/mayaUsd/fileio/utils/shadingUtil.cpp | 13 +- .../shading/mtlxFileTextureWriter.cpp | 123 ++++++++++----- .../shading/mtlxPreviewSurfaceWriter.cpp | 17 ++- .../shading/mtlxStandardSurfaceWriter.cpp | 17 ++- lib/usd/translators/shading/shadingTokens.h | 1 + .../shading/usdFileTextureWriter.cpp | 23 ++- .../UsdImportMaterialX.usda | 12 +- .../UsdImportMaterialX.usda | 12 +- .../usd/translators/testUsdExportMaterialX.py | 9 +- .../translators/testUsdExportMultiMaterial.py | 140 +++++++++++++++++- 13 files changed, 333 insertions(+), 93 deletions(-) diff --git a/lib/mayaUsd/fileio/shading/shadingModeExporterContext.cpp b/lib/mayaUsd/fileio/shading/shadingModeExporterContext.cpp index 032e7900ec..7208580806 100644 --- a/lib/mayaUsd/fileio/shading/shadingModeExporterContext.cpp +++ b/lib/mayaUsd/fileio/shading/shadingModeExporterContext.cpp @@ -68,6 +68,7 @@ TF_DEFINE_PRIVATE_TOKENS( (volumeShader) (displacementShader) (varname) + (varnameStr) (map1) ); // clang-format on @@ -480,8 +481,8 @@ UsdPrim UsdMayaShadingModeExportContext::MakeStandardMaterialPrim( } namespace { -/// We can have multiple mesh with differing UV channel names and we need to make sure the -/// exported material has varname inputs that match the texcoords exported by the shape +/// We can have multiple mesh with differing UV channel names and we need to make sure the exported +/// material has varname or varnameStr inputs that match the texcoords exported by the shape class _UVMappingManager { public: @@ -496,6 +497,7 @@ class _UVMappingManager // shader nodes contained in the material that have UV inputs that requires mapping: // // token inputs:node_with_uv_input:varname = "st" + // string inputs:node_with_uv_input:varnameStr = "st" // // The "node_with_uv_input" is a dependency node which is a valid target for the Maya // "uvLink" command, which describes UV linkage for all shapes in the scene that reference @@ -506,7 +508,8 @@ class _UVMappingManager for (const UsdShadeInput& input : material.GetInputs()) { const UsdAttribute& usdAttr = input.GetAttr(); std::vector splitName = usdAttr.SplitName(); - if (splitName.back() != _tokens->varname.GetString()) { + if (splitName.back() != _tokens->varname.GetString() + && splitName.back() != _tokens->varnameStr.GetString()) { continue; } @@ -602,13 +605,17 @@ class _UVMappingManager TfTokenVector::const_iterator itNode = _nodesWithUVInput.cbegin(); TfTokenVector::const_iterator itName = largestSet.cbegin(); for (; itNode != _nodesWithUVInput.cend(); ++itNode, ++itName) { - TfToken inputName( - TfStringPrintf("%s:%s", itNode->GetText(), _tokens->varname.GetText())); - UsdShadeInput materialInput = material.GetInput(inputName); TF_VERIFY(itName != largestSet.cend()); - if (materialInput.GetTypeName() == SdfValueTypeNames->Token) { + std::string inputName( + TfStringPrintf("%s:%s", itNode->GetText(), _tokens->varname.GetText())); + UsdShadeInput materialInput = material.GetInput(TfToken(inputName.c_str())); + if (materialInput) { materialInput.Set(*itName); - } else if (materialInput.GetTypeName() == SdfValueTypeNames->String) { + } + inputName + = TfStringPrintf("%s:%s", itNode->GetText(), _tokens->varnameStr.GetText()); + materialInput = material.GetInput(TfToken(inputName.c_str())); + if (materialInput) { materialInput.Set((*itName).GetString()); } } @@ -649,13 +656,15 @@ class _UVMappingManager TfTokenVector::const_iterator itNode = _nodesWithUVInput.cbegin(); TfTokenVector::const_iterator itName = uvNames.cbegin(); for (; itNode != _nodesWithUVInput.cend(); ++itNode, ++itName) { - TfToken inputName( + std::string inputName( TfStringPrintf("%s:%s", itNode->GetText(), _tokens->varname.GetText())); - UsdShadeInput materialInput - = newMaterial.CreateInput(inputName, SdfValueTypeNames->Token); - if (materialInput.GetTypeName() == SdfValueTypeNames->Token) { + UsdShadeInput materialInput = newMaterial.GetInput(TfToken(inputName.c_str())); + if (materialInput) { materialInput.Set(*itName); - } else if (materialInput.GetTypeName() == SdfValueTypeNames->String) { + } + inputName = TfStringPrintf("%s:%s", itNode->GetText(), _tokens->varnameStr.GetText()); + materialInput = newMaterial.GetInput(TfToken(inputName.c_str())); + if (materialInput) { materialInput.Set((*itName).GetString()); } } diff --git a/lib/mayaUsd/fileio/shading/shadingModeUseRegistry.cpp b/lib/mayaUsd/fileio/shading/shadingModeUseRegistry.cpp index 67f872933a..24afdb3530 100644 --- a/lib/mayaUsd/fileio/shading/shadingModeUseRegistry.cpp +++ b/lib/mayaUsd/fileio/shading/shadingModeUseRegistry.cpp @@ -348,6 +348,9 @@ class UseRegistryShadingModeExporter : public UsdMayaShadingModeExporter if (context.GetExportArgs().allMaterialConversions.size() > 1) { // Write each material in its own scope materialExportPath = materialExportPath.AppendChild(currentMaterialConversion); + + // This path needs to be a NodeGraph: + UsdShadeNodeGraph::Define(context.GetUsdStage(), materialExportPath); } UsdShadeShader surfaceShaderSchema = _ExportShadingDepGraph( @@ -364,6 +367,15 @@ class UseRegistryShadingModeExporter : public UsdMayaShadingModeExporter materialExportPath, context.GetDisplacementShaderPlug(), context); UsdMayaShadingUtil::CreateShaderOutputAndConnectMaterial( displacementShaderSchema, material, UsdShadeTokens->displacement, renderContext); + + // Clean-up nodegraph if nothing was exported: + if (context.GetExportArgs().allMaterialConversions.size() > 1) { + UsdPrim nodeGraphPrim(context.GetUsdStage()->GetPrimAtPath(materialExportPath)); + + if (nodeGraphPrim.GetAllChildren().empty()) { + context.GetUsdStage()->RemovePrim(materialExportPath); + } + } } context.BindStandardMaterialPrim(materialPrim, assignments, boundPrimPaths); } diff --git a/lib/mayaUsd/fileio/translators/translatorMaterial.cpp b/lib/mayaUsd/fileio/translators/translatorMaterial.cpp index 1489804ef1..a9752af5bc 100644 --- a/lib/mayaUsd/fileio/translators/translatorMaterial.cpp +++ b/lib/mayaUsd/fileio/translators/translatorMaterial.cpp @@ -63,6 +63,7 @@ TF_DEFINE_PRIVATE_TOKENS( (inputs) (varname) + (varnameStr) ); // clang-format on @@ -101,14 +102,15 @@ bool _IsMergeableMaterial(const UsdShadeMaterial& shadeMaterial) return false; } - // Check that the only properties authored are varname inputs. + // Check that the only properties authored are varname and varnameStr inputs. for (const SdfPropertySpecHandle& propSpec : primSpec->GetProperties()) { const SdfPath propPath = propSpec->GetPath(); const std::vector splitName = SdfPath::TokenizeIdentifier(propPath.GetName()); - // We allow only ["inputs", "", "varname"] + // We allow only ["inputs", "", "varname" or "varnameStr"] if (splitName.size() != 3u || splitName[0u] != _tokens->inputs.GetString() - || splitName[2u] != _tokens->varname.GetString()) { + || (splitName[2u] != _tokens->varname.GetString() + && splitName[2u] != _tokens->varnameStr.GetString())) { return false; } } @@ -178,7 +180,9 @@ _GetUVBindingsFromMaterial(const UsdShadeMaterial& material, UsdMayaPrimReaderCo for (const UsdShadeInput& input : material.GetInputs()) { const UsdAttribute& usdAttr = input.GetAttr(); std::vector splitName = usdAttr.SplitName(); - if (splitName.size() != 3 || splitName[2] != _tokens->varname.GetString()) { + if (splitName.size() != 3 + || (splitName[2] != _tokens->varname.GetString() + && splitName[2] != _tokens->varnameStr.GetString())) { continue; } VtValue val; diff --git a/lib/mayaUsd/fileio/utils/shadingUtil.cpp b/lib/mayaUsd/fileio/utils/shadingUtil.cpp index 3c52911ce4..998f98e5d4 100644 --- a/lib/mayaUsd/fileio/utils/shadingUtil.cpp +++ b/lib/mayaUsd/fileio/utils/shadingUtil.cpp @@ -149,7 +149,18 @@ UsdShadeOutput UsdMayaShadingUtil::CreateShaderOutputAndConnectMaterial( UsdShadeOutput shaderOutput = shader.CreateOutput(terminalName, materialOutput.GetTypeName()); - materialOutput.ConnectToSource(shaderOutput); + UsdPrim parentPrim = shader.GetPrim().GetParent(); + if (parentPrim == material.GetPrim()) { + materialOutput.ConnectToSource(shaderOutput); + } else { + // If the surface is inside a multi-material node graph, then we must create an intermediate + // output on the NodeGraph + UsdShadeNodeGraph parentNodeGraph(parentPrim); + UsdShadeOutput parentOutput + = parentNodeGraph.CreateOutput(terminalName, materialOutput.GetTypeName()); + parentOutput.ConnectToSource(shaderOutput); + materialOutput.ConnectToSource(parentOutput); + } return shaderOutput; } diff --git a/lib/usd/translators/shading/mtlxFileTextureWriter.cpp b/lib/usd/translators/shading/mtlxFileTextureWriter.cpp index 9bd74eeb91..8ff5d93388 100644 --- a/lib/usd/translators/shading/mtlxFileTextureWriter.cpp +++ b/lib/usd/translators/shading/mtlxFileTextureWriter.cpp @@ -71,6 +71,8 @@ class MtlxUsd_FileWriter : public MtlxUsd_BaseWriter const SdfValueTypeName& typeName) override; private: + SdfPath _GetPlace2DTexturePath(const MFnDependencyNode& depNodeFn); + int _numChannels = 4; }; @@ -80,8 +82,8 @@ PXRUSDMAYA_REGISTER_SHADER_WRITER(file, MtlxUsd_FileWriter); TF_DEFINE_PRIVATE_TOKENS( _tokens, - // Prefix for helper nodes: - ((PrimvarReaderPrefix, "MayaGeomPropValue")) + // Shared primvar writer (when no place2dTexture found): + ((PrimvarReaderShaderName, "shared_MayaGeomPropValue")) ); // clang-format on @@ -148,45 +150,68 @@ MtlxUsd_FileWriter::MtlxUsd_FileWriter( } // Now create a geompropvalue reader that the image shader will use. - TfToken primvarReaderName( - TfStringPrintf("%s_%s", _tokens->PrimvarReaderPrefix.GetText(), depNodeFn.name().asChar())); - const SdfPath primvarReaderPath = nodegraphPath.AppendChild(primvarReaderName); - UsdShadeShader primvarReaderSchema = UsdShadeShader::Define(GetUsdStage(), primvarReaderPath); - - primvarReaderSchema.CreateIdAttr(VtValue(TrMtlxTokens->ND_geompropvalue_vector2)); - - UsdShadeInput varnameInput - = primvarReaderSchema.CreateInput(TrMtlxTokens->geomprop, SdfValueTypeNames->String); - - // We expose the primvar reader varname attribute to the material to allow - // easy specialization based on UV mappings to geometries: - SdfPath materialPath = GetUsdPath().GetParentPath(); - UsdShadeMaterial materialSchema(GetUsdStage()->GetPrimAtPath(materialPath)); - while (!materialSchema && !materialPath.IsEmpty()) { - materialPath = materialPath.GetParentPath(); - materialSchema = UsdShadeMaterial(GetUsdStage()->GetPrimAtPath(materialPath)); - } + SdfPath primvarReaderPath = _GetPlace2DTexturePath(depNodeFn); + + if (!GetUsdStage()->GetPrimAtPath(primvarReaderPath)) { + UsdShadeShader primvarReaderSchema + = UsdShadeShader::Define(GetUsdStage(), primvarReaderPath); + primvarReaderSchema.CreateIdAttr(VtValue(TrMtlxTokens->ND_geompropvalue_vector2)); + UsdShadeInput varnameInput + = primvarReaderSchema.CreateInput(TrMtlxTokens->geomprop, SdfValueTypeNames->String); + + TfToken inputName(TfStringPrintf( + "%s:%s", depNodeFn.name().asChar(), TrMtlxTokens->varnameStr.GetText())); + + // We expose the primvar reader varnameStr attribute to the material to allow + // easy specialization based on UV mappings to geometries: + UsdPrim materialPrim = primvarReaderSchema.GetPrim().GetParent(); + UsdShadeMaterial materialSchema(materialPrim); + while (!materialSchema && materialPrim) { + UsdShadeNodeGraph intermediateNodeGraph(materialPrim); + if (intermediateNodeGraph) { + UsdShadeInput intermediateInput + = intermediateNodeGraph.CreateInput(inputName, SdfValueTypeNames->String); + varnameInput.ConnectToSource(intermediateInput); + varnameInput = intermediateInput; + } - if (materialSchema) { - TfToken inputName( - TfStringPrintf("%s:%s", depNodeFn.name().asChar(), TrUsdTokens->varname.GetText())); - UsdShadeInput materialInput - = materialSchema.CreateInput(inputName, SdfValueTypeNames->String); - materialInput.Set(UsdUtilsGetPrimaryUVSetName().GetString()); - varnameInput.ConnectToSource(materialInput); - } else { - varnameInput.Set(UsdUtilsGetPrimaryUVSetName()); - } + materialPrim = materialPrim.GetParent(); + materialSchema = UsdShadeMaterial(materialPrim); + } - UsdShadeOutput primvarReaderOutput - = primvarReaderSchema.CreateOutput(TrMtlxTokens->out, SdfValueTypeNames->Float2); + if (materialSchema) { + UsdShadeInput materialInput + = materialSchema.CreateInput(inputName, SdfValueTypeNames->String); + materialInput.Set(UsdUtilsGetPrimaryUVSetName().GetString()); + varnameInput.ConnectToSource(materialInput); + } else { + varnameInput.Set(UsdUtilsGetPrimaryUVSetName()); + } + + UsdShadeOutput primvarReaderOutput + = primvarReaderSchema.CreateOutput(TrMtlxTokens->out, SdfValueTypeNames->Float2); - // TODO: Handle UV SRT with a ND_place2d_vector2 node. + // TODO: Handle UV SRT with a ND_place2d_vector2 node. Make sure the name derives from the + // place2dTexture node if there is one (see usdFileTextureWriter for details) - // Connect the output of the primvar reader to the texture coordinate - // input of the UV texture. - texSchema.CreateInput(TrMtlxTokens->texcoord, SdfValueTypeNames->Float2) - .ConnectToSource(primvarReaderOutput); + // Connect the output of the primvar reader to the texture coordinate + // input of the UV texture. + texSchema.CreateInput(TrMtlxTokens->texcoord, SdfValueTypeNames->Float2) + .ConnectToSource(primvarReaderOutput); + } else { + // Re-using an existing primvar reader: + UsdShadeShader primvarReaderShaderSchema(GetUsdStage()->GetPrimAtPath(primvarReaderPath)); + UsdShadeOutput primvarReaderOutput + = primvarReaderShaderSchema.GetOutput(TrMtlxTokens->out); + + // TODO: Handle UV SRT with a ND_place2d_vector2 node. Make sure the name derives from the + // place2dTexture node if there is one (see usdFileTextureWriter for details) + + // Connect the output of the primvar reader to the texture coordinate + // input of the UV texture. + texSchema.CreateInput(TrMtlxTokens->texcoord, SdfValueTypeNames->Float2) + .ConnectToSource(primvarReaderOutput); + } } /* virtual */ @@ -422,4 +447,28 @@ UsdAttribute MtlxUsd_FileWriter::GetShadingAttributeForMayaAttrName( return UsdAttribute(); } +SdfPath MtlxUsd_FileWriter::_GetPlace2DTexturePath(const MFnDependencyNode& depNodeFn) +{ + MStatus status; + std::string usdUvTextureName; + const MPlug plug = depNodeFn.findPlug( + TrMayaTokens->uvCoord.GetText(), + /* wantNetworkedPlug = */ true, + &status); + if (status == MS::kSuccess && plug.isDestination(&status)) { + MPlug source = plug.source(&status); + if (status == MS::kSuccess && !source.isNull()) { + MFnDependencyNode sourceNode(source.node()); + usdUvTextureName = sourceNode.name().asChar(); + } + } + + if (usdUvTextureName.empty()) { + // We want a single UV reader for all file nodes not connected to a place2DTexture node + usdUvTextureName = _tokens->PrimvarReaderShaderName.GetString(); + } + + return GetNodeGraph().GetPath().AppendChild(TfToken(usdUvTextureName.c_str())); +} + PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/usd/translators/shading/mtlxPreviewSurfaceWriter.cpp b/lib/usd/translators/shading/mtlxPreviewSurfaceWriter.cpp index b2acdfc0aa..24b65a0ed3 100644 --- a/lib/usd/translators/shading/mtlxPreviewSurfaceWriter.cpp +++ b/lib/usd/translators/shading/mtlxPreviewSurfaceWriter.cpp @@ -88,13 +88,7 @@ MtlxUsd_PreviewSurfaceWriter::MtlxUsd_PreviewSurfaceWriter( shaderSchema.CreateIdAttr(VtValue(TrMtlxTokens->ND_UsdPreviewSurface_surfaceshader)); - UsdShadeNodeGraph nodegraphSchema(GetNodeGraph()); - if (!TF_VERIFY( - nodegraphSchema, - "Could not define UsdShadeNodeGraph at path '%s'\n", - GetUsdPath().GetText())) { - return; - } + UsdShadeNodeGraph nodegraphSchema; for (const TfToken& mayaAttrName : PxrMayaUsdPreviewSurfaceTokens->allTokens) { @@ -135,6 +129,15 @@ MtlxUsd_PreviewSurfaceWriter::MtlxUsd_PreviewSurfaceWriter( // All connections go directly to the node graph: if (attrPlug.isConnected()) { + if (!nodegraphSchema) { + nodegraphSchema = UsdShadeNodeGraph(GetNodeGraph()); + if (!TF_VERIFY( + nodegraphSchema, + "Could not define UsdShadeNodeGraph at path '%s'\n", + GetUsdPath().GetText())) { + return; + } + } UsdShadeOutput ngOutput = nodegraphSchema.CreateOutput(mayaAttrName, valueTypeName); input.ConnectToSource(ngOutput); } diff --git a/lib/usd/translators/shading/mtlxStandardSurfaceWriter.cpp b/lib/usd/translators/shading/mtlxStandardSurfaceWriter.cpp index ae3ce97485..5ecde7f89a 100644 --- a/lib/usd/translators/shading/mtlxStandardSurfaceWriter.cpp +++ b/lib/usd/translators/shading/mtlxStandardSurfaceWriter.cpp @@ -135,13 +135,7 @@ MaterialXTranslators_StandardSurfaceWriter::MaterialXTranslators_StandardSurface shaderSchema.CreateIdAttr(VtValue(TrMtlxTokens->ND_standard_surface_surfaceshader)); - UsdShadeNodeGraph nodegraphSchema(GetNodeGraph()); - if (!TF_VERIFY( - nodegraphSchema, - "Could not define UsdShadeNodeGraph at path '%s'\n", - GetUsdPath().GetText())) { - return; - } + UsdShadeNodeGraph nodegraphSchema; for (unsigned int i = 0u; i < depNodeFn.attributeCount(); ++i) { const MObject attrObj = depNodeFn.reorderedAttribute(i); @@ -188,6 +182,15 @@ MaterialXTranslators_StandardSurfaceWriter::MaterialXTranslators_StandardSurface // All connections go directly to the node graph: if (attrPlug.isConnected()) { + if (!nodegraphSchema) { + nodegraphSchema = UsdShadeNodeGraph(GetNodeGraph()); + if (!TF_VERIFY( + nodegraphSchema, + "Could not define UsdShadeNodeGraph at path '%s'\n", + GetUsdPath().GetText())) { + return; + } + } UsdShadeOutput ngOutput = nodegraphSchema.CreateOutput(mayaAttrName, valueTypeName); input.ConnectToSource(ngOutput); } diff --git a/lib/usd/translators/shading/shadingTokens.h b/lib/usd/translators/shading/shadingTokens.h index f0a16f352c..ebd83e27a1 100644 --- a/lib/usd/translators/shading/shadingTokens.h +++ b/lib/usd/translators/shading/shadingTokens.h @@ -262,6 +262,7 @@ TF_DECLARE_PUBLIC_TOKENS( (cubic) #define TR_MTLX_ATTRIBUTES \ + (varnameStr) \ (geomprop) \ (channels) \ (in) \ diff --git a/lib/usd/translators/shading/usdFileTextureWriter.cpp b/lib/usd/translators/shading/usdFileTextureWriter.cpp index 8623c8e79c..4e3334b31f 100644 --- a/lib/usd/translators/shading/usdFileTextureWriter.cpp +++ b/lib/usd/translators/shading/usdFileTextureWriter.cpp @@ -137,18 +137,27 @@ PxrUsdTranslators_FileTextureWriter::PxrUsdTranslators_FileTextureWriter( UsdShadeInput varnameInput = primvarReaderShaderSchema.CreateInput(TrUsdTokens->varname, SdfValueTypeNames->Token); + TfToken inputName( + TfStringPrintf("%s:%s", depNodeFn.name().asChar(), TrUsdTokens->varname.GetText())); + // We expose the primvar reader varname attribute to the material to allow // easy specialization based on UV mappings to geometries: - SdfPath materialPath = GetUsdPath().GetParentPath(); - UsdShadeMaterial materialSchema(GetUsdStage()->GetPrimAtPath(materialPath)); - while (!materialSchema && !materialPath.IsEmpty()) { - materialPath = materialPath.GetParentPath(); - materialSchema = UsdShadeMaterial(GetUsdStage()->GetPrimAtPath(materialPath)); + UsdPrim materialPrim = primvarReaderShaderSchema.GetPrim().GetParent(); + UsdShadeMaterial materialSchema(materialPrim); + while (!materialSchema && materialPrim) { + UsdShadeNodeGraph intermediateNodeGraph(materialPrim); + if (intermediateNodeGraph) { + UsdShadeInput intermediateInput + = intermediateNodeGraph.CreateInput(inputName, SdfValueTypeNames->Token); + varnameInput.ConnectToSource(intermediateInput); + varnameInput = intermediateInput; + } + + materialPrim = materialPrim.GetParent(); + materialSchema = UsdShadeMaterial(materialPrim); } if (materialSchema) { - TfToken inputName( - TfStringPrintf("%s:%s", depNodeFn.name().asChar(), TrUsdTokens->varname.GetText())); UsdShadeInput materialInput = materialSchema.CreateInput(inputName, SdfValueTypeNames->Token); materialInput.Set(UsdUtilsGetPrimaryUVSetName()); diff --git a/test/lib/mayaUsd/fileio/UsdImportMaterialX/UsdImportMaterialX.usda b/test/lib/mayaUsd/fileio/UsdImportMaterialX/UsdImportMaterialX.usda index 92e04ce014..a09753cb9c 100644 --- a/test/lib/mayaUsd/fileio/UsdImportMaterialX/UsdImportMaterialX.usda +++ b/test/lib/mayaUsd/fileio/UsdImportMaterialX/UsdImportMaterialX.usda @@ -52,9 +52,9 @@ def Scope "Looks" { def Material "standardSurface2SG" { - string inputs:file1:varname = "map1" - string inputs:file2:varname = "map1" - string inputs:file3:varname = "map1" + string inputs:file1:varnameStr = "map1" + string inputs:file2:varnameStr = "map1" + string inputs:file3:varnameStr = "map1" token outputs:mtlx:surface.connect = def Shader "standardSurface2" @@ -92,7 +92,7 @@ def Scope "Looks" def Shader "MayaGeomPropValue_file1" { uniform token info:id = "ND_geompropvalue_vector2" - string inputs:geomprop.connect = + string inputs:geomprop.connect = float2 outputs:out } @@ -112,7 +112,7 @@ def Scope "Looks" def Shader "MayaGeomPropValue_file2" { uniform token info:id = "ND_geompropvalue_vector2" - string inputs:geomprop.connect = + string inputs:geomprop.connect = float2 outputs:out } @@ -148,7 +148,7 @@ def Scope "Looks" def Shader "MayaGeomPropValue_file3" { uniform token info:id = "ND_geompropvalue_vector2" - string inputs:geomprop.connect = + string inputs:geomprop.connect = float2 outputs:out } diff --git a/test/lib/usd/translators/UsdImportMaterialX/UsdImportMaterialX.usda b/test/lib/usd/translators/UsdImportMaterialX/UsdImportMaterialX.usda index 92e04ce014..a09753cb9c 100644 --- a/test/lib/usd/translators/UsdImportMaterialX/UsdImportMaterialX.usda +++ b/test/lib/usd/translators/UsdImportMaterialX/UsdImportMaterialX.usda @@ -52,9 +52,9 @@ def Scope "Looks" { def Material "standardSurface2SG" { - string inputs:file1:varname = "map1" - string inputs:file2:varname = "map1" - string inputs:file3:varname = "map1" + string inputs:file1:varnameStr = "map1" + string inputs:file2:varnameStr = "map1" + string inputs:file3:varnameStr = "map1" token outputs:mtlx:surface.connect = def Shader "standardSurface2" @@ -92,7 +92,7 @@ def Scope "Looks" def Shader "MayaGeomPropValue_file1" { uniform token info:id = "ND_geompropvalue_vector2" - string inputs:geomprop.connect = + string inputs:geomprop.connect = float2 outputs:out } @@ -112,7 +112,7 @@ def Scope "Looks" def Shader "MayaGeomPropValue_file2" { uniform token info:id = "ND_geompropvalue_vector2" - string inputs:geomprop.connect = + string inputs:geomprop.connect = float2 outputs:out } @@ -148,7 +148,7 @@ def Scope "Looks" def Shader "MayaGeomPropValue_file3" { uniform token info:id = "ND_geompropvalue_vector2" - string inputs:geomprop.connect = + string inputs:geomprop.connect = float2 outputs:out } diff --git a/test/lib/usd/translators/testUsdExportMaterialX.py b/test/lib/usd/translators/testUsdExportMaterialX.py index 59bd71e20f..e896b2001d 100644 --- a/test/lib/usd/translators/testUsdExportMaterialX.py +++ b/test/lib/usd/translators/testUsdExportMaterialX.py @@ -84,8 +84,8 @@ def testExportTexturedMaterialXpPlane1(self): material_path = mat.GetPath().pathString self.assertEqual(material_path, base_path) - # Needs a resolved inputs:file1:varname attribute: - self.assertEqual(mat.GetInput("file1:varname").GetAttr().Get(), "st") + # Needs a resolved inputs:file1:varnameStr attribute: + self.assertEqual(mat.GetInput("file1:varnameStr").GetAttr().Get(), "st") # Needs a MaterialX surface source: shader = mat.ComputeSurfaceSource("mtlx")[0] @@ -132,7 +132,7 @@ def testExportTexturedMaterialXpPlane1(self): shader = UsdShade.Shader(cnxTuple[0]) self.assertEqual(shader.GetIdAttr().Get(), "ND_geompropvalue_vector2") self.assertEqual(shader.GetPath(), - ng_path + "/MayaGeomPropValue_file1") + ng_path + "/place2dTexture1") def testExportTexturedMaterialXNodeTypes(self): ''' @@ -146,7 +146,7 @@ def testExportTexturedMaterialXNodeTypes(self): (1, 2, "file1", "ND_image_color3"), (4, 5, "file4", "ND_image_color4"), - (1, 2, "MayaGeomPropValue_file1", "ND_geompropvalue_vector2"), + (1, 2, "place2dTexture1", "ND_geompropvalue_vector2"), (4, 5, "MayaSwizzle_file4_rgb", "ND_swizzle_color4_color3"), (6, 7, "MayaSwizzle_file6_xxx", "ND_swizzle_vector2_color3"), @@ -173,5 +173,6 @@ def testExportTexturedMaterialXNodeTypes(self): self.assertTrue(shader, prim_path) self.assertEqual(shader.GetIdAttr().Get(), id_attr, id_attr) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/test/lib/usd/translators/testUsdExportMultiMaterial.py b/test/lib/usd/translators/testUsdExportMultiMaterial.py index 1c11a69a3a..7a4280ef52 100644 --- a/test/lib/usd/translators/testUsdExportMultiMaterial.py +++ b/test/lib/usd/translators/testUsdExportMultiMaterial.py @@ -29,7 +29,18 @@ import fixturesUtils -class testUsdExportMaterialX(unittest.TestCase): +def connectUVNode(uv_node, file_node): + for att_name in (".coverage", ".translateFrame", ".rotateFrame", + ".mirrorU", ".mirrorV", ".stagger", ".wrapU", + ".wrapV", ".repeatUV", ".offset", ".rotateUV", + ".noiseUV", ".vertexUvOne", ".vertexUvTwo", + ".vertexUvThree", ".vertexCameraOne"): + cmds.connectAttr(uv_node + att_name, file_node + att_name, f=True) + cmds.connectAttr(uv_node + ".outUV", file_node + ".uvCoord", f=True) + cmds.connectAttr(uv_node + ".outUvFilterSize", file_node + ".uvFilterSize", f=True) + + +class testUsdExportMultiMaterial(unittest.TestCase): @classmethod def setUpClass(cls): @@ -85,5 +96,132 @@ def testExportedUsdShadeNodeTypes(self): self.assertTrue(shader, prim_path) self.assertEqual(shader.GetIdAttr().Get(), id_attr) + def testVarnameMerging(self): + """ + Test that we produce a minimal number of UV readers and varname/varnameStr and that the + connections are properly propagated across nodegraph boundaries. + """ + cmds.file(f=True, new=True) + + sphere_xform = cmds.polySphere()[0] + + material_node = cmds.shadingNode("usdPreviewSurface", asShader=True, + name="ss01") + material_sg = cmds.sets(renderable=True, noSurfaceShader=True, + empty=True, name="ss01SG") + cmds.connectAttr(material_node+".outColor", + material_sg+".surfaceShader", force=True) + cmds.sets(sphere_xform, e=True, forceElement=material_sg) + + # One file with UVs connected to diffuse: + file_node = cmds.shadingNode("file", asTexture=True, + isColorManaged=True) + uv_node = cmds.shadingNode("place2dTexture", asUtility=True) + cmds.setAttr(uv_node + ".offsetU", 0.125) + cmds.setAttr(uv_node + ".offsetV", 0.5) + connectUVNode(uv_node, file_node) + cmds.connectAttr(file_node + ".outColor", + material_node + ".diffuseColor", f=True) + + # Another file, same UVs, connected to emissiveColor + file_node = cmds.shadingNode("file", asTexture=True, + isColorManaged=True) + connectUVNode(uv_node, file_node) + cmds.connectAttr(file_node + ".outColor", + material_node + ".emissiveColor", f=True) + + # Another file, no UVs, connected to metallic + file_node = cmds.shadingNode("file", asTexture=True, + isColorManaged=True) + cmds.connectAttr(file_node + ".outColorR", + material_node + ".metallic", f=True) + + # Another file, no UVs, connected to roughness + file_node = cmds.shadingNode("file", asTexture=True, + isColorManaged=True) + cmds.connectAttr(file_node + ".outColorR", + material_node + ".roughness", f=True) + cmds.setAttr(file_node + ".offsetU", 0.25) + cmds.setAttr(file_node + ".offsetV", 0.75) + + # Export to USD: + usd_path = os.path.abspath('MinimalUVReader.usda') + cmds.usdExport(mergeTransformAndShape=True, + file=usd_path, + shadingMode='useRegistry', + convertMaterialsTo=['MaterialX', 'UsdPreviewSurface']) + + # We expect 2 primvar readers, and 2 st transforms: + stage = Usd.Stage.Open(usd_path) + mat_path = "/pSphere1/Looks/ss01SG" + + # Here are the expected connections in the produced USD file: + connections = [ + # UsdPreviewSurface section + + # Source node, input, destination node: + ("/UsdPreviewSurface/ss01", "diffuseColor", "/UsdPreviewSurface/file1"), + ("/UsdPreviewSurface/file1", "st", "/UsdPreviewSurface/place2dTexture1_UsdTransform2d"), + ("/UsdPreviewSurface/place2dTexture1_UsdTransform2d", "in", "/UsdPreviewSurface/place2dTexture1"), + + ("/UsdPreviewSurface/ss01", "emissiveColor", "/UsdPreviewSurface/file2"), + ("/UsdPreviewSurface/file2", "st", "/UsdPreviewSurface/place2dTexture1_UsdTransform2d"), # re-used + # Note that the transform name is derived from place2DTexture name. + + ("/UsdPreviewSurface/ss01", "metallic", "/UsdPreviewSurface/file3"), + ("/UsdPreviewSurface/file3", "st", "/UsdPreviewSurface/shared_TexCoordReader"), # no UV in Maya. + + ("/UsdPreviewSurface/ss01", "roughness", "/UsdPreviewSurface/file4"), + ("/UsdPreviewSurface/file4", "st", "/UsdPreviewSurface/file4_UsdTransform2d"), # xform on file node + ("/UsdPreviewSurface/file4_UsdTransform2d", "in", "/UsdPreviewSurface/shared_TexCoordReader"), + # Note that the transform name is derived from file node name. + + # MaterialX section + + # Source node, input, destination node: + ("/MaterialX/MayaNG_MaterialX", "diffuseColor", "/MaterialX/MayaNG_MaterialX/MayaSwizzle_file1_rgb"), + ("/MaterialX/MayaNG_MaterialX/MayaSwizzle_file1_rgb", "in", "/MaterialX/MayaNG_MaterialX/file1"), + ("/MaterialX/MayaNG_MaterialX/file1", "texcoord", "/MaterialX/MayaNG_MaterialX/place2dTexture1"), + + ("/MaterialX/MayaNG_MaterialX", "emissiveColor", "/MaterialX/MayaNG_MaterialX/MayaSwizzle_file2_rgb"), + ("/MaterialX/MayaNG_MaterialX/MayaSwizzle_file2_rgb", "in", "/MaterialX/MayaNG_MaterialX/file2"), + ("/MaterialX/MayaNG_MaterialX/file2", "texcoord", "/MaterialX/MayaNG_MaterialX/place2dTexture1"), # re-used + + ("/MaterialX/MayaNG_MaterialX", "metallic", "/MaterialX/MayaNG_MaterialX/MayaSwizzle_file3_r"), + ("/MaterialX/MayaNG_MaterialX/MayaSwizzle_file3_r", "in", "/MaterialX/MayaNG_MaterialX/file3"), + ("/MaterialX/MayaNG_MaterialX/file3", "texcoord", "/MaterialX/MayaNG_MaterialX/shared_MayaGeomPropValue"), # no UV in Maya. + + ("/MaterialX/MayaNG_MaterialX", "roughness", "/MaterialX/MayaNG_MaterialX/MayaSwizzle_file4_r"), + ("/MaterialX/MayaNG_MaterialX/MayaSwizzle_file4_r", "in", "/MaterialX/MayaNG_MaterialX/file4"), + ("/MaterialX/MayaNG_MaterialX/file4", "texcoord", "/MaterialX/MayaNG_MaterialX/shared_MayaGeomPropValue"), # re-used + + # Making sure no NodeGraph boundaries were skipped downstream: + ("", "surface", "/UsdPreviewSurface"), + ("/UsdPreviewSurface", "surface", "/UsdPreviewSurface/ss01"), + + ("", "mtlx:surface", "/MaterialX"), + ("/MaterialX", "surface", "/MaterialX/ss01"), + + # Making sure no NodeGraph boundaries were skipped upstream: + ("/UsdPreviewSurface/place2dTexture1", "varname", "/UsdPreviewSurface"), + ("/UsdPreviewSurface", "file1:varname", ""), + + ("/MaterialX/MayaNG_MaterialX/place2dTexture1", "geomprop", "/MaterialX/MayaNG_MaterialX"), + ("/MaterialX/MayaNG_MaterialX", "file1:varnameStr", "/MaterialX"), + ("/MaterialX", "file1:varnameStr", ""), + ] + for src_name, input_name, dst_name in connections: + src_prim = stage.GetPrimAtPath(mat_path + src_name) + self.assertTrue(src_prim, mat_path + src_name + " does not exist") + src_shade = UsdShade.ConnectableAPI(src_prim) + self.assertTrue(src_shade) + src_input = src_shade.GetInput(input_name) + if not src_input: + src_input = src_shade.GetOutput(input_name) + self.assertTrue(src_input, input_name + " does not exist on " + mat_path + src_name) + self.assertTrue(src_input.HasConnectedSource(), input_name + " does not have source") + (connect_api, out_name, _) = src_input.GetConnectedSource() + self.assertEqual(connect_api.GetPath(), mat_path + dst_name) + if __name__ == '__main__': unittest.main(verbosity=2) From 05eb281bc8c6854ca958379b508caf3590cc414f Mon Sep 17 00:00:00 2001 From: Jerry Gamache Date: Tue, 21 Dec 2021 17:12:44 -0500 Subject: [PATCH 2/3] Clang-format. --- lib/usd/translators/shading/mtlxFileTextureWriter.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/usd/translators/shading/mtlxFileTextureWriter.cpp b/lib/usd/translators/shading/mtlxFileTextureWriter.cpp index 8ff5d93388..8c677aaea0 100644 --- a/lib/usd/translators/shading/mtlxFileTextureWriter.cpp +++ b/lib/usd/translators/shading/mtlxFileTextureWriter.cpp @@ -159,8 +159,8 @@ MtlxUsd_FileWriter::MtlxUsd_FileWriter( UsdShadeInput varnameInput = primvarReaderSchema.CreateInput(TrMtlxTokens->geomprop, SdfValueTypeNames->String); - TfToken inputName(TfStringPrintf( - "%s:%s", depNodeFn.name().asChar(), TrMtlxTokens->varnameStr.GetText())); + TfToken inputName( + TfStringPrintf("%s:%s", depNodeFn.name().asChar(), TrMtlxTokens->varnameStr.GetText())); // We expose the primvar reader varnameStr attribute to the material to allow // easy specialization based on UV mappings to geometries: @@ -201,8 +201,7 @@ MtlxUsd_FileWriter::MtlxUsd_FileWriter( } else { // Re-using an existing primvar reader: UsdShadeShader primvarReaderShaderSchema(GetUsdStage()->GetPrimAtPath(primvarReaderPath)); - UsdShadeOutput primvarReaderOutput - = primvarReaderShaderSchema.GetOutput(TrMtlxTokens->out); + UsdShadeOutput primvarReaderOutput = primvarReaderShaderSchema.GetOutput(TrMtlxTokens->out); // TODO: Handle UV SRT with a ND_place2d_vector2 node. Make sure the name derives from the // place2dTexture node if there is one (see usdFileTextureWriter for details) From b32ba8c2f4da9512f70f1fb6ec19611232b12aef Mon Sep 17 00:00:00 2001 From: Jerry Gamache Date: Mon, 3 Jan 2022 12:22:52 -0500 Subject: [PATCH 3/3] Refactor common code. --- .../shading/mtlxFileTextureWriter.cpp | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/lib/usd/translators/shading/mtlxFileTextureWriter.cpp b/lib/usd/translators/shading/mtlxFileTextureWriter.cpp index 8c677aaea0..23674d5bd3 100644 --- a/lib/usd/translators/shading/mtlxFileTextureWriter.cpp +++ b/lib/usd/translators/shading/mtlxFileTextureWriter.cpp @@ -150,7 +150,8 @@ MtlxUsd_FileWriter::MtlxUsd_FileWriter( } // Now create a geompropvalue reader that the image shader will use. - SdfPath primvarReaderPath = _GetPlace2DTexturePath(depNodeFn); + SdfPath primvarReaderPath = _GetPlace2DTexturePath(depNodeFn); + UsdShadeOutput primvarReaderOutput; if (!GetUsdStage()->GetPrimAtPath(primvarReaderPath)) { UsdShadeShader primvarReaderSchema @@ -188,29 +189,20 @@ MtlxUsd_FileWriter::MtlxUsd_FileWriter( varnameInput.Set(UsdUtilsGetPrimaryUVSetName()); } - UsdShadeOutput primvarReaderOutput + primvarReaderOutput = primvarReaderSchema.CreateOutput(TrMtlxTokens->out, SdfValueTypeNames->Float2); - - // TODO: Handle UV SRT with a ND_place2d_vector2 node. Make sure the name derives from the - // place2dTexture node if there is one (see usdFileTextureWriter for details) - - // Connect the output of the primvar reader to the texture coordinate - // input of the UV texture. - texSchema.CreateInput(TrMtlxTokens->texcoord, SdfValueTypeNames->Float2) - .ConnectToSource(primvarReaderOutput); } else { // Re-using an existing primvar reader: UsdShadeShader primvarReaderShaderSchema(GetUsdStage()->GetPrimAtPath(primvarReaderPath)); - UsdShadeOutput primvarReaderOutput = primvarReaderShaderSchema.GetOutput(TrMtlxTokens->out); - - // TODO: Handle UV SRT with a ND_place2d_vector2 node. Make sure the name derives from the - // place2dTexture node if there is one (see usdFileTextureWriter for details) - - // Connect the output of the primvar reader to the texture coordinate - // input of the UV texture. - texSchema.CreateInput(TrMtlxTokens->texcoord, SdfValueTypeNames->Float2) - .ConnectToSource(primvarReaderOutput); + primvarReaderOutput = primvarReaderShaderSchema.GetOutput(TrMtlxTokens->out); } + // TODO: Handle UV SRT with a ND_place2d_vector2 node. Make sure the name derives from the + // place2dTexture node if there is one (see usdFileTextureWriter for details) + + // Connect the output of the primvar reader to the texture coordinate + // input of the UV texture. + texSchema.CreateInput(TrMtlxTokens->texcoord, SdfValueTypeNames->Float2) + .ConnectToSource(primvarReaderOutput); } /* virtual */