diff --git a/lib/mayaUsd/fileio/shading/shadingModeExporterContext.cpp b/lib/mayaUsd/fileio/shading/shadingModeExporterContext.cpp index 25f4cbdb76..1cbb97b33e 100644 --- a/lib/mayaUsd/fileio/shading/shadingModeExporterContext.cpp +++ b/lib/mayaUsd/fileio/shading/shadingModeExporterContext.cpp @@ -537,7 +537,7 @@ class _UVMappingManager = _material.GetPrim().GetPath().GetParentPath().AppendChild(TfToken(newName.c_str())); UsdShadeMaterial newMaterial = UsdShadeMaterial::Define(_material.GetPrim().GetStage(), newPath); - newMaterial.GetPrim().GetSpecializes().AddSpecialize(_material.GetPrim().GetPath()); + newMaterial.SetBaseMaterial(_material); TfTokenVector::const_iterator itNode = _nodesWithUVInput.cbegin(); TfTokenVector::const_iterator itName = uvNames.cbegin(); diff --git a/lib/mayaUsd/fileio/translators/translatorMaterial.cpp b/lib/mayaUsd/fileio/translators/translatorMaterial.cpp index c185c82611..d74cd77523 100644 --- a/lib/mayaUsd/fileio/translators/translatorMaterial.cpp +++ b/lib/mayaUsd/fileio/translators/translatorMaterial.cpp @@ -28,7 +28,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -38,8 +40,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -51,6 +55,66 @@ PXR_NAMESPACE_OPEN_SCOPE +// clang-format off +TF_DEFINE_PRIVATE_TOKENS( + _tokens, + + (inputs) + (varname) +); +// clang-format on + +namespace { +// We want to know if this material is a specialization that was created to handle UV mappings on +// export. For details, see the _UVMappingManager class in ..\shading\shadingModeExporterContext.cpp +// +bool _IsMergeableMaterial(const UsdShadeMaterial& shadeMaterial) +{ + if (!shadeMaterial || !shadeMaterial.HasBaseMaterial()) { + return false; + } + + // Check for materials created by _UVMappingManager::getMaterial(). This code could probably be + // expanded to be more generic and handle more complex composition arcs at a later stage. + + UsdPrimCompositionQuery query(shadeMaterial.GetPrim()); + if (query.GetCompositionArcs().size() != 2) { + // Materials created by the _UVMappingManager have only 2 arcs: + return false; + } + + // This is a little more robust than grabbing a specific arc index. + UsdPrimCompositionQuery::Filter filter; + filter.arcTypeFilter = UsdPrimCompositionQuery::ArcTypeFilter::Specialize; + query.SetFilter(filter); + std::vector arcs = query.GetCompositionArcs(); + const UsdPrimCompositionQueryArc specializationArc = arcs.front(); + + const SdfLayerHandle layer = specializationArc.GetIntroducingLayer(); + const SdfPrimSpecHandle primSpec = layer->GetPrimAtPath(shadeMaterial.GetPath()); + + // If the primSpec that specializes the base material introduces other + // namespace children, it can't be merged. + if (!primSpec->GetNameChildren().empty()) { + return false; + } + + // Check that the only properties authored are varname 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"] + if (splitName.size() != 3u || splitName[0u] != _tokens->inputs.GetString() + || splitName[2u] != _tokens->varname.GetString()) { + return false; + } + } + + return true; +} +} // namespace + /* static */ MObject UsdMayaTranslatorMaterial::Read( const UsdMayaJobImportArgs& jobArguments, @@ -70,6 +134,11 @@ MObject UsdMayaTranslatorMaterial::Read( return shadingEngine; } + if (_IsMergeableMaterial(shadeMaterial)) { + // Use the base material instead + return Read(jobArguments, shadeMaterial.GetBaseMaterial(), boundPrim, context); + } + UsdMayaJobImportArgs localArguments = jobArguments; for (const auto& shadingMode : jobArguments.shadingModes) { if (shadingMode.mode == UsdMayaShadingModeTokens->none) { @@ -89,10 +158,87 @@ MObject UsdMayaTranslatorMaterial::Read( return shadingEngine; } +namespace { +using _UVBindings = std::map; + +static _UVBindings +_GetUVBindingsFromMaterial(const UsdShadeMaterial& material, UsdMayaPrimReaderContext* context) +{ + _UVBindings retVal; + + if (!material || !context) { + return retVal; + } + const bool isMergeable = _IsMergeableMaterial(material); + // Find out the nodes requiring mapping. This code has deep knowledge of how the mappings are + // exported. See the _UVMappingManager class in ..\shading\shadingModeExporterContext.cpp for + // details. + 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()) { + continue; + } + VtValue val; + usdAttr.Get(&val); + if (!val.IsHolding()) { + continue; + } + SdfPath nodePath = isMergeable + ? material.GetBaseMaterial().GetPath().AppendChild(TfToken(splitName[1])) + : material.GetPath().AppendChild(TfToken(splitName[1])); + MObject mayaNode = context->GetMayaNode(nodePath, false); + MStatus status; + MFnDependencyNode depFn(mayaNode, &status); + if (!status) { + continue; + } + retVal[val.UncheckedGet()] = TfToken(depFn.name().asChar()); + } + + return retVal; +} + +static void _BindUVs(const MDagPath& shapeDagPath, const _UVBindings& uvBindings) +{ + if (uvBindings.empty()) { + return; + } + + MStatus status; + MFnMesh meshFn(shapeDagPath, &status); + if (!status) { + return; + } + + MStringArray uvSets; + meshFn.getUVSetNames(uvSets); + + // We explicitly skip uvSet[0] since it is the default in Maya and does not require explicit + // linking: + for (unsigned int uvSetIndex = 1; uvSetIndex < uvSets.length(); uvSetIndex++) { + TfToken uvSetName(uvSets[uvSetIndex].asChar()); + _UVBindings::const_iterator iter = uvBindings.find(uvSetName); + if (iter == uvBindings.cend()) { + continue; + } + MString uvLinkCommand("uvLink -make -uvs \""); + uvLinkCommand += shapeDagPath.fullPathName(); + uvLinkCommand += ".uvSet["; + uvLinkCommand += uvSetIndex; + uvLinkCommand += "].uvSetName\" -t \""; + uvLinkCommand += iter->second.GetText(); // texture name + uvLinkCommand += "\";"; + status = MGlobal::executeCommand(uvLinkCommand); + } +} +} // namespace + static bool _AssignMaterialFaceSet( - const MObject& shadingEngine, - const MDagPath& shapeDagPath, - const VtIntArray& faceIndices) + const MObject& shadingEngine, + const MDagPath& shapeDagPath, + const VtIntArray& faceIndices, + const _UVBindings& faceUVBindings) { MStatus status; @@ -117,6 +263,7 @@ static bool _AssignMaterialFaceSet( "Could not add component to shadingEngine %s.", seFnSet.name().asChar()); return false; } + _BindUVs(shapeDagPath, faceUVBindings); } return true; @@ -141,14 +288,18 @@ bool UsdMayaTranslatorMaterial::AssignMaterial( MStatus status; const UsdShadeMaterialBindingAPI bindingAPI(primSchema.GetPrim()); - MObject shadingEngine = UsdMayaTranslatorMaterial::Read( - jobArguments, bindingAPI.ComputeBoundMaterial(), primSchema, context); + UsdShadeMaterial meshMaterial = bindingAPI.ComputeBoundMaterial(); + _UVBindings uvBindings; + MObject shadingEngine + = UsdMayaTranslatorMaterial::Read(jobArguments, meshMaterial, primSchema, context); if (shadingEngine.isNull()) { status = UsdMayaUtil::GetMObjectByName("initialShadingGroup", shadingEngine); if (status != MS::kSuccess) { return false; } + } else { + uvBindings = _GetUVBindingsFromMaterial(meshMaterial, context); } // If the gprim does not have a material faceSet which represents per-face @@ -164,6 +315,7 @@ bool UsdMayaTranslatorMaterial::AssignMaterial( TF_RUNTIME_ERROR( "Could not add shadingEngine for '%s'.", shapeDagPath.fullPathName().asChar()); } + _BindUVs(shapeDagPath, uvBindings); } return true; @@ -197,7 +349,8 @@ bool UsdMayaTranslatorMaterial::AssignMaterial( VtIntArray unassignedIndices = UsdGeomSubset::GetUnassignedIndices(faceSubsets, faceCount); - if (!_AssignMaterialFaceSet(shadingEngine, shapeDagPath, unassignedIndices)) { + if (!_AssignMaterialFaceSet( + shadingEngine, shapeDagPath, unassignedIndices, uvBindings)) { return false; } } @@ -208,12 +361,16 @@ bool UsdMayaTranslatorMaterial::AssignMaterial( if (boundMaterial) { MObject faceSubsetShadingEngine = UsdMayaTranslatorMaterial::Read( jobArguments, boundMaterial, UsdGeomGprim(), context); + + _UVBindings faceUVBindings; if (faceSubsetShadingEngine.isNull()) { status = UsdMayaUtil::GetMObjectByName( "initialShadingGroup", faceSubsetShadingEngine); if (status != MS::kSuccess) { return false; } + } else { + faceUVBindings = _GetUVBindingsFromMaterial(boundMaterial, context); } // Only transfer the first timeSample or default indices, if @@ -221,7 +378,8 @@ bool UsdMayaTranslatorMaterial::AssignMaterial( VtIntArray indices; subset.GetIndicesAttr().Get(&indices, UsdTimeCode::EarliestTime()); - if (!_AssignMaterialFaceSet(faceSubsetShadingEngine, shapeDagPath, indices)) { + if (!_AssignMaterialFaceSet( + faceSubsetShadingEngine, shapeDagPath, indices, faceUVBindings)) { return false; } } diff --git a/lib/usd/translators/meshReader.cpp b/lib/usd/translators/meshReader.cpp index 7547a0cbcf..feacab2abd 100644 --- a/lib/usd/translators/meshReader.cpp +++ b/lib/usd/translators/meshReader.cpp @@ -127,15 +127,16 @@ bool MayaUsdPrimReaderMesh::Read(UsdMayaPrimReaderContext* context) } } - // assign material - assignMaterial(mesh, _GetArgs(), meshRead.meshObject(), context); - // assign primvars to mesh UsdMayaMeshReadUtils::assignPrimvarsToMesh( mesh, meshRead.meshObject(), _GetArgs().GetExcludePrimvarNames()); + // assign invisible faces UsdMayaMeshReadUtils::assignInvisibleFaces(mesh, meshRead.meshObject()); + // assign material + assignMaterial(mesh, _GetArgs(), meshRead.meshObject(), context); + return true; } diff --git a/lib/usd/translators/shading/usdUVTextureReader.cpp b/lib/usd/translators/shading/usdUVTextureReader.cpp index f83ea4d0ca..b9fa95060b 100644 --- a/lib/usd/translators/shading/usdUVTextureReader.cpp +++ b/lib/usd/translators/shading/usdUVTextureReader.cpp @@ -98,6 +98,12 @@ TF_DEFINE_PRIVATE_TOKENS( (wrapS) (wrapT) + // uv connections: + (outUvFilterSize) + (uvFilterSize) + (outUV) + (uvCoord) + // Values for wrapS and wrapT (black) (repeat) @@ -175,6 +181,16 @@ bool PxrMayaUsdUVTexture_Reader::Read(UsdMayaPrimReaderContext* context) } // Connect manually (fileTexturePlacementConnect is not available in batch): + { + MPlug uvPlug = uvDepFn.findPlug(_tokens->outUV.GetText(), true, &status); + MPlug filePlug = depFn.findPlug(_tokens->uvCoord.GetText(), true, &status); + UsdMayaUtil::Connect(uvPlug, filePlug, false); + } + { + MPlug uvPlug = uvDepFn.findPlug(_tokens->outUvFilterSize.GetText(), true, &status); + MPlug filePlug = depFn.findPlug(_tokens->uvFilterSize.GetText(), true, &status); + UsdMayaUtil::Connect(uvPlug, filePlug, false); + } MString connectCmd; for (const TfToken& uvName : _Place2dTextureConnections) { MPlug uvPlug = uvDepFn.findPlug(uvName.GetText(), true, &status); diff --git a/test/lib/usd/translators/CMakeLists.txt b/test/lib/usd/translators/CMakeLists.txt index 08e8064550..e0640e4bd7 100644 --- a/test/lib/usd/translators/CMakeLists.txt +++ b/test/lib/usd/translators/CMakeLists.txt @@ -56,6 +56,7 @@ set(TEST_SCRIPT_FILES testUsdImportSessionLayer.py testUsdImportShadingModeDisplayColor.py testUsdImportShadingModePxrRis.py + testUsdImportUVSetMappings.py testUsdExportImportRoundtripPreviewSurface.py testUsdExportImportUDIM.py testUsdImportSkeleton.py diff --git a/test/lib/usd/translators/UsdImportUVSetMappingsTest/UsdImportUVSetMappings.usda b/test/lib/usd/translators/UsdImportUVSetMappingsTest/UsdImportUVSetMappings.usda new file mode 100644 index 0000000000..c98ebe2738 --- /dev/null +++ b/test/lib/usd/translators/UsdImportUVSetMappingsTest/UsdImportUVSetMappings.usda @@ -0,0 +1,424 @@ +#usda 1.0 +( + defaultPrim = "pPlane1" + metersPerUnit = 0.01 + upAxis = "Y" +) + +def Mesh "pPlane1" ( + prepend apiSchemas = ["MaterialBindingAPI"] + kind = "component" +) +{ + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, 0, -0.5), (0.5, 0, 0.5)] + int[] faceVertexCounts = [4, 4, 4, 4] + int[] faceVertexIndices = [0, 1, 4, 3, 1, 2, 5, 4, 3, 4, 7, 6, 4, 5, 8, 7] + rel material:binding = + point3f[] points = [(-0.5, 0, 0.5), (0, 0, 0.5), (0.5, 0, 0.5), (-0.5, 0, 0), (0, 0, 0), (0.5, 0, 0), (-0.5, 0, -0.5), (0, 0, -0.5), (0.5, 0, -0.5)] + texCoord2f[] primvars:st = [(0, 0), (0.5, 0), (0.5, 0.5), (0, 0.5), (1, 0), (1, 0.5), (0.5, 1), (0, 1), (1, 1)] ( + interpolation = "vertex" + ) + texCoord2f[] primvars:st0 = [(0.633502, 0.0017636716), (0.8158692, 0.31763285), (0.5, 0.5), (0.31763285, 0.18413082), (0.9982363, 0.633502), (0.68236715, 0.8158692), (0.18413082, 0.68236715), (0.0017636716, 0.366498), (0.366498, 0.9982363)] ( + interpolation = "vertex" + ) + int[] primvars:st0:indices = [0, 1, 4, 3, 2, 5, 7, 6, 8] + texCoord2f[] primvars:st1 = [(0.0017636716, 0.633502), (0.18413082, 0.31763285), (0.5, 0.5), (0.31763285, 0.8158692), (0.366498, 0.0017636716), (0.68236715, 0.18413082), (0.8158692, 0.68236715), (0.633502, 0.9982363), (0.9982363, 0.366498)] ( + interpolation = "vertex" + ) + int[] primvars:st1:indices = [0, 1, 4, 3, 2, 5, 7, 6, 8] + int[] primvars:st:indices = [0, 1, 4, 3, 2, 5, 7, 6, 8] + float3 xformOp:rotateXYZ = (90, 0, 0) + uniform token[] xformOpOrder = ["xformOp:rotateXYZ"] +} + +def Mesh "pPlane2" ( + prepend apiSchemas = ["MaterialBindingAPI"] + kind = "component" +) +{ + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, -1.110223e-16, -0.5), (0.5, 1.110223e-16, 0.5)] + int[] faceVertexCounts = [4, 4, 4, 4] + int[] faceVertexIndices = [0, 1, 4, 3, 1, 2, 5, 4, 3, 4, 7, 6, 4, 5, 8, 7] + point3f[] points = [(-0.5, -1.110223e-16, 0.5), (0, -1.110223e-16, 0.5), (0.5, -1.110223e-16, 0.5), (-0.5, 0, 0), (0, 0, 0), (0.5, 0, 0), (-0.5, 1.110223e-16, -0.5), (0, 1.110223e-16, -0.5), (0.5, 1.110223e-16, -0.5)] + texCoord2f[] primvars:st = [(0, 0), (0.5, 0), (0.5, 0.5), (0, 0.5), (1, 0), (1, 0.5), (0.5, 1), (0, 1), (1, 1)] ( + interpolation = "vertex" + ) + texCoord2f[] primvars:st0 = [(0.633502, 0.0017636716), (0.8158692, 0.31763285), (0.5, 0.5), (0.31763285, 0.18413082), (0.9982363, 0.633502), (0.68236715, 0.8158692), (0.18413082, 0.68236715), (0.0017636716, 0.366498), (0.366498, 0.9982363)] ( + interpolation = "vertex" + ) + int[] primvars:st0:indices = [0, 1, 4, 3, 2, 5, 7, 6, 8] + texCoord2f[] primvars:st1 = [(0.0017636716, 0.633502), (0.18413082, 0.31763285), (0.5, 0.5), (0.31763285, 0.8158692), (0.366498, 0.0017636716), (0.68236715, 0.18413082), (0.8158692, 0.68236715), (0.633502, 0.9982363), (0.9982363, 0.366498)] ( + interpolation = "vertex" + ) + int[] primvars:st1:indices = [0, 1, 4, 3, 2, 5, 7, 6, 8] + int[] primvars:st:indices = [0, 1, 4, 3, 2, 5, 7, 6, 8] + uniform token subsetFamily:materialBind:familyType = "partition" + float3 xformOp:rotateXYZ = (90, 0, 0) + double3 xformOp:translate = (1.2, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def GeomSubset "blinn1SG" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform token elementType = "face" + uniform token familyName = "materialBind" + int[] indices = [1] + rel material:binding = + } + + def GeomSubset "blinn2SG" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform token elementType = "face" + uniform token familyName = "materialBind" + int[] indices = [2] + rel material:binding = + } + + def GeomSubset "blinn3SG" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform token elementType = "face" + uniform token familyName = "materialBind" + int[] indices = [3] + rel material:binding = + } + + def GeomSubset "blinn4SG" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform token elementType = "face" + uniform token familyName = "materialBind" + int[] indices = [0] + rel material:binding = + } +} + +def Mesh "pPlane3" ( + prepend apiSchemas = ["MaterialBindingAPI"] + kind = "component" +) +{ + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, -1.110223e-16, -0.5), (0.5, 1.110223e-16, 0.5)] + int[] faceVertexCounts = [4, 4, 4, 4] + int[] faceVertexIndices = [0, 1, 4, 3, 1, 2, 5, 4, 3, 4, 7, 6, 4, 5, 8, 7] + rel material:binding = + point3f[] points = [(-0.5, -1.110223e-16, 0.5), (0, -1.110223e-16, 0.5), (0.5, -1.110223e-16, 0.5), (-0.5, 0, 0), (0, 0, 0), (0.5, 0, 0), (-0.5, 1.110223e-16, -0.5), (0, 1.110223e-16, -0.5), (0.5, 1.110223e-16, -0.5)] + texCoord2f[] primvars:map1 = [(0, 0), (0.5, 0), (0.5, 0.5), (0, 0.5), (1, 0), (1, 0.5), (0.5, 1), (0, 1), (1, 1)] ( + interpolation = "vertex" + ) + int[] primvars:map1:indices = [0, 1, 4, 3, 2, 5, 7, 6, 8] + texCoord2f[] primvars:uvSet1 = [(0.633502, 0.0017636716), (0.8158692, 0.31763285), (0.5, 0.5), (0.31763285, 0.18413082), (0.9982363, 0.633502), (0.68236715, 0.8158692), (0.18413082, 0.68236715), (0.0017636716, 0.366498), (0.366498, 0.9982363)] ( + interpolation = "vertex" + ) + int[] primvars:uvSet1:indices = [0, 1, 4, 3, 2, 5, 7, 6, 8] + texCoord2f[] primvars:uvSet2 = [(0.0017636716, 0.633502), (0.18413082, 0.31763285), (0.5, 0.5), (0.31763285, 0.8158692), (0.366498, 0.0017636716), (0.68236715, 0.18413082), (0.8158692, 0.68236715), (0.633502, 0.9982363), (0.9982363, 0.366498)] ( + interpolation = "vertex" + ) + int[] primvars:uvSet2:indices = [0, 1, 4, 3, 2, 5, 7, 6, 8] + float3 xformOp:rotateXYZ = (90, 0, 0) + double3 xformOp:translate = (0, 1.2, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] +} + +def Mesh "pPlane4" ( + prepend apiSchemas = ["MaterialBindingAPI"] + kind = "component" +) +{ + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, -1.110223e-16, -0.5), (0.5, 1.110223e-16, 0.5)] + int[] faceVertexCounts = [4, 4, 4, 4] + int[] faceVertexIndices = [0, 1, 4, 3, 1, 2, 5, 4, 3, 4, 7, 6, 4, 5, 8, 7] + point3f[] points = [(-0.5, -1.110223e-16, 0.5), (0, -1.110223e-16, 0.5), (0.5, -1.110223e-16, 0.5), (-0.5, 0, 0), (0, 0, 0), (0.5, 0, 0), (-0.5, 1.110223e-16, -0.5), (0, 1.110223e-16, -0.5), (0.5, 1.110223e-16, -0.5)] + texCoord2f[] primvars:map1 = [(0, 0), (0.5, 0), (0.5, 0.5), (0, 0.5), (1, 0), (1, 0.5), (0.5, 1), (0, 1), (1, 1)] ( + interpolation = "vertex" + ) + int[] primvars:map1:indices = [0, 1, 4, 3, 2, 5, 7, 6, 8] + texCoord2f[] primvars:uvSet1 = [(0.633502, 0.0017636716), (0.8158692, 0.31763285), (0.5, 0.5), (0.31763285, 0.18413082), (0.9982363, 0.633502), (0.68236715, 0.8158692), (0.18413082, 0.68236715), (0.0017636716, 0.366498), (0.366498, 0.9982363)] ( + interpolation = "vertex" + ) + int[] primvars:uvSet1:indices = [0, 1, 4, 3, 2, 5, 7, 6, 8] + texCoord2f[] primvars:uvSet2 = [(0.0017636716, 0.633502), (0.18413082, 0.31763285), (0.5, 0.5), (0.31763285, 0.8158692), (0.366498, 0.0017636716), (0.68236715, 0.18413082), (0.8158692, 0.68236715), (0.633502, 0.9982363), (0.9982363, 0.366498)] ( + interpolation = "vertex" + ) + int[] primvars:uvSet2:indices = [0, 1, 4, 3, 2, 5, 7, 6, 8] + uniform token subsetFamily:materialBind:familyType = "partition" + float3 xformOp:rotateXYZ = (90, 0, 0) + double3 xformOp:translate = (1.2, 1.2, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def GeomSubset "blinn1SG" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform token elementType = "face" + uniform token familyName = "materialBind" + int[] indices = [1] + rel material:binding = + } + + def GeomSubset "blinn2SG" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform token elementType = "face" + uniform token familyName = "materialBind" + int[] indices = [2] + rel material:binding = + } + + def GeomSubset "blinn3SG" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform token elementType = "face" + uniform token familyName = "materialBind" + int[] indices = [3] + rel material:binding = + } + + def GeomSubset "blinn4SG" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform token elementType = "face" + uniform token familyName = "materialBind" + int[] indices = [0] + rel material:binding = + } +} + +def Material "blinn1SG" ( + kind = "assembly" +) +{ + token inputs:file1:varname = "map1" + token inputs:file2:varname = "uvSet1" + token inputs:file3:varname = "uvSet2" + token outputs:surface.connect = + + def Shader "blinn1" + { + uniform token info:id = "UsdPreviewSurface" + color3f inputs:diffuseColor.connect = + color3f inputs:emissiveColor.connect = + normal3f inputs:normal = (1, 1, 1) + float inputs:roughness = 0.3 + color3f inputs:specularColor.connect = + int inputs:useSpecularWorkflow = 1 + token outputs:displacement + token outputs:surface + } + + def Shader "file1" + { + uniform token info:id = "UsdUVTexture" + float4 inputs:fallback = (0.5, 0.5, 0.5, 1) + asset inputs:file = @lines.png@ + float2 inputs:st.connect = + token inputs:wrapS = "repeat" + token inputs:wrapT = "repeat" + float3 outputs:rgb + + def Shader "TexCoordReader" + { + uniform token info:id = "UsdPrimvarReader_float2" + token inputs:varname.connect = + float2 outputs:result + } + } + + def Shader "file2" + { + uniform token info:id = "UsdUVTexture" + float4 inputs:fallback = (0.5, 0.5, 0.5, 1) + asset inputs:file = @lines.png@ + float2 inputs:st.connect = + token inputs:wrapS = "repeat" + token inputs:wrapT = "repeat" + float3 outputs:rgb + + def Shader "TexCoordReader" + { + uniform token info:id = "UsdPrimvarReader_float2" + token inputs:varname.connect = + float2 outputs:result + } + } + + def Shader "file3" + { + uniform token info:id = "UsdUVTexture" + float4 inputs:fallback = (0.5, 0.5, 0.5, 1) + asset inputs:file = @lines.png@ + float2 inputs:st.connect = + token inputs:wrapS = "repeat" + token inputs:wrapT = "repeat" + float3 outputs:rgb + + def Shader "TexCoordReader" + { + uniform token info:id = "UsdPrimvarReader_float2" + token inputs:varname.connect = + float2 outputs:result + } + } +} + +def Material "blinn1SG_st_st0_st1" ( + prepend specializes = +) +{ + token inputs:file1:varname = "st" + token inputs:file2:varname = "st0" + token inputs:file3:varname = "st1" +} + +def Material "blinn2SG" ( + kind = "assembly" +) +{ + token inputs:file4:varname = "map1" + token outputs:surface.connect = + + def Shader "blinn2" + { + uniform token info:id = "UsdPreviewSurface" + color3f inputs:diffuseColor.connect = + normal3f inputs:normal = (1, 1, 1) + float inputs:roughness = 0.3 + color3f inputs:specularColor = (0.35, 0.35, 0.35) + int inputs:useSpecularWorkflow = 1 + token outputs:displacement + token outputs:surface + } + + def Shader "file4" + { + uniform token info:id = "UsdUVTexture" + float4 inputs:fallback = (0.5, 0.5, 0.5, 1) + asset inputs:file = @lines.png@ + float2 inputs:st.connect = + token inputs:wrapS = "repeat" + token inputs:wrapT = "repeat" + float3 outputs:rgb + + def Shader "TexCoordReader" + { + uniform token info:id = "UsdPrimvarReader_float2" + token inputs:varname.connect = + float2 outputs:result + } + } +} + +def Material "blinn2SG_st" ( + prepend specializes = +) +{ + token inputs:file4:varname = "st" +} + +def Material "blinn3SG" ( + kind = "assembly" +) +{ + token inputs:file5:varname = "st0" + token outputs:surface.connect = + + def Shader "blinn3" + { + uniform token info:id = "UsdPreviewSurface" + color3f inputs:diffuseColor.connect = + normal3f inputs:normal = (1, 1, 1) + float inputs:roughness = 0.3 + color3f inputs:specularColor = (0.35, 0.35, 0.35) + int inputs:useSpecularWorkflow = 1 + token outputs:displacement + token outputs:surface + } + + def Shader "file5" + { + uniform token info:id = "UsdUVTexture" + float4 inputs:fallback = (0.5, 0.5, 0.5, 1) + asset inputs:file = @lines.png@ + float2 inputs:st.connect = + token inputs:wrapS = "repeat" + token inputs:wrapT = "repeat" + float3 outputs:rgb + + def Shader "TexCoordReader" + { + uniform token info:id = "UsdPrimvarReader_float2" + token inputs:varname.connect = + float2 outputs:result + } + } +} + +def Material "blinn3SG_uvSet1" ( + prepend specializes = +) +{ + token inputs:file5:varname = "uvSet1" + token not:mergeable:anymore = "becauseModified" +} + +def Material "blinn4SG" ( + kind = "assembly" +) +{ + token inputs:file6:varname = "st1" + token outputs:surface.connect = + + def Shader "blinn4" + { + uniform token info:id = "UsdPreviewSurface" + color3f inputs:diffuseColor.connect = + normal3f inputs:normal = (1, 1, 1) + float inputs:roughness = 0.3 + color3f inputs:specularColor = (0.35, 0.35, 0.35) + int inputs:useSpecularWorkflow = 1 + token outputs:displacement + token outputs:surface + } + + def Shader "file6" + { + uniform token info:id = "UsdUVTexture" + float4 inputs:fallback = (0.5, 0.5, 0.5, 1) + asset inputs:file = @lines.png@ + float2 inputs:st.connect = + token inputs:wrapS = "repeat" + token inputs:wrapT = "repeat" + float3 outputs:rgb + + def Shader "TexCoordReader" + { + uniform token info:id = "UsdPrimvarReader_float2" + token inputs:varname.connect = + float2 outputs:result + } + } +} + +def Material "blinn4SG_uvSet2" ( + prepend specializes = +) +{ + token inputs:file6:varname = "uvSet2" + def Shader "BlocksMerging" + { + uniform token info:id = "UsdPrimvarReader_float2" + } +} + diff --git a/test/lib/usd/translators/UsdImportUVSetMappingsTest/lines.png b/test/lib/usd/translators/UsdImportUVSetMappingsTest/lines.png new file mode 100644 index 0000000000..662d7acb5d Binary files /dev/null and b/test/lib/usd/translators/UsdImportUVSetMappingsTest/lines.png differ diff --git a/test/lib/usd/translators/testUsdImportUVSetMappings.py b/test/lib/usd/translators/testUsdImportUVSetMappings.py new file mode 100644 index 0000000000..4f4e2d183c --- /dev/null +++ b/test/lib/usd/translators/testUsdImportUVSetMappings.py @@ -0,0 +1,94 @@ +#!/pxrpythonsubst +# +# Copyright 2020 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from pxr import Usd +from pxr import UsdShade + +from maya import cmds +from maya import standalone + +import os +import unittest + +import fixturesUtils + + +class testUsdImportUVSetMappings(unittest.TestCase): + + @classmethod + def setUpClass(cls): + input_path = fixturesUtils.setUpClass(__file__) + + cls.test_dir = os.path.join(input_path, + "UsdImportUVSetMappingsTest") + + @classmethod + def tearDownClass(cls): + standalone.uninitialize() + + def testImportUVSetMappings(self): + ''' + Tests that importing complex UV set mappings work: + ''' + usd_path = os.path.join(self.test_dir, "UsdImportUVSetMappings.usda") + options = ["shadingMode=[[useRegistry,UsdPreviewSurface]]", + "primPath=/"] + cmds.file(usd_path, i=True, type="USD Import", + ignoreVersion=True, ra=True, mergeNamespacesOnClash=False, + namespace="Test", pr=True, importTimeRange="combine", + options=";".join(options)) + + # With merging working, we expect exactly these shading groups (since + # two of them were made unmergeable) + expected_sg = set(['initialParticleSE', + 'initialShadingGroup', + 'USD_Materials:blinn1SG', + 'USD_Materials:blinn2SG', + 'USD_Materials:blinn3SG', + 'USD_Materials:blinn3SG_uvSet1', + 'USD_Materials:blinn4SG', + 'USD_Materials:blinn4SG_uvSet2']) + + self.assertEqual(set(cmds.ls(type="shadingEngine")), expected_sg) + + expected_links = [ + ("file1", ['pPlane4Shape.uvSet[0].uvSetName', + 'pPlane3Shape.uvSet[0].uvSetName', + 'pPlane1Shape.uvSet[0].uvSetName', + 'pPlane2Shape.uvSet[0].uvSetName']), + ("file2", ['pPlane4Shape.uvSet[1].uvSetName', + 'pPlane3Shape.uvSet[1].uvSetName', + 'pPlane1Shape.uvSet[1].uvSetName', + 'pPlane2Shape.uvSet[1].uvSetName']), + ("file3", ['pPlane4Shape.uvSet[2].uvSetName', + 'pPlane3Shape.uvSet[2].uvSetName', + 'pPlane1Shape.uvSet[2].uvSetName', + 'pPlane2Shape.uvSet[2].uvSetName']), + ("file4", ['pPlane4Shape.uvSet[0].uvSetName', + 'pPlane2Shape.uvSet[0].uvSetName']), + ("file5", ['pPlane2Shape.uvSet[1].uvSetName',]), + ("file6", ['pPlane2Shape.uvSet[2].uvSetName',]), + ("file7", ['pPlane4Shape.uvSet[1].uvSetName',]), + ("file8", ['pPlane4Shape.uvSet[2].uvSetName',]), + ] + for file_name, links in expected_links: + links = set(links) + self.assertEqual(set(cmds.uvLink(texture=file_name)), links) + + +if __name__ == '__main__': + unittest.main(verbosity=2)