From 5f8278664111812be3b5ebbd20d63927e64513b4 Mon Sep 17 00:00:00 2001 From: Pierre Baillargeon Date: Thu, 3 Aug 2023 13:02:42 -0400 Subject: [PATCH] EMSUSD-280 import relative textures Refactor out the texture resolution code into a common reusable function: - Add a file system function to make a path absolute but relative to the project folder. - Add code to make the texture path relative to the project. - Use the common texture import code for materialX. - Split the texture handling into multiple helper functions to make each one clearer. - Warn if a USDZ archive is imported without the required corresponding flag to extract textures. - Properly detect absolute or relative path in automatic mode. Add import command flag: - Add the "importRelativeTextures" flag to the mayaUSDImport command. - Added a "none" relative textures mode for backward compatibility that leave all file names alone. - Made it the default for the command. - Add handling of the flag. - Add a "importRelativeTextures" option to the import job arguments class. - Add handling and validation of the option. - Wrap the option for Python. - Update the import command documentation. - Use the job args option in the import code. Add some unit tests. --- lib/mayaUsd/commands/Readme.md | 3 +- lib/mayaUsd/commands/baseImportCommand.cpp | 4 + lib/mayaUsd/commands/baseImportCommand.h | 1 + lib/mayaUsd/fileio/jobs/jobArgs.cpp | 12 + lib/mayaUsd/fileio/jobs/jobArgs.h | 7 + lib/mayaUsd/python/wrapPrimReader.cpp | 1 + lib/mayaUsd/utils/utilFileSystem.cpp | 31 +- lib/mayaUsd/utils/utilFileSystem.h | 11 + lib/usd/translators/shading/CMakeLists.txt | 1 + .../translators/shading/mtlxImageReader.cpp | 36 +- lib/usd/translators/shading/shadingAsset.cpp | 383 ++++++++++++++++++ lib/usd/translators/shading/shadingAsset.h | 38 ++ .../shading/usdUVTextureReader.cpp | 212 +--------- plugin/pxr/doc/README.md | 2 +- test/lib/usd/translators/CMakeLists.txt | 1 + .../UsdImportRelativeTextures.usda | 178 ++++++++ .../rel_project/workspace.mel | 72 ++++ .../standard_surface_gold.mtlx | 14 + .../standard_surface_jade.usda | 118 ++++++ .../testUsdImportRelativeTextures.py | 131 ++++++ 20 files changed, 1017 insertions(+), 239 deletions(-) create mode 100644 lib/usd/translators/shading/shadingAsset.cpp create mode 100644 lib/usd/translators/shading/shadingAsset.h create mode 100644 test/lib/usd/translators/UsdImportRelativeTextures/UsdImportRelativeTextures.usda create mode 100644 test/lib/usd/translators/UsdImportRelativeTextures/rel_project/workspace.mel create mode 100644 test/lib/usd/translators/UsdImportRelativeTextures/standard_surface_gold.mtlx create mode 100644 test/lib/usd/translators/UsdImportRelativeTextures/standard_surface_jade.usda create mode 100644 test/lib/usd/translators/testUsdImportRelativeTextures.py diff --git a/lib/mayaUsd/commands/Readme.md b/lib/mayaUsd/commands/Readme.md index 73fa320313..3eaf731bd1 100644 --- a/lib/mayaUsd/commands/Readme.md +++ b/lib/mayaUsd/commands/Readme.md @@ -48,7 +48,8 @@ Each base command class is documented in the following sections. | `-verbose` | `-v` | noarg | false | Make the command output more verbose. | | `-variant` | `-var` | string[2] | none | Set variant key value pairs | | `-importUSDZTextures` | `-itx` | bool | false | Imports textures from USDZ archives during import to disk. Can be used in conjuction with `-importUSDZTexturesFilePath` to specify an explicit directory to write imported textures to. If not specified, requires a Maya project to be set in the current context. | -| `-importUSDZTexturesFilePath` | `-itf` | string | none | Specifies an explicit directory to write imported textures to from a USDZ archive. Has no effect if `-importUSDZTextures` is not specified. +| `-importUSDZTexturesFilePath` | `-itf` | string | none | Specifies an explicit directory to write imported textures to from a USDZ archive. Has no effect if `-importUSDZTextures` is not specified. | +| `-importRelativeTextures` | `-rtx` | string | none | Selects how textures filenames are generated: absolute, relative, automatic or none. When automatic, the filename is relative if the source filename of the texture being imported is relative. When none, the file path is left alone, for backward compatible behavior. | ### Return Value diff --git a/lib/mayaUsd/commands/baseImportCommand.cpp b/lib/mayaUsd/commands/baseImportCommand.cpp index 548a31a256..fda256e113 100644 --- a/lib/mayaUsd/commands/baseImportCommand.cpp +++ b/lib/mayaUsd/commands/baseImportCommand.cpp @@ -64,6 +64,10 @@ MSyntax MayaUSDImportCommand::createSyntax() kImportUSDZTexturesFilePathFlag, UsdMayaJobImportArgsTokens->importUSDZTexturesFilePath.GetText(), MSyntax::kString); + syntax.addFlag( + kImportRelativeTexturesFlag, + UsdMayaJobImportArgsTokens->importRelativeTextures.GetText(), + MSyntax::kString); syntax.addFlag(kMetadataFlag, UsdMayaJobImportArgsTokens->metadata.GetText(), MSyntax::kString); syntax.makeFlagMultiUse(kMetadataFlag); syntax.addFlag( diff --git a/lib/mayaUsd/commands/baseImportCommand.h b/lib/mayaUsd/commands/baseImportCommand.h index 6f57cd3704..9316d8ff29 100644 --- a/lib/mayaUsd/commands/baseImportCommand.h +++ b/lib/mayaUsd/commands/baseImportCommand.h @@ -45,6 +45,7 @@ class MAYAUSD_CORE_PUBLIC MayaUSDImportCommand : public MPxCommand static constexpr auto kImportInstancesFlag = "ii"; static constexpr auto kImportUSDZTexturesFlag = "itx"; static constexpr auto kImportUSDZTexturesFilePathFlag = "itf"; + static constexpr auto kImportRelativeTexturesFlag = "rtx"; static constexpr auto kMetadataFlag = "md"; static constexpr auto kApiSchemaFlag = "api"; static constexpr auto kJobContextFlag = "jc"; diff --git a/lib/mayaUsd/fileio/jobs/jobArgs.cpp b/lib/mayaUsd/fileio/jobs/jobArgs.cpp index a01f6c1e01..af97ff39d9 100644 --- a/lib/mayaUsd/fileio/jobs/jobArgs.cpp +++ b/lib/mayaUsd/fileio/jobs/jobArgs.cpp @@ -1151,6 +1151,14 @@ UsdMayaJobImportArgs::UsdMayaJobImportArgs( UsdMayaPreferredMaterialTokens->allTokens)) , importUSDZTexturesFilePath(UsdMayaJobImportArgs::GetImportUSDZTexturesFilePath(userArgs)) , importUSDZTextures(extractBoolean(userArgs, UsdMayaJobImportArgsTokens->importUSDZTextures)) + , importRelativeTextures(extractToken( + userArgs, + UsdMayaJobImportArgsTokens->importRelativeTextures, + UsdMayaJobImportArgsTokens->none, + { UsdMayaJobImportArgsTokens->automatic, + UsdMayaJobImportArgsTokens->absolute, + UsdMayaJobImportArgsTokens->relative, + UsdMayaJobImportArgsTokens->none })) , importInstances(extractBoolean(userArgs, UsdMayaJobImportArgsTokens->importInstances)) , useAsAnimationCache(extractBoolean(userArgs, UsdMayaJobImportArgsTokens->useAsAnimationCache)) , importWithProxyShapes(importWithProxyShapes) @@ -1209,6 +1217,8 @@ const VtDictionary& UsdMayaJobImportArgs::GetDefaultDictionary() d[UsdMayaJobImportArgsTokens->importInstances] = true; d[UsdMayaJobImportArgsTokens->importUSDZTextures] = false; d[UsdMayaJobImportArgsTokens->importUSDZTexturesFilePath] = ""; + d[UsdMayaJobImportArgsTokens->importRelativeTextures] + = UsdMayaJobImportArgsTokens->none.GetString(); d[UsdMayaJobImportArgsTokens->pullImportStage] = UsdStageRefPtr(); d[UsdMayaJobImportArgsTokens->useAsAnimationCache] = false; d[UsdMayaJobImportArgsTokens->preserveTimeline] = false; @@ -1289,6 +1299,7 @@ const VtDictionary& UsdMayaJobImportArgs::GetGuideDictionary() d[UsdMayaJobImportArgsTokens->importInstances] = _boolean; d[UsdMayaJobImportArgsTokens->importUSDZTextures] = _boolean; d[UsdMayaJobImportArgsTokens->importUSDZTexturesFilePath] = _string; + d[UsdMayaJobImportArgsTokens->importRelativeTextures] = _string; d[UsdMayaJobImportArgsTokens->pullImportStage] = _usdStageRefPtr; d[UsdMayaJobImportArgsTokens->useAsAnimationCache] = _boolean; d[UsdMayaJobImportArgsTokens->preserveTimeline] = _boolean; @@ -1377,6 +1388,7 @@ std::ostream& operator<<(std::ostream& out, const UsdMayaJobImportArgs& importAr << "importInstances: " << TfStringify(importArgs.importInstances) << std::endl << "importUSDZTextures: " << TfStringify(importArgs.importUSDZTextures) << std::endl << "importUSDZTexturesFilePath: " << TfStringify(importArgs.importUSDZTexturesFilePath) + << "importRelativeTextures: " << TfStringify(importArgs.importRelativeTextures) << std::endl << "pullImportStage: " << TfStringify(importArgs.pullImportStage) << std::endl << std::endl << "timeInterval: " << importArgs.timeInterval << std::endl diff --git a/lib/mayaUsd/fileio/jobs/jobArgs.h b/lib/mayaUsd/fileio/jobs/jobArgs.h index 45c04c385c..75d8c2c2ad 100644 --- a/lib/mayaUsd/fileio/jobs/jobArgs.h +++ b/lib/mayaUsd/fileio/jobs/jobArgs.h @@ -155,8 +155,14 @@ TF_DECLARE_PUBLIC_TOKENS( (importInstances) \ (importUSDZTextures) \ (importUSDZTexturesFilePath) \ + (importRelativeTextures) \ (pullImportStage) \ (preserveTimeline) \ + /* values for import relative textures */ \ + (automatic) \ + (absolute) \ + (relative) \ + (none) \ /* assemblyRep values */ \ (Collapsed) \ (Full) \ @@ -348,6 +354,7 @@ struct UsdMayaJobImportArgs const TfToken preferredMaterial; const std::string importUSDZTexturesFilePath; const bool importUSDZTextures; + const std::string importRelativeTextures; const bool importInstances; const bool useAsAnimationCache; const bool importWithProxyShapes; diff --git a/lib/mayaUsd/python/wrapPrimReader.cpp b/lib/mayaUsd/python/wrapPrimReader.cpp index 66c0f6822d..49e2227acb 100644 --- a/lib/mayaUsd/python/wrapPrimReader.cpp +++ b/lib/mayaUsd/python/wrapPrimReader.cpp @@ -462,6 +462,7 @@ void wrapJobImportArgs() .def_readonly("importUSDZTextures", &UsdMayaJobImportArgs::importUSDZTextures) .def_readonly( "importUSDZTexturesFilePath", &UsdMayaJobImportArgs::importUSDZTexturesFilePath) + .def_readonly("importRelativeTextures", &UsdMayaJobImportArgs::importRelativeTextures) .def_readonly("importWithProxyShapes", &UsdMayaJobImportArgs::importWithProxyShapes) .add_property( "includeAPINames", diff --git a/lib/mayaUsd/utils/utilFileSystem.cpp b/lib/mayaUsd/utils/utilFileSystem.cpp index 3d61045ceb..2ad73b87a9 100644 --- a/lib/mayaUsd/utils/utilFileSystem.cpp +++ b/lib/mayaUsd/utils/utilFileSystem.cpp @@ -174,14 +174,20 @@ std::pair UsdMayaUtilFileSystem::makePathRelativeTo( return std::make_pair(relativePath.generic_string(), true); } +std::string UsdMayaUtilFileSystem::getProjectPath() +{ + MString projectPath = MGlobal::executeCommandStringResult("workspace -q -rd"); + return projectPath.asChar(); +} + std::string UsdMayaUtilFileSystem::getPathRelativeToProject(const std::string& fileName) { if (fileName.empty()) return {}; - MString projectPath = MGlobal::executeCommandStringResult("workspace -q -rd"); // Note: don't use isEmpty() because it is not available in Maya 2022 and earlier. - if (projectPath.length() == 0) + const std::string projectPath = getProjectPath(); + if (projectPath.empty()) return {}; // Note: we do *not* use filesystem function to attempt to make the @@ -191,11 +197,11 @@ std::string UsdMayaUtilFileSystem::getPathRelativeToProject(const std::string& f // preserve paths entered manually with relative folder ("..") // by keping an absolute path with ".." embedded in them, // so this works even in this situation. - const auto pos = fileName.find(projectPath.asChar()); + const auto pos = fileName.find(projectPath); if (pos != 0) return {}; - auto relativePathAndSuccess = makePathRelativeTo(fileName, projectPath.asChar()); + auto relativePathAndSuccess = makePathRelativeTo(fileName, projectPath); if (!relativePathAndSuccess.second) return {}; @@ -203,6 +209,23 @@ std::string UsdMayaUtilFileSystem::getPathRelativeToProject(const std::string& f return relativePathAndSuccess.first; } +std::string UsdMayaUtilFileSystem::makeProjectRelatedPath(const std::string& fileName) +{ + const std::string projectPath = UsdMayaUtilFileSystem::getProjectPath(); + if (projectPath.empty()) + return {}; + + // Attempt to create a relative path relative to the project folder. + // If that fails, we cannot create the project-relative path. + const auto pathAndSuccess = UsdMayaUtilFileSystem::makePathRelativeTo(fileName, projectPath); + if (!pathAndSuccess.second) + return {}; + + // Make the path absolute but relative to the project folder. That is an absolute + // path that starts with the project path. + return UsdMayaUtilFileSystem::appendPaths(projectPath, pathAndSuccess.first); +} + std::string UsdMayaUtilFileSystem::getPathRelativeToDirectory( const std::string& fileName, const std::string& relativeToDir) diff --git a/lib/mayaUsd/utils/utilFileSystem.h b/lib/mayaUsd/utils/utilFileSystem.h index c44ee9d63b..2bf02439e7 100644 --- a/lib/mayaUsd/utils/utilFileSystem.h +++ b/lib/mayaUsd/utils/utilFileSystem.h @@ -89,6 +89,17 @@ getPathRelativeToDirectory(const std::string& fileName, const std::string& relat MAYAUSD_CORE_PUBLIC std::string getPathRelativeToProject(const std::string& fileName); +/*! \brief returns the path to the Maya scene project folder. + */ +MAYAUSD_CORE_PUBLIC +std::string getProjectPath(); + +/*! \brief returns the absolute path of a file but relative to the Maya scene project folder. + Returns an empty string if the path cannot be made relative to the project. + */ +MAYAUSD_CORE_PUBLIC +std::string makeProjectRelatedPath(const std::string& fileName); + /*! \brief returns parent directory of a maya scene file opened by reference */ MAYAUSD_CORE_PUBLIC diff --git a/lib/usd/translators/shading/CMakeLists.txt b/lib/usd/translators/shading/CMakeLists.txt index 3424f4243e..71458354bf 100644 --- a/lib/usd/translators/shading/CMakeLists.txt +++ b/lib/usd/translators/shading/CMakeLists.txt @@ -3,6 +3,7 @@ # ----------------------------------------------------------------------------- target_sources(${TARGET_NAME} PRIVATE + shadingAsset.cpp shadingTokens.cpp usdBlinnReader.cpp usdBlinnWriter.cpp diff --git a/lib/usd/translators/shading/mtlxImageReader.cpp b/lib/usd/translators/shading/mtlxImageReader.cpp index 2833b8161a..725a9e5c42 100644 --- a/lib/usd/translators/shading/mtlxImageReader.cpp +++ b/lib/usd/translators/shading/mtlxImageReader.cpp @@ -14,6 +14,7 @@ // limitations under the License. // #include "mtlxBaseReader.h" +#include "shadingAsset.h" #include "shadingTokens.h" #include @@ -118,34 +119,8 @@ bool MtlxUsd_ImageReader::Read(UsdMayaPrimReaderContext& context) } // File - VtValue val; - MPlug mayaAttr = depFn.findPlug(TrMayaTokens->fileTextureName.GetText(), true, &status); - - UsdShadeInput usdInput = shaderSchema.GetInput(TrMtlxTokens->file); - if (status == MS::kSuccess && usdInput && usdInput.Get(&val) && val.IsHolding()) { - std::string filePath = val.UncheckedGet().GetResolvedPath(); - if (!filePath.empty() && !ArIsPackageRelativePath(filePath)) { - // Maya has issues with relative paths, especially if deep inside a - // nesting of referenced assets. Use absolute path instead if USD was - // able to resolve. A better fix will require providing an asset - // resolver to Maya that can resolve the file correctly using the - // MPxFileResolver API. We also make sure the path is not expressed - // as a relationship like texture paths inside USDZ assets. - val = SdfAssetPath(filePath); - } - - // NOTE: Will need UDIM support and potentially USDZ support. When that happens, consider - // refactoring existing code from usdUVTextureReader.cpp as shared utilities. - UsdMayaReadUtil::SetMayaAttr(mayaAttr, val); - - // colorSpace: - if (usdInput.GetAttr().HasColorSpace()) { - MString colorSpace = usdInput.GetAttr().GetColorSpace().GetText(); - mayaAttr = depFn.findPlug(TrMayaTokens->colorSpace.GetText(), true, &status); - if (status == MS::kSuccess) { - mayaAttr.setString(colorSpace); - } - } + if (!ResolveTextureAssetPath(prim, shaderSchema, depFn, _GetArgs().GetJobArguments())) { + return false; } // Default color @@ -158,8 +133,9 @@ bool MtlxUsd_ImageReader::Read(UsdMayaPrimReaderContext& context) } // Filter type: - mayaAttr = depFn.findPlug(TrMayaTokens->filterType.GetText(), true, &status); - usdInput = shaderSchema.GetInput(TrMtlxTokens->filtertype); + MPlug mayaAttr = depFn.findPlug(TrMayaTokens->filterType.GetText(), true, &status); + UsdShadeInput usdInput = shaderSchema.GetInput(TrMtlxTokens->filtertype); + VtValue val; if (status == MS::kSuccess && usdInput && usdInput.Get(&val) && val.IsHolding()) { std::string filterType = val.UncheckedGet(); if (filterType == TrMtlxTokens->closest.GetString()) { diff --git a/lib/usd/translators/shading/shadingAsset.cpp b/lib/usd/translators/shading/shadingAsset.cpp new file mode 100644 index 0000000000..f7f69b4cf9 --- /dev/null +++ b/lib/usd/translators/shading/shadingAsset.cpp @@ -0,0 +1,383 @@ +// +// Copyright 2023 Autodesk +// +// 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. +// + +#include "shadingAsset.h" + +#include "shadingTokens.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +static void +updateAssetPath(std::string assetPath, std::string resolvedPath, SdfAssetPath* resolvedAssetPath) +{ + assetPath = TfStringReplace(assetPath, "\\", "/"); + + if (resolvedPath.empty()) + resolvedPath = assetPath; + else + resolvedPath = TfStringReplace(resolvedPath, "\\", "/"); + + *resolvedAssetPath = SdfAssetPath(assetPath, resolvedPath); +} + +static SdfAssetPath handleShaderInput(const UsdShadeInput& usdInput) +{ + VtValue val; + if (!usdInput.Get(&val)) + return {}; + + if (!val.IsHolding()) + return {}; + + return val.UncheckedGet(); +} + +static bool handleMissingResolvedPath(SdfAssetPath* resolvedAssetPath) +{ + const std::string& filePath = resolvedAssetPath->GetResolvedPath(); + if (!filePath.empty() && !ArIsPackageRelativePath(filePath)) { + // Maya has issues with relative paths, especially if deep inside a + // nesting of referenced assets. Use absolute path instead if USD was + // able to resolve. A better fix will require providing an asset + // resolver to Maya that can resolve the file correctly using the + // MPxFileResolver API. We also make sure the path is not expressed + // as a relationship like texture paths inside USDZ assets. + *resolvedAssetPath = SdfAssetPath(filePath, filePath); + } + + return true; +} + +// Note: return false only on import errors. Currently always returns true. +static bool +handleUDIM(const UsdPrim& prim, MFnDependencyNode& depFn, SdfAssetPath* resolvedAssetPath) +{ + const std::string unresolvedFilePath = resolvedAssetPath->GetAssetPath(); + + std::string::size_type udimPos = unresolvedFilePath.rfind(TrMayaTokens->UDIMTag.GetString()); + if (udimPos == std::string::npos) + return true; + + MStatus status; + MPlug tilingAttr = depFn.findPlug(TrMayaTokens->uvTilingMode.GetText(), true, &status); + if (status != MS::kSuccess) + return true; + + tilingAttr.setInt(3); + + // USD did not resolve the path to absolute because the file name was not an + // actual file on disk. We need to find the first tile to help Maya find the + // other ones. + std::string udimPath(unresolvedFilePath.substr(0, udimPos)); + udimPath += "1001"; + udimPath += unresolvedFilePath.substr(udimPos + TrMayaTokens->UDIMTag.GetString().size()); + + Usd_Resolver res(&prim.GetPrimIndex()); + for (; res.IsValid(); res.NextLayer()) { + std::string resolvedName = SdfComputeAssetPathRelativeToLayer(res.GetLayer(), udimPath); + + if (!resolvedName.empty() && !ArIsPackageRelativePath(resolvedName) + && resolvedName != udimPath) { + udimPath = resolvedName; + break; + } + } + + const std::string absPath = resolvedAssetPath->GetResolvedPath(); + updateAssetPath(udimPath, absPath, resolvedAssetPath); + return true; +} + +// Note: return false only on import errors. +static bool handleUSDZTexture( + const UsdPrim& prim, + MFnDependencyNode& depFn, + const UsdMayaJobImportArgs& jobArgs, + SdfAssetPath* resolvedAssetPath) +{ + const std::string filePath = resolvedAssetPath->GetResolvedPath(); + + if (filePath.empty()) + return true; + + if (!ArIsPackageRelativePath(filePath)) + return true; + + if (!jobArgs.importUSDZTextures) { + TF_WARN( + "Imported USD file contains an USDZ archive but the importUSDZTextures flag is off."); + return true; + } + + if (jobArgs.importUSDZTexturesFilePath.empty()) { + TF_WARN("Imported USD file contains an USDZ archive but no importUSDZTexturesFilePath flag " + "were provided."); + return true; + } + + std::string unresolvedFilePath = resolvedAssetPath->GetAssetPath(); + + // NOTE: (yliangsiew) Package-relatve path means that we are inside of a USDZ file. + ArResolver& arResolver = ArGetResolver(); // NOTE: (yliangsiew) This is cached. + std::shared_ptr assetPtr = arResolver.OpenAsset(ArResolvedPath(filePath)); + if (assetPtr == nullptr) { + TF_WARN( + "The file: %s could not be found within the USDZ archive for extraction.", + filePath.c_str()); + return false; + } + + ArAsset* asset = assetPtr.get(); + std::shared_ptr fileData = asset->GetBuffer(); + const size_t fileSize = asset->GetSize(); + + bool needsUniqueFilename = false; + uint64_t spookyHash = ArchHash64(fileData.get(), fileSize); + std::unordered_map::iterator itExistingHash + = UsdMayaReadUtil::mapFileHashes.find(unresolvedFilePath); + if (itExistingHash + == UsdMayaReadUtil::mapFileHashes.end()) { // NOTE: (yliangsiew) Means that the + // texture hasn't been extracted before. + UsdMayaReadUtil::mapFileHashes.insert( + { unresolvedFilePath, + spookyHash }); // NOTE: (yliangsiew) This _should_ be the common case. + } else { + uint64_t existingHash = itExistingHash->second; + if (spookyHash == existingHash) { + TF_WARN( + "A duplicate texture: %s was found, skipping extraction of it and re-using " + "the existing one.", + unresolvedFilePath.c_str()); + unresolvedFilePath = itExistingHash->first; + } else { + // NOTE: (yliangsiew) Means that a duplicate texture with the same name but with + // different contents was found. Instead of failing, continue extraction with a + // different filename instead and point to that one. + needsUniqueFilename = true; + } + } + + // NOTE: (yliangsiew) Write the file to disk now. + std::string filename(unresolvedFilePath); + UsdMayaUtilFileSystem::pathStripPath(filename); + std::string extractedFilePath(jobArgs.importUSDZTexturesFilePath); + bool bStat = UsdMayaUtilFileSystem::pathAppendPath(extractedFilePath, filename); + TF_VERIFY(bStat); + + if (needsUniqueFilename) { + int counter = 0; + std::string checkPath(extractedFilePath); + while (ghc::filesystem::is_regular_file(checkPath)) { + checkPath.assign(extractedFilePath); + std::string filenameNoExt(checkPath); + std::string ext = UsdMayaUtilFileSystem::pathFindExtension(checkPath); + UsdMayaUtilFileSystem::pathRemoveExtension(checkPath); + checkPath = TfStringPrintf("%s_%d%s", checkPath.c_str(), counter, ext.c_str()); + ++counter; + } + extractedFilePath.assign(checkPath); + TF_WARN( + "A file was duplicated within the archive, but was unique in content. Writing " + "file with a suffix instead: %s", + extractedFilePath.c_str()); + } + + // NOTE: (yliangsiew) Check if the texture already exists on disk and skip overwriting + // it if necessary. This is because what happens if two USDZ files are imported, but + // they have textures with the same names in them? We can't overwrite them.... + // If the texture exists on disk already and it is has the same contents, however, we + // skip overwriting it. + bool needsWrite = true; + if (ghc::filesystem::is_regular_file(extractedFilePath)) { + FILE* pFile = fopen(extractedFilePath.c_str(), "rb"); + fseek(pFile, 0, SEEK_END); + long fileSize = ftell(pFile); + fseek(pFile, 0, SEEK_SET); + char* buf = (char*)malloc(sizeof(char) * fileSize); + fread(buf, fileSize, 1, pFile); + uint64_t existingSpookyHash = ArchHash64(buf, fileSize); + fclose(pFile); + free(buf); + if (spookyHash == existingSpookyHash) { + TF_WARN( + "The texture: %s already on disk is the same, skipping overwriting it.", + extractedFilePath.c_str()); + needsWrite = false; + } else { + int counter = 0; + std::string checkPath(extractedFilePath); + while (ghc::filesystem::is_regular_file(checkPath)) { + checkPath.assign(extractedFilePath); + std::string filenameNoExt(checkPath); + std::string ext = UsdMayaUtilFileSystem::pathFindExtension(checkPath); + UsdMayaUtilFileSystem::pathRemoveExtension(checkPath); + checkPath = TfStringPrintf("%s_%d%s", checkPath.c_str(), counter, ext.c_str()); + ++counter; + } + extractedFilePath.assign(checkPath); + TF_WARN( + "A duplicate file exists, but was unique in content. Writing a new" + "file with a suffix instead: %s", + extractedFilePath.c_str()); + } + } + + if (needsWrite) { + // TODO: (yliangsiew) Support undo/redo of mayaUSDImport command...though this might + // be too risky compared to just having the end-user delete the textures manually + // when needed. + size_t bytesWritten = UsdMayaUtilFileSystem::writeToFilePath( + extractedFilePath.c_str(), fileData.get(), fileSize); + if (bytesWritten != fileSize) { + TF_WARN( + "Failed to write out texture: %s to disk. Check that there is enough disk " + "space available", + extractedFilePath.c_str()); + return false; + } + } + + // NOTE: (yliangsiew) Continue setting the texture file node attribute to point to the + // new file that was written to disk. + updateAssetPath(extractedFilePath, extractedFilePath, resolvedAssetPath); + return true; +} + +static bool handleMakeRelative( + const UsdMayaJobImportArgs& jobArgs, + const SdfAssetPath& originalAssetPath, + SdfAssetPath* resolvedAssetPath) +{ + const std::string& relativeMode = jobArgs.importRelativeTextures; + if (relativeMode == UsdMayaJobImportArgsTokens->none) + return true; + + bool makeRelative = (relativeMode == UsdMayaJobImportArgsTokens->relative); + bool makeAbsolute = (relativeMode == UsdMayaJobImportArgsTokens->absolute); + + // When in automatic mode (neither relative nor absolute), select a mode based on + // the input texture filename. Maya always keeps paths as absolute paths internally, + // so we need to detect if the path is in the Maya project folders. + const bool isForced = (makeRelative || makeAbsolute); + if (!isForced) { + const std::string& fileName = originalAssetPath.GetAssetPath(); + const bool isAbs = ghc::filesystem::path(fileName).is_absolute(); + if (isAbs) { + makeAbsolute = true; + } else { + makeRelative = true; + } + } + + // Make the path relative to the project if requested. + if (makeAbsolute) { + std::error_code ec; + ghc::filesystem::path absolutePath + = ghc::filesystem::absolute(resolvedAssetPath->GetAssetPath(), ec); + if (!ec && !absolutePath.empty()) { + const std::string absPath = absolutePath.generic_string(); + updateAssetPath(absPath, absPath, resolvedAssetPath); + } + } else if (makeRelative) { + std::string absPath = resolvedAssetPath->GetResolvedPath(); + if (absPath.empty()) + absPath = resolvedAssetPath->GetAssetPath(); + const std::string relToProject = UsdMayaUtilFileSystem::makeProjectRelatedPath(absPath); + if (!relToProject.empty()) { + updateAssetPath(relToProject, absPath, resolvedAssetPath); + } else { + TF_WARN("Could not make texture file path relative for [%s].", absPath.c_str()); + } + } + + return true; +} + +bool ResolveTextureAssetPath( + const UsdPrim& prim, + const UsdShadeShader& shaderSchema, + MFnDependencyNode& depFn, + const UsdMayaJobImportArgs& jobArgs) +{ + // Note: not having a shader input is not an error. + UsdShadeInput usdInput = shaderSchema.GetInput(TrUsdTokens->file); + if (!usdInput) + return true; + + const SdfAssetPath originalAssetPath = handleShaderInput(usdInput); + if (originalAssetPath == SdfAssetPath()) + return true; + + SdfAssetPath resolvedAssetPath = originalAssetPath; + + if (!handleMissingResolvedPath(&resolvedAssetPath)) + return false; + + // Handle UDIM texture files: + if (!handleUDIM(prim, depFn, &resolvedAssetPath)) + return false; + + if (!handleUSDZTexture(prim, depFn, jobArgs, &resolvedAssetPath)) + return false; + + if (!handleMakeRelative(jobArgs, originalAssetPath, &resolvedAssetPath)) + return false; + + if (resolvedAssetPath != SdfAssetPath()) { + MStatus status; + MPlug mayaAttr = depFn.findPlug(TrMayaTokens->fileTextureName.GetText(), true, &status); + if (status != MS::kSuccess) { + TF_RUNTIME_ERROR( + "Could not find the built-in attribute fileTextureName on a Maya file node: " + "%s! " + "Something is seriously wrong with your current Maya session.", + depFn.name().asChar()); + return false; + } + UsdMayaReadUtil::SetMayaAttr(mayaAttr, VtValue(resolvedAssetPath)); + } + + // colorSpace: + if (usdInput.GetAttr().HasColorSpace()) { + MStatus status; + MString colorSpace = usdInput.GetAttr().GetColorSpace().GetText(); + MPlug mayaAttr = depFn.findPlug(TrMayaTokens->colorSpace.GetText(), true, &status); + if (status == MS::kSuccess) { + mayaAttr.setString(colorSpace); + } + } + + return true; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/usd/translators/shading/shadingAsset.h b/lib/usd/translators/shading/shadingAsset.h new file mode 100644 index 0000000000..48f0a015ea --- /dev/null +++ b/lib/usd/translators/shading/shadingAsset.h @@ -0,0 +1,38 @@ +// +// Copyright 2023 Autodesk +// +// 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. +// +#ifndef MTLXUSDTRANSLATORS_SHADING_ASSET_H +#define MTLXUSDTRANSLATORS_SHADING_ASSET_H + +#include + +#include +#include +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// Resolve an asset (for example a texture) to be imported into Maya. +bool ResolveTextureAssetPath( + const UsdPrim& prim, + const UsdShadeShader& shaderSchema, + MFnDependencyNode& depFn, + const UsdMayaJobImportArgs& jobArgs); + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // MTLXUSDTRANSLATORS_SHADING_ASSET_H diff --git a/lib/usd/translators/shading/usdUVTextureReader.cpp b/lib/usd/translators/shading/usdUVTextureReader.cpp index ba5a2cabee..604660caf3 100644 --- a/lib/usd/translators/shading/usdUVTextureReader.cpp +++ b/lib/usd/translators/shading/usdUVTextureReader.cpp @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // +#include "shadingAsset.h" #include "shadingTokens.h" #include @@ -31,14 +32,10 @@ #include #include #include -#include -#include -#include #include #include #include #include -#include #include #include #include @@ -50,8 +47,6 @@ #include #include -#include - PXR_NAMESPACE_OPEN_SCOPE class PxrMayaUsdUVTexture_Reader : public UsdMayaShaderReader @@ -104,207 +99,18 @@ bool PxrMayaUsdUVTexture_Reader::Read(UsdMayaPrimReaderContext& context) MObject uvObj = UsdMayaShadingUtil::CreatePlace2dTextureAndConnectTexture(mayaObject); MFnDependencyNode uvDepFn(uvObj); - VtValue val; - MPlug mayaAttr; - UsdShadeInput usdInput = shaderSchema.GetInput(TrUsdTokens->file); - if (usdInput && usdInput.Get(&val) && val.IsHolding()) { - std::string filePath = val.UncheckedGet().GetResolvedPath(); - if (!filePath.empty() && !ArIsPackageRelativePath(filePath)) { - // Maya has issues with relative paths, especially if deep inside a - // nesting of referenced assets. Use absolute path instead if USD was - // able to resolve. A better fix will require providing an asset - // resolver to Maya that can resolve the file correctly using the - // MPxFileResolver API. We also make sure the path is not expressed - // as a relationship like texture paths inside USDZ assets. - val = SdfAssetPath(filePath); - } - // Re-fetch the file name in case it is UDIM-tagged - std::string unresolvedFilePath = val.UncheckedGet().GetAssetPath(); - mayaAttr = depFn.findPlug(TrMayaTokens->fileTextureName.GetText(), true, &status); - if (status != MS::kSuccess) { - TF_RUNTIME_ERROR( - "Could not find the built-in attribute fileTextureName on a Maya file node: %s! " - "Something is seriously wrong with your current Maya session.", - depFn.name().asChar()); - return false; - } - // Handle UDIM texture files: - std::string::size_type udimPos - = unresolvedFilePath.rfind(TrMayaTokens->UDIMTag.GetString()); - if (udimPos != std::string::npos) { - MPlug tilingAttr = depFn.findPlug(TrMayaTokens->uvTilingMode.GetText(), true, &status); - if (status == MS::kSuccess) { - tilingAttr.setInt(3); - - // USD did not resolve the path to absolute because the file name was not an - // actual file on disk. We need to find the first tile to help Maya find the - // other ones. - std::string udimPath(unresolvedFilePath.substr(0, udimPos)); - udimPath += "1001"; - udimPath += unresolvedFilePath.substr( - udimPos + TrMayaTokens->UDIMTag.GetString().size()); - - Usd_Resolver res(&prim.GetPrimIndex()); - for (; res.IsValid(); res.NextLayer()) { - std::string resolvedName - = SdfComputeAssetPathRelativeToLayer(res.GetLayer(), udimPath); - - if (!resolvedName.empty() && !ArIsPackageRelativePath(resolvedName) - && resolvedName != udimPath) { - udimPath = resolvedName; - break; - } - } - val = SdfAssetPath(udimPath); - } - } - - const UsdMayaJobImportArgs& jobArgs = this->_GetArgs().GetJobArguments(); - if (jobArgs.importUSDZTextures && !jobArgs.importUSDZTexturesFilePath.empty() - && !filePath.empty() && ArIsPackageRelativePath(filePath)) { - // NOTE: (yliangsiew) Package-relatve path means that we are inside of a USDZ file. - ArResolver& arResolver = ArGetResolver(); // NOTE: (yliangsiew) This is cached. - std::shared_ptr assetPtr = arResolver.OpenAsset(ArResolvedPath(filePath)); - if (assetPtr == nullptr) { - TF_WARN( - "The file: %s could not be found within the USDZ archive for extraction.", - filePath.c_str()); - return false; - } - - ArAsset* asset = assetPtr.get(); - std::shared_ptr fileData = asset->GetBuffer(); - const size_t fileSize = asset->GetSize(); - - bool needsUniqueFilename = false; - uint64_t spookyHash = ArchHash64(fileData.get(), fileSize); - std::unordered_map::iterator itExistingHash - = UsdMayaReadUtil::mapFileHashes.find(unresolvedFilePath); - if (itExistingHash - == UsdMayaReadUtil::mapFileHashes.end()) { // NOTE: (yliangsiew) Means that the - // texture hasn't been extracted before. - UsdMayaReadUtil::mapFileHashes.insert( - { unresolvedFilePath, - spookyHash }); // NOTE: (yliangsiew) This _should_ be the common case. - } else { - uint64_t existingHash = itExistingHash->second; - if (spookyHash == existingHash) { - TF_WARN( - "A duplicate texture: %s was found, skipping extraction of it and re-using " - "the existing one.", - unresolvedFilePath.c_str()); - unresolvedFilePath = itExistingHash->first; - } else { - // NOTE: (yliangsiew) Means that a duplicate texture with the same name but with - // different contents was found. Instead of failing, continue extraction with a - // different filename instead and point to that one. - needsUniqueFilename = true; - } - } - - // NOTE: (yliangsiew) Write the file to disk now. - std::string filename(unresolvedFilePath); - UsdMayaUtilFileSystem::pathStripPath(filename); - std::string extractedFilePath(jobArgs.importUSDZTexturesFilePath); - bool bStat = UsdMayaUtilFileSystem::pathAppendPath(extractedFilePath, filename); - TF_VERIFY(bStat); - - if (needsUniqueFilename) { - int counter = 0; - std::string checkPath(extractedFilePath); - while (ghc::filesystem::is_regular_file(checkPath)) { - checkPath.assign(extractedFilePath); - std::string filenameNoExt(checkPath); - std::string ext = UsdMayaUtilFileSystem::pathFindExtension(checkPath); - UsdMayaUtilFileSystem::pathRemoveExtension(checkPath); - checkPath = TfStringPrintf("%s_%d%s", checkPath.c_str(), counter, ext.c_str()); - ++counter; - } - extractedFilePath.assign(checkPath); - TF_WARN( - "A file was duplicated within the archive, but was unique in content. Writing " - "file with a suffix instead: %s", - extractedFilePath.c_str()); - } - - // NOTE: (yliangsiew) Check if the texture already exists on disk and skip overwriting - // it if necessary. This is because what happens if two USDZ files are imported, but - // they have textures with the same names in them? We can't overwrite them.... - // If the texture exists on disk already and it is has the same contents, however, we - // skip overwriting it. - bool needsWrite = true; - if (ghc::filesystem::is_regular_file(extractedFilePath)) { - FILE* pFile = fopen(extractedFilePath.c_str(), "rb"); - fseek(pFile, 0, SEEK_END); - long fileSize = ftell(pFile); - fseek(pFile, 0, SEEK_SET); - char* buf = (char*)malloc(sizeof(char) * fileSize); - fread(buf, fileSize, 1, pFile); - uint64_t existingSpookyHash = ArchHash64(buf, fileSize); - fclose(pFile); - free(buf); - if (spookyHash == existingSpookyHash) { - TF_WARN( - "The texture: %s already on disk is the same, skipping overwriting it.", - extractedFilePath.c_str()); - needsWrite = false; - } else { - int counter = 0; - std::string checkPath(extractedFilePath); - while (ghc::filesystem::is_regular_file(checkPath)) { - checkPath.assign(extractedFilePath); - std::string filenameNoExt(checkPath); - std::string ext = UsdMayaUtilFileSystem::pathFindExtension(checkPath); - UsdMayaUtilFileSystem::pathRemoveExtension(checkPath); - checkPath - = TfStringPrintf("%s_%d%s", checkPath.c_str(), counter, ext.c_str()); - ++counter; - } - extractedFilePath.assign(checkPath); - TF_WARN( - "A duplicate file exists, but was unique in content. Writing a new" - "file with a suffix instead: %s", - extractedFilePath.c_str()); - } - } - if (needsWrite) { - // TODO: (yliangsiew) Support undo/redo of mayaUSDImport command...though this might - // be too risky compared to just having the end-user delete the textures manually - // when needed. - size_t bytesWritten = UsdMayaUtilFileSystem::writeToFilePath( - extractedFilePath.c_str(), fileData.get(), fileSize); - if (bytesWritten != fileSize) { - TF_WARN( - "Failed to write out texture: %s to disk. Check that there is enough disk " - "space available", - extractedFilePath.c_str()); - return false; - } - } - - // NOTE: (yliangsiew) Continue setting the texture file node attribute to point to the - // new file that was written to disk. - val = SdfAssetPath(extractedFilePath); - } - UsdMayaReadUtil::SetMayaAttr(mayaAttr, val); - - // colorSpace: - if (usdInput.GetAttr().HasColorSpace()) { - MString colorSpace = usdInput.GetAttr().GetColorSpace().GetText(); - mayaAttr = depFn.findPlug(TrMayaTokens->colorSpace.GetText(), true, &status); - if (status == MS::kSuccess) { - mayaAttr.setString(colorSpace); - } - } + if (!ResolveTextureAssetPath(prim, shaderSchema, depFn, _GetArgs().GetJobArguments())) { + return false; } // The Maya file node's 'colorGain' and 'alphaGain' attributes map to the // UsdUVTexture's scale input. - usdInput = shaderSchema.GetInput(TrUsdTokens->scale); + VtValue val; + UsdShadeInput usdInput = shaderSchema.GetInput(TrUsdTokens->scale); if (usdInput) { if (usdInput.Get(&val) && val.IsHolding()) { GfVec4f scale = val.UncheckedGet(); - mayaAttr = depFn.findPlug(TrMayaTokens->colorGain.GetText(), true, &status); + MPlug mayaAttr = depFn.findPlug(TrMayaTokens->colorGain.GetText(), true, &status); if (status == MS::kSuccess) { val = GfVec3f(scale[0], scale[1], scale[2]); UsdMayaReadUtil::SetMayaAttr(mayaAttr, val, /*unlinearizeColors*/ false); @@ -323,7 +129,7 @@ bool PxrMayaUsdUVTexture_Reader::Read(UsdMayaPrimReaderContext& context) if (usdInput) { if (usdInput.Get(&val) && val.IsHolding()) { GfVec4f bias = val.UncheckedGet(); - mayaAttr = depFn.findPlug(TrMayaTokens->colorOffset.GetText(), true, &status); + MPlug mayaAttr = depFn.findPlug(TrMayaTokens->colorOffset.GetText(), true, &status); if (status == MS::kSuccess) { val = GfVec3f(bias[0], bias[1], bias[2]); UsdMayaReadUtil::SetMayaAttr(mayaAttr, val, /*unlinearizeColors*/ false); @@ -339,7 +145,7 @@ bool PxrMayaUsdUVTexture_Reader::Read(UsdMayaPrimReaderContext& context) // Default color usdInput = shaderSchema.GetInput(TrUsdTokens->fallback); - mayaAttr = depFn.findPlug(TrMayaTokens->defaultColor.GetText(), true, &status); + MPlug mayaAttr = depFn.findPlug(TrMayaTokens->defaultColor.GetText(), true, &status); if (usdInput && status == MS::kSuccess) { if (usdInput.Get(&val) && val.IsHolding()) { GfVec4f fallback = val.UncheckedGet(); @@ -374,7 +180,7 @@ bool PxrMayaUsdUVTexture_Reader::Read(UsdMayaPrimReaderContext& context) plugName = wrapUVToken; val = false; } - mayaAttr = uvDepFn.findPlug(plugName.GetText(), true, &status); + MPlug mayaAttr = uvDepFn.findPlug(plugName.GetText(), true, &status); if (status != MS::kSuccess) { continue; } diff --git a/plugin/pxr/doc/README.md b/plugin/pxr/doc/README.md index bd52c3396f..c66ed14679 100644 --- a/plugin/pxr/doc/README.md +++ b/plugin/pxr/doc/README.md @@ -47,7 +47,7 @@ The plugin creates two commands: `usdImport` and `usdExport`, and will also regi | `-var` | `-variant` | string[2] | none | Set variant key value pairs | | `-itx` | `-importUSDZTextures` | bool | false | Imports textures from USDZ archives during import to disk. Can be used in conjuction with `-importUSDZTexturesFilePath` to specify an explicit directory to write imported textures to. If not specified, requires a Maya project to be set in the current context. | | `-itf` | `-importUSDZTexturesFilePath` | string | none | Specifies an explicit directory to write imported textures to from a USDZ archive. Has no effect if `-importUSDZTextures` is not specified. - +| `-importRelativeTextures` | `-rtx` | string | none | Selects how textures filenames are generated: absolute, relative, automatic or none. When automatic, the filename is relative if the source filename of the texture being imported is relative. When none, the file path is left alone, for backward compatible behavior. | ### Return Value `usdImport` will return an array containing the fullDagPath of the highest prim(s) imported. This is generally the fullDagPath that corresponds to the imported primPath but could be multiple paths if primPath="/". diff --git a/test/lib/usd/translators/CMakeLists.txt b/test/lib/usd/translators/CMakeLists.txt index dcd4d26bac..36a98c7541 100644 --- a/test/lib/usd/translators/CMakeLists.txt +++ b/test/lib/usd/translators/CMakeLists.txt @@ -72,6 +72,7 @@ set(TEST_SCRIPT_FILES testUsdImportMesh.py testUsdImportPointCache.py testUsdImportPreviewSurface.py + testUsdImportRelativeTextures.py # XXX: This test is disabled by default since it requires the RenderMan for Maya plugin. # testUsdImportRfMLight.py diff --git a/test/lib/usd/translators/UsdImportRelativeTextures/UsdImportRelativeTextures.usda b/test/lib/usd/translators/UsdImportRelativeTextures/UsdImportRelativeTextures.usda new file mode 100644 index 0000000000..a531f01fd9 --- /dev/null +++ b/test/lib/usd/translators/UsdImportRelativeTextures/UsdImportRelativeTextures.usda @@ -0,0 +1,178 @@ +#usda 1.0 +( + defaultPrim = "pPlatonic1" + metersPerUnit = 0.01 + upAxis = "Y" +) + +def Mesh "pPlatonic1" ( + prepend apiSchemas = ["MaterialBindingAPI"] + kind = "component" +) +{ + uniform bool doubleSided = 1 + float3[] extent = [(-3.928988, -3.9177284, -4), (3.928988, 3.9177284, 4)] + int[] faceVertexCounts = [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3] + int[] faceVertexIndices = [0, 12, 14, 12, 13, 18, 12, 18, 14, 14, 18, 15, 13, 2, 16, 13, 16, 18, 18, 16, 17, 18, 17, 15, 15, 17, 1, 0, 19, 12, 19, 20, 23, 19, 23, 12, 12, 23, 13, 20, 3, 21, 20, 21, 23, 23, 21, 22, 23, 22, 13, 13, 22, 2, 0, 24, 19, 24, 25, 28, 24, 28, 19, 19, 28, 20, 25, 4, 26, 25, 26, 28, 28, 26, 27, 28, 27, 20, 20, 27, 3, 0, 29, 24, 29, 30, 33, 29, 33, 24, 24, 33, 25, 30, 5, 31, 30, 31, 33, 33, 31, 32, 33, 32, 25, 25, 32, 4, 0, 14, 29, 14, 15, 36, 14, 36, 29, 29, 36, 30, 15, 1, 34, 15, 34, 36, 36, 34, 35, 36, 35, 30, 30, 35, 5, 1, 17, 37, 17, 16, 41, 17, 41, 37, 37, 41, 38, 16, 2, 39, 16, 39, 41, 41, 39, 40, 41, 40, 38, 38, 40, 7, 2, 22, 42, 22, 21, 46, 22, 46, 42, 42, 46, 43, 21, 3, 44, 21, 44, 46, 46, 44, 45, 46, 45, 43, 43, 45, 8, 3, 27, 47, 27, 26, 51, 27, 51, 47, 47, 51, 48, 26, 4, 49, 26, 49, 51, 51, 49, 50, 51, 50, 48, 48, 50, 9, 4, 32, 52, 32, 31, 56, 32, 56, 52, 52, 56, 53, 31, 5, 54, 31, 54, 56, 56, 54, 55, 56, 55, 53, 53, 55, 10, 5, 35, 57, 35, 34, 61, 35, 61, 57, 57, 61, 58, 34, 1, 59, 34, 59, 61, 61, 59, 60, 61, 60, 58, 58, 60, 6, 1, 37, 59, 37, 38, 64, 37, 64, 59, 59, 64, 60, 38, 7, 62, 38, 62, 64, 64, 62, 63, 64, 63, 60, 60, 63, 6, 2, 42, 39, 42, 43, 67, 42, 67, 39, 39, 67, 40, 43, 8, 65, 43, 65, 67, 67, 65, 66, 67, 66, 40, 40, 66, 7, 3, 47, 44, 47, 48, 70, 47, 70, 44, 44, 70, 45, 48, 9, 68, 48, 68, 70, 70, 68, 69, 70, 69, 45, 45, 69, 8, 4, 52, 49, 52, 53, 73, 52, 73, 49, 49, 73, 50, 53, 10, 71, 53, 71, 73, 73, 71, 72, 73, 72, 50, 50, 72, 9, 5, 57, 54, 57, 58, 76, 57, 76, 54, 54, 76, 55, 58, 6, 74, 58, 74, 76, 76, 74, 75, 76, 75, 55, 55, 75, 10, 6, 63, 77, 63, 62, 81, 63, 81, 77, 77, 81, 78, 62, 7, 79, 62, 79, 81, 81, 79, 80, 81, 80, 78, 78, 80, 11, 7, 66, 79, 66, 65, 84, 66, 84, 79, 79, 84, 80, 65, 8, 82, 65, 82, 84, 84, 82, 83, 84, 83, 80, 80, 83, 11, 8, 69, 82, 69, 68, 87, 69, 87, 82, 82, 87, 83, 68, 9, 85, 68, 85, 87, 87, 85, 86, 87, 86, 83, 83, 86, 11, 9, 72, 85, 72, 71, 90, 72, 90, 85, 85, 90, 86, 71, 10, 88, 71, 88, 90, 90, 88, 89, 90, 89, 86, 86, 89, 11, 10, 75, 88, 75, 74, 91, 75, 91, 88, 88, 91, 89, 74, 6, 77, 74, 77, 91, 91, 77, 78, 91, 78, 89, 89, 78, 11] + rel material:binding = + point3f[] points = [(0, 0, -4), (2.8944273, 2.1029243, -1.7888544), (-1.105573, 3.4026036, -1.7888546), (-3.5777092, -3.1277327e-7, -1.7888546), (-1.1055732, -3.4026031, -1.7888544), (2.894427, -2.1029246, -1.7888544), (3.5777092, 0, 1.7888546), (1.1055727, 3.4026034, 1.7888544), (-2.8944275, 2.1029243, 1.7888546), (-2.894427, -2.1029248, 1.7888544), (1.1055733, -3.4026031, 1.7888544), (0, 0, 4), (-0.42431572, 1.3059093, -3.7569344), (-0.84863144, 2.6118186, -2.908303), (1.110873, 0.80709636, -3.7569344), (2.221746, 1.6141927, -2.908303), (0.26224142, 3.4189153, -2.0596716), (1.7974302, 2.9201021, -2.0596716), (0.7503699, 2.309401, -3.178618), (-1.3731143, -1.2004148e-7, -3.7569344), (-2.7462287, -2.4008295e-7, -2.908303), (-3.1705449, 1.305909, -2.0596716), (-2.221746, 2.6118186, -2.0596716), (-1.964494, 1.4272882, -3.178618), (-0.42431584, -1.3059093, -3.7569344), (-0.8486317, -2.6118186, -2.908303), (-2.221746, -2.6118186, -2.0596714), (-3.1705446, -1.3059098, -2.0596716), (-1.964494, -1.4272884, -3.178618), (1.1108729, -0.8070965, -3.7569344), (2.2217457, -1.614193, -2.908303), (1.7974302, -2.9201024, -2.0596716), (0.2622413, -3.4189153, -2.0596716), (0.75036967, -2.309401, -3.178618), (3.3326187, 0.80709636, -2.0596714), (3.3326187, -0.8070965, -2.0596716), (2.428248, -7.5007e-8, -3.178618), (2.6460614, 2.9201021, -0.68655723), (1.9595041, 3.418915, 0.68655705), (-0.42431587, 3.9177284, -0.68655735), (0.42431557, 3.9177284, 0.6865571), (1.2141238, 3.7366893, -0.75036997), (-1.9595045, 3.4189153, -0.68655735), (-2.6460617, 2.9201021, 0.6865571), (-3.8571017, 0.80709594, -0.68655723), (-3.5948603, 1.6141924, 0.6865571), (-3.1786182, 2.3094008, -0.75037), (-3.8571014, -0.80709666, -0.68655723), (-3.5948598, -1.614193, 0.68655705), (-1.9595044, -3.418915, -0.68655723), (-2.6460612, -2.9201024, 0.68655705), (-3.178618, -2.309401, -0.75036997), (-0.42431587, -3.9177284, -0.68655735), (0.4243159, -3.9177284, 0.6865572), (2.6460614, -2.9201021, -0.68655723), (1.9595044, -3.4189148, 0.68655705), (1.2141242, -3.7366896, -0.75037), (3.5948603, -1.6141931, -0.68655735), (3.857102, -0.8070966, 0.6865572), (3.5948603, 1.614193, -0.68655735), (3.857102, 0.8070965, 0.6865571), (3.928988, -3.75035e-8, -0.75037), (2.2217455, 2.611819, 2.0596714), (3.1705446, 1.3059096, 2.0596716), (3.178618, 2.3094013, 0.75036985), (-1.7974304, 2.9201021, 2.0596716), (-0.2622417, 3.4189153, 2.0596716), (-1.2141242, 3.7366896, 0.75036985), (-3.3326187, -0.80709684, 2.0596714), (-3.3326187, 0.8070958, 2.0596714), (-3.928988, -4.5004202e-7, 0.75036985), (-0.26224098, -3.418915, 2.0596714), (-1.7974297, -2.9201026, 2.0596716), (-1.2141238, -3.7366898, 0.75036985), (3.1705444, -1.3059092, 2.0596714), (2.2217462, -2.6118186, 2.0596716), (3.178618, -2.3094013, 0.75036985), (2.746229, 0, 2.908303), (1.3731145, 0, 3.7569344), (0.8486313, 2.611819, 2.9083028), (0.4243157, 1.3059096, 3.7569344), (1.964494, 1.4272885, 3.178618), (-2.221746, 1.6141926, 2.908303), (-1.110873, 0.8070963, 3.7569344), (-0.75037, 2.309401, 3.178618), (-2.2217457, -1.614193, 2.9083028), (-1.110873, -0.8070966, 3.7569344), (-2.4282484, -3.375315e-7, 3.178618), (0.8486318, -2.611819, 2.908303), (0.4243159, -1.3059095, 3.7569344), (-0.75036967, -2.3094013, 3.178618), (1.9644942, -1.4272884, 3.178618)] + texCoord2f[] primvars:map1 = [(1, 0.5), (0.83810407, 0.67620814), (0.088104114, 0.8237918), (0.1762082, 0.5), (0.088104114, 0.17620823), (0.8381041, 0.32379177), (0.6762082, 0.5), (0.58810407, 0.82379186), (0.3381041, 0.67620814), (0.3381041, 0.32379177), (0.5881041, 0.17620823), (0.5, 0.5), (0.017899446, 0.6058617), (0.045186203, 0.7264708), (0.95424384, 0.5646707), (0.89617395, 0.63222325), (0.97984457, 0.8262766), (0.8858042, 0.7604919), (0.96310407, 0.69591326), (0.055768747, 0.5), (0.12043945, 0.5), (0.15830879, 0.60586166), (0.13102199, 0.7264708), (0.088104114, 0.61613977), (0.017899446, 0.3941383), (0.04518624, 0.27352917), (0.13102199, 0.27352917), (0.15830879, 0.39413828), (0.088104114, 0.38386026), (0.95424384, 0.43532932), (0.89617395, 0.36777675), (0.8858042, 0.23950814), (0.97984457, 0.1737234), (0.96310407, 0.30408674), (0.83810407, 0.5646707), (0.83810407, 0.4353293), (0.89617395, 0.5), (0.79040396, 0.7604919), (0.6963637, 0.82627666), (0.088104114, 0.9353294), (0.58810407, 0.9353294), (0.8381041, 0.8838603), (0.19636367, 0.8262766), (0.290404, 0.76049185), (0.22196433, 0.5646706), (0.2800342, 0.6322232), (0.21310408, 0.69591326), (0.22196433, 0.4353293), (0.2800342, 0.36777675), (0.19636367, 0.1737234), (0.290404, 0.23950806), (0.21310408, 0.30408674), (0.088104114, 0.06467063), (0.5881041, 0.06467063), (0.79040396, 0.23950814), (0.6963637, 0.17372333), (0.83810407, 0.11613975), (0.78003424, 0.36777675), (0.72196436, 0.4353293), (0.78003424, 0.63222325), (0.72196436, 0.5646707), (0.7800342, 0.5), (0.631022, 0.7264709), (0.65830874, 0.6058617), (0.7131041, 0.6959133), (0.38580418, 0.76049185), (0.4798445, 0.8262766), (0.33810407, 0.8838603), (0.33810407, 0.4353293), (0.33810407, 0.5646706), (0.2800342, 0.49999994), (0.47984457, 0.17372333), (0.3858042, 0.23950806), (0.3381041, 0.11613967), (0.65830874, 0.3941383), (0.631022, 0.27352923), (0.7131041, 0.3040867), (0.6204394, 0.5), (0.5557687, 0.5), (0.5451862, 0.7264709), (0.51789945, 0.6058617), (0.5881041, 0.61613977), (0.39617398, 0.63222325), (0.45424387, 0.5646707), (0.4631041, 0.69591326), (0.39617398, 0.36777675), (0.45424387, 0.4353293), (0.39617395, 0.5), (0.5451862, 0.27352917), (0.51789945, 0.3941383), (0.4631041, 0.3040867), (0.5881041, 0.38386026), (1.0178994, 0.6058617), (1.0451862, 0.7264708), (1.0881041, 0.8237918), (1.0557687, 0.5), (1.0178994, 0.3941383), (1.0451863, 0.27352917), (1.0881041, 0.17620823), (1.0881041, 0.9353294), (1.0881041, 0.06467063)] ( + interpolation = "faceVarying" + ) + int[] primvars:map1:indices = [0, 92, 14, 92, 93, 18, 92, 18, 14, 14, 18, 15, 93, 94, 16, 93, 16, 18, 18, 16, 17, 18, 17, 15, 15, 17, 1, 0, 95, 92, 19, 20, 23, 19, 23, 12, 12, 23, 13, 20, 3, 21, 20, 21, 23, 23, 21, 22, 23, 22, 13, 13, 22, 2, 0, 96, 95, 24, 25, 28, 24, 28, 19, 19, 28, 20, 25, 4, 26, 25, 26, 28, 28, 26, 27, 28, 27, 20, 20, 27, 3, 0, 29, 96, 29, 30, 33, 29, 33, 96, 96, 33, 97, 30, 5, 31, 30, 31, 33, 33, 31, 32, 33, 32, 97, 97, 32, 98, 0, 14, 29, 14, 15, 36, 14, 36, 29, 29, 36, 30, 15, 1, 34, 15, 34, 36, 36, 34, 35, 36, 35, 30, 30, 35, 5, 1, 17, 37, 17, 16, 41, 17, 41, 37, 37, 41, 38, 16, 94, 99, 16, 99, 41, 41, 99, 40, 41, 40, 38, 38, 40, 7, 2, 22, 42, 22, 21, 46, 22, 46, 42, 42, 46, 43, 21, 3, 44, 21, 44, 46, 46, 44, 45, 46, 45, 43, 43, 45, 8, 3, 27, 47, 27, 26, 51, 27, 51, 47, 47, 51, 48, 26, 4, 49, 26, 49, 51, 51, 49, 50, 51, 50, 48, 48, 50, 9, 98, 32, 100, 32, 31, 56, 32, 56, 100, 100, 56, 53, 31, 5, 54, 31, 54, 56, 56, 54, 55, 56, 55, 53, 53, 55, 10, 5, 35, 57, 35, 34, 61, 35, 61, 57, 57, 61, 58, 34, 1, 59, 34, 59, 61, 61, 59, 60, 61, 60, 58, 58, 60, 6, 1, 37, 59, 37, 38, 64, 37, 64, 59, 59, 64, 60, 38, 7, 62, 38, 62, 64, 64, 62, 63, 64, 63, 60, 60, 63, 6, 2, 42, 39, 42, 43, 67, 42, 67, 39, 39, 67, 40, 43, 8, 65, 43, 65, 67, 67, 65, 66, 67, 66, 40, 40, 66, 7, 3, 47, 44, 47, 48, 70, 47, 70, 44, 44, 70, 45, 48, 9, 68, 48, 68, 70, 70, 68, 69, 70, 69, 45, 45, 69, 8, 4, 52, 49, 52, 53, 73, 52, 73, 49, 49, 73, 50, 53, 10, 71, 53, 71, 73, 73, 71, 72, 73, 72, 50, 50, 72, 9, 5, 57, 54, 57, 58, 76, 57, 76, 54, 54, 76, 55, 58, 6, 74, 58, 74, 76, 76, 74, 75, 76, 75, 55, 55, 75, 10, 6, 63, 77, 63, 62, 81, 63, 81, 77, 77, 81, 78, 62, 7, 79, 62, 79, 81, 81, 79, 80, 81, 80, 78, 78, 80, 11, 7, 66, 79, 66, 65, 84, 66, 84, 79, 79, 84, 80, 65, 8, 82, 65, 82, 84, 84, 82, 83, 84, 83, 80, 80, 83, 11, 8, 69, 82, 69, 68, 87, 69, 87, 82, 82, 87, 83, 68, 9, 85, 68, 85, 87, 87, 85, 86, 87, 86, 83, 83, 86, 11, 9, 72, 85, 72, 71, 90, 72, 90, 85, 85, 90, 86, 71, 10, 88, 71, 88, 90, 90, 88, 89, 90, 89, 86, 86, 89, 11, 10, 75, 88, 75, 74, 91, 75, 91, 88, 88, 91, 89, 74, 6, 77, 74, 77, 91, 91, 77, 78, 91, 78, 89, 89, 78, 11] +} + +def Mesh "pPlatonic2" ( + references = +) +{ + rel material:binding = + double3 xformOp:translate = (-8, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] +} + +def Mesh "pPlatonic3" ( + references = +) +{ + rel material:binding = + double3 xformOp:translate = (8, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] +} + +over "MaterialX" +( + references = [@./standard_surface_jade.usda@, + @./standard_surface_gold.mtlx@] +) +{ +} + +def Scope "Looks" +{ + def Material "standardSurface2SG" + { + string inputs:file1:varnameStr = "map1" + string inputs:file2:varnameStr = "map1" + string inputs:file3:varnameStr = "map1" + token outputs:mtlx:surface.connect = + + def Shader "standardSurface2" + { + uniform token info:id = "ND_standard_surface_surfaceshader" + float inputs:base = 1 + color3f inputs:base_color.connect = + float inputs:emission.connect = + color3f inputs:emission_color = (0.4709, 0.3034, 0) + float inputs:metalness.connect = + float3 inputs:normal.connect = + float inputs:specular_roughness.connect = + token outputs:surface + } + + def NodeGraph "MayaNG_standardSurface2SG" + { + color3f outputs:baseColor.connect = + float outputs:emission.connect = + float outputs:metalness.connect = + float3 outputs:normalCamera.connect = + float outputs:specularRoughness.connect = + + def Shader "file1" + { + uniform token info:id = "ND_image_color3" + color3f inputs:default = (0.5, 0.5, 0.5) + asset inputs:file = @../textures/Brazilian_rosewood_pxr128.png@ + float2 inputs:texcoord.connect = + string inputs:uaddressmode = "periodic" + string inputs:vaddressmode = "periodic" + color3f outputs:out + } + + def Shader "MayaGeomPropValue_file1" + { + uniform token info:id = "ND_geompropvalue_vector2" + string inputs:geomprop.connect = + float2 outputs:out + } + + def Shader "file2" + { + uniform token info:id = "ND_image_color3" + color3f inputs:default = (0.5, 0.5, 0.5) + asset inputs:file = @../textures/RGB.png@ ( + colorSpace = "Raw" + ) + float2 inputs:texcoord.connect = + string inputs:uaddressmode = "periodic" + string inputs:vaddressmode = "periodic" + color3f outputs:out + } + + def Shader "MayaGeomPropValue_file2" + { + uniform token info:id = "ND_geompropvalue_vector2" + string inputs:geomprop.connect = + float2 outputs:out + } + + def Shader "MayaSwizzle_file2_r" + { + uniform token info:id = "ND_swizzle_color3_float" + string inputs:channels = "r" + color3f inputs:in.connect = + float outputs:out + } + + def Shader "MayaSwizzle_file2_g" + { + uniform token info:id = "ND_swizzle_color3_float" + string inputs:channels = "g" + color3f inputs:in.connect = + float outputs:out + } + + def Shader "file3" + { + uniform token info:id = "ND_image_color3" + color3f inputs:default = (0.5, 0.5, 0.5) + asset inputs:file = @../textures/normalSpiral.png@ ( + colorSpace = "Raw" + ) + float2 inputs:texcoord.connect = + string inputs:uaddressmode = "periodic" + string inputs:vaddressmode = "periodic" + color3f outputs:out + } + + def Shader "MayaGeomPropValue_file3" + { + uniform token info:id = "ND_geompropvalue_vector2" + string inputs:geomprop.connect = + float2 outputs:out + } + + def Shader "MayaConvert_file3_color3f_float3" + { + uniform token info:id = "ND_convert_color3_vector3" + color3f inputs:in.connect = + float3 outputs:out + } + + def Shader "MayaNormalMap_standardSurface2_normalCamera" + { + uniform token info:id = "ND_normalmap" + float3 inputs:in.connect = + float3 outputs:out + } + + def Shader "MayaSwizzle_file2_b" + { + uniform token info:id = "ND_swizzle_color3_float" + string inputs:channels = "b" + color3f inputs:in.connect = + float outputs:out + } + } + } +} diff --git a/test/lib/usd/translators/UsdImportRelativeTextures/rel_project/workspace.mel b/test/lib/usd/translators/UsdImportRelativeTextures/rel_project/workspace.mel new file mode 100644 index 0000000000..247ddbe51e --- /dev/null +++ b/test/lib/usd/translators/UsdImportRelativeTextures/rel_project/workspace.mel @@ -0,0 +1,72 @@ + +workspace -fr "fluidCache" "cache/nCache/fluid"; +workspace -fr "images" "images"; +workspace -fr "JT_ATF" "data"; +workspace -fr "offlineEdit" "scenes/edits"; +workspace -fr "STEP_ATF Export" "data"; +workspace -fr "furShadowMap" "renderData/fur/furShadowMap"; +workspace -fr "SVG" "data"; +workspace -fr "scripts" "scripts"; +workspace -fr "DAE_FBX" "data"; +workspace -fr "shaders" "renderData/shaders"; +workspace -fr "NX_ATF" "data"; +workspace -fr "CATIAV5_ATF Export" "data"; +workspace -fr "furFiles" "renderData/fur/furFiles"; +workspace -fr "OBJ" "data"; +workspace -fr "PARASOLID_ATF Export" "data"; +workspace -fr "FBX export" "data"; +workspace -fr "furEqualMap" "renderData/fur/furEqualMap"; +workspace -fr "DAE_FBX export" "data"; +workspace -fr "CATIAV5_ATF" "data"; +workspace -fr "SAT_ATF Export" "data"; +workspace -fr "movie" "movies"; +workspace -fr "ASS Export" "data"; +workspace -fr "move" "data"; +workspace -fr "mayaAscii" "scenes"; +workspace -fr "autoSave" "autosave"; +workspace -fr "NX_ATF Export" "data"; +workspace -fr "sound" "sound"; +workspace -fr "mayaBinary" "scenes"; +workspace -fr "timeEditor" "Time Editor"; +workspace -fr "DWG_ATF" "data"; +workspace -fr "Arnold-USD" "data"; +workspace -fr "JT_ATF Export" "data"; +workspace -fr "iprImages" "renderData/iprImages"; +workspace -fr "FBX" "data"; +workspace -fr "renderData" "renderData"; +workspace -fr "CATIAV4_ATF" "data"; +workspace -fr "fileCache" "cache/nCache"; +workspace -fr "eps" "data"; +workspace -fr "3dPaintTextures" "sourceimages/3dPaintTextures"; +workspace -fr "DXF_ATF Export" "data"; +workspace -fr "translatorData" "data"; +workspace -fr "mel" "scripts"; +workspace -fr "particles" "cache/particles"; +workspace -fr "DXF_ATF" "data"; +workspace -fr "scene" "scenes"; +workspace -fr "USD Export" "data"; +workspace -fr "mayaLT" ""; +workspace -fr "SAT_ATF" "data"; +workspace -fr "PROE_ATF" "data"; +workspace -fr "WIRE_ATF Export" "data"; +workspace -fr "sourceImages" "sourceimages"; +workspace -fr "clips" "clips"; +workspace -fr "furImages" "renderData/fur/furImages"; +workspace -fr "STEP_ATF" "data"; +workspace -fr "DWG_ATF Export" "data"; +workspace -fr "depth" "renderData/depth"; +workspace -fr "sceneAssembly" "sceneAssembly"; +workspace -fr "IGES_ATF Export" "data"; +workspace -fr "PARASOLID_ATF" "data"; +workspace -fr "IGES_ATF" "data"; +workspace -fr "teClipExports" "Time Editor/Clip Exports"; +workspace -fr "ASS" "data"; +workspace -fr "audio" "sound"; +workspace -fr "USD Import" "data"; +workspace -fr "Alembic" "data"; +workspace -fr "illustrator" "data"; +workspace -fr "diskCache" "data"; +workspace -fr "WIRE_ATF" "data"; +workspace -fr "templates" "assets"; +workspace -fr "OBJexport" "data"; +workspace -fr "furAttrMap" "renderData/fur/furAttrMap"; diff --git a/test/lib/usd/translators/UsdImportRelativeTextures/standard_surface_gold.mtlx b/test/lib/usd/translators/UsdImportRelativeTextures/standard_surface_gold.mtlx new file mode 100644 index 0000000000..912c3d123d --- /dev/null +++ b/test/lib/usd/translators/UsdImportRelativeTextures/standard_surface_gold.mtlx @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/test/lib/usd/translators/UsdImportRelativeTextures/standard_surface_jade.usda b/test/lib/usd/translators/UsdImportRelativeTextures/standard_surface_jade.usda new file mode 100644 index 0000000000..910c5cd264 --- /dev/null +++ b/test/lib/usd/translators/UsdImportRelativeTextures/standard_surface_jade.usda @@ -0,0 +1,118 @@ +#usda 1.0 +( + customLayerData = { + string colorSpace = "lin_rec709" + } + doc = """Generated from Composed Stage of root layer +""" +) + +def "MaterialX" +{ + def "Materials" + { + def Material "Jade" + { + float inputs:base = 0.5 + color3f inputs:base_color = (0.0603, 0.4398, 0.1916) + float inputs:coat + float inputs:coat_affect_color + float inputs:coat_affect_roughness + float inputs:coat_anisotropy + color3f inputs:coat_color + float inputs:coat_IOR + float3 inputs:coat_normal + float inputs:coat_rotation + float inputs:coat_roughness + float inputs:diffuse_roughness + float inputs:emission + color3f inputs:emission_color + float inputs:metalness + float3 inputs:normal + color3f inputs:opacity + float inputs:sheen + color3f inputs:sheen_color + float inputs:sheen_roughness + float inputs:specular + float inputs:specular_anisotropy = 0.5 + color3f inputs:specular_color + float inputs:specular_IOR = 2.418 + float inputs:specular_rotation + float inputs:specular_roughness = 0.25 + float inputs:subsurface = 0.4 + float inputs:subsurface_anisotropy + color3f inputs:subsurface_color = (0.0603, 0.4398, 0.1916) + color3f inputs:subsurface_radius + float inputs:subsurface_scale + float3 inputs:tangent + float inputs:thin_film_IOR + float inputs:thin_film_thickness + bool inputs:thin_walled + float inputs:transmission + color3f inputs:transmission_color + float inputs:transmission_depth + float inputs:transmission_dispersion + float inputs:transmission_extra_roughness + color3f inputs:transmission_scatter + float inputs:transmission_scatter_anisotropy + token outputs:mtlx:surface.connect = + + def Shader "ND_standard_surface_surfaceshader" + { + uniform token info:id = "ND_standard_surface_surfaceshader" + float inputs:base.connect = + color3f inputs:base_color.connect = + float inputs:coat.connect = + float inputs:coat_affect_color.connect = + float inputs:coat_affect_roughness.connect = + float inputs:coat_anisotropy.connect = + color3f inputs:coat_color.connect = + float inputs:coat_IOR.connect = + float3 inputs:coat_normal.connect = + float inputs:coat_rotation.connect = + float inputs:coat_roughness.connect = + float inputs:diffuse_roughness.connect = + float inputs:emission.connect = + color3f inputs:emission_color.connect = + float inputs:metalness.connect = + float3 inputs:normal.connect = + color3f inputs:opacity.connect = + float inputs:sheen.connect = + color3f inputs:sheen_color.connect = + float inputs:sheen_roughness.connect = + float inputs:specular.connect = + float inputs:specular_anisotropy.connect = + color3f inputs:specular_color.connect = + float inputs:specular_IOR.connect = + float inputs:specular_rotation.connect = + float inputs:specular_roughness.connect = + float inputs:subsurface.connect = + float inputs:subsurface_anisotropy.connect = + color3f inputs:subsurface_color.connect = + color3f inputs:subsurface_radius.connect = + float inputs:subsurface_scale.connect = + float3 inputs:tangent.connect = + float inputs:thin_film_IOR.connect = + float inputs:thin_film_thickness.connect = + bool inputs:thin_walled.connect = + float inputs:transmission.connect = + color3f inputs:transmission_color.connect = + float inputs:transmission_depth.connect = + float inputs:transmission_dispersion.connect = + float inputs:transmission_extra_roughness.connect = + color3f inputs:transmission_scatter.connect = + float inputs:transmission_scatter_anisotropy.connect = + token outputs:surface + } + } + } + + def "Shaders" + { + def Shader "ND_standard_surface_surfaceshader" + { + uniform token info:id = "ND_standard_surface_surfaceshader" + token outputs:surface + } + } +} \ No newline at end of file diff --git a/test/lib/usd/translators/testUsdImportRelativeTextures.py b/test/lib/usd/translators/testUsdImportRelativeTextures.py new file mode 100644 index 0000000000..2fd56cc842 --- /dev/null +++ b/test/lib/usd/translators/testUsdImportRelativeTextures.py @@ -0,0 +1,131 @@ +#!/usr/bin/env mayapy +# +# Copyright 2023 Autodesk +# +# 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 maya import cmds +from maya import standalone + +import os +import re +import tempfile +import unittest + +import fixturesUtils + + +class testUsdImportRelativeTextures(unittest.TestCase): + """ + Test importing with different modes for textures file paths. + """ + + @classmethod + def setUpClass(cls): + cls.inputPath = fixturesUtils.readOnlySetUpClass(__file__) + + @classmethod + def tearDownClass(cls): + standalone.uninitialize() + + def runImportRelativeTexturesTest(self, relativeMode): + """ + Tests that the textures file paths are what is expected. + """ + + # Ensure the project folder is set in Maya. + projectFolder = os.path.join(self.inputPath, 'UsdImportRelativeTextures', 'rel_project') + cmds.workspace(projectFolder, openWorkspace=True) + + usdFile = os.path.join(self.inputPath, "UsdImportRelativeTextures", "UsdImportRelativeTextures.usda") + options = ["shadingMode=[[useRegistry,MaterialX]]", + "primPath=/", + "importRelativeTextures=%s" % relativeMode] + cmds.file(usdFile, i=True, type="USD Import", + ignoreVersion=True, ra=True, mergeNamespacesOnClash=False, + namespace="Test", pr=True, importTimeRange="combine", + options=";".join(options)) + + tmpDir = tempfile.mkdtemp(prefix='ImportRelativeTexturesTest') + mayaFile = os.path.join(tmpDir, 'ImportRelativeTexturesScene.ma') + try: + cmds.file(rename=mayaFile) + cmds.file(save=True, force=True, type="mayaAscii") + cmds.file(new=True, force=True) + + # Check texture file names + expected = { + "Test:file1": "Brazilian_rosewood_pxr128.png", + "Test:file2": "RGB.png", + "Test:file3": "normalSpiral.png", + } + + # The getAttr command returns resolved file names. + # The only way to validate that the names are relative to the project + # is read the saved Maya ASCII file and find the file nodes and attribute + # by hand. + + relProjectMarker = 'rel_project//..' + + fileRE = re.compile(r'''.*createNode file -n "([^"]+)";.*''') + attrRE = re.compile(r'''.*setAttr ".ftn" -type "string" "([^"]+)";.*''') + expectedFile = None + + with open(mayaFile, 'r') as f: + for line in f: + m = fileRE.match(line) + if m: + fileNode = m.group(1) + if fileNode in expected: + expectedFile = expected[fileNode] + continue + + m = attrRE.match(line) + if not m: + continue + if not expectedFile: + continue + textureFile = m.group(1) + self.assertIn(expectedFile, textureFile) + if relativeMode == 'absolute': + self.assertNotIn(relProjectMarker, textureFile) + else: + self.assertIn(relProjectMarker, textureFile) + finally: + if os.path.exists(mayaFile): + os.remove(mayaFile) + if os.path.exists(tmpDir): + os.rmdir(tmpDir) + + + def testImportRelativeTextures(self): + """ + Tests that the textures file paths are relative. + """ + self.runImportRelativeTexturesTest('relative') + + def testImportAbsoluteTextures(self): + """ + Tests that the textures file paths are absolute. + """ + self.runImportRelativeTexturesTest('absolute') + + def testImportAutomaticTextures(self): + """ + Tests that the textures file paths mode is automatically detected. + """ + self.runImportRelativeTexturesTest('automatic') + +if __name__ == '__main__': + unittest.main(verbosity=2)