diff --git a/lib/mayaUsd/commands/Readme.md b/lib/mayaUsd/commands/Readme.md index 73fa320313..4074da6831 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 | automatic | Selects how textures filenames are generated: absolute, relative or automatic. When automatic, the filename is relative if the source filename of the texture being imported is relative | ### 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..14febc1a05 100644 --- a/lib/mayaUsd/fileio/jobs/jobArgs.cpp +++ b/lib/mayaUsd/fileio/jobs/jobArgs.cpp @@ -1151,6 +1151,13 @@ UsdMayaJobImportArgs::UsdMayaJobImportArgs( UsdMayaPreferredMaterialTokens->allTokens)) , importUSDZTexturesFilePath(UsdMayaJobImportArgs::GetImportUSDZTexturesFilePath(userArgs)) , importUSDZTextures(extractBoolean(userArgs, UsdMayaJobImportArgsTokens->importUSDZTextures)) + , importRelativeTextures(extractToken( + userArgs, + UsdMayaJobImportArgsTokens->importRelativeTextures, + UsdMayaJobImportArgsTokens->automatic, + { UsdMayaJobImportArgsTokens->automatic, + UsdMayaJobImportArgsTokens->absolute, + UsdMayaJobImportArgsTokens->relative })) , importInstances(extractBoolean(userArgs, UsdMayaJobImportArgsTokens->importInstances)) , useAsAnimationCache(extractBoolean(userArgs, UsdMayaJobImportArgsTokens->useAsAnimationCache)) , importWithProxyShapes(importWithProxyShapes) @@ -1209,6 +1216,8 @@ const VtDictionary& UsdMayaJobImportArgs::GetDefaultDictionary() d[UsdMayaJobImportArgsTokens->importInstances] = true; d[UsdMayaJobImportArgsTokens->importUSDZTextures] = false; d[UsdMayaJobImportArgsTokens->importUSDZTexturesFilePath] = ""; + d[UsdMayaJobImportArgsTokens->importRelativeTextures] + = UsdMayaJobImportArgsTokens->automatic.GetString(); d[UsdMayaJobImportArgsTokens->pullImportStage] = UsdStageRefPtr(); d[UsdMayaJobImportArgsTokens->useAsAnimationCache] = false; d[UsdMayaJobImportArgsTokens->preserveTimeline] = false; @@ -1289,6 +1298,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 +1387,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..1e98849c6f 100644 --- a/lib/mayaUsd/fileio/jobs/jobArgs.h +++ b/lib/mayaUsd/fileio/jobs/jobArgs.h @@ -155,8 +155,13 @@ TF_DECLARE_PUBLIC_TOKENS( (importInstances) \ (importUSDZTextures) \ (importUSDZTexturesFilePath) \ + (importRelativeTextures) \ (pullImportStage) \ (preserveTimeline) \ + /* values for import relative textures */ \ + (automatic) \ + (absolute) \ + (relative) \ /* assemblyRep values */ \ (Collapsed) \ (Full) \ @@ -348,6 +353,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..cb9e549ecf --- /dev/null +++ b/lib/usd/translators/shading/shadingAsset.cpp @@ -0,0 +1,356 @@ +// +// 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 SdfAssetPath handleShaderInput(const UsdShadeInput& usdInput) +{ + VtValue val; + if (!usdInput.Get(&val)) + return {}; + + if (!val.IsHolding()) + return {}; + + return val.UncheckedGet(); +} + +// Note: return false only on import errors. Currently always returns true. +static bool handleUDIM( + const UsdPrim& prim, + MFnDependencyNode& depFn, + const std::string& unresolvedFilePath, + SdfAssetPath* resolvedAssetPath) +{ + 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; + } + } + + *resolvedAssetPath = SdfAssetPath(udimPath); + return true; +} + +// Note: return false only on import errors. +static bool handleUSDZTexture( + const UsdPrim& prim, + MFnDependencyNode& depFn, + const UsdMayaJobImportArgs& jobArgs, + const std::string& filePath, + std::string unresolvedFilePath, + SdfAssetPath* resolvedAssetPath) +{ + if (!jobArgs.importUSDZTextures) + return true; + + if (jobArgs.importUSDZTexturesFilePath.empty()) + return true; + + if (filePath.empty()) + return true; + + if (!ArIsPackageRelativePath(filePath)) + return true; + + // 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. + *resolvedAssetPath = SdfAssetPath(extractedFilePath); + return true; +} + +static bool handleMakeRelative( + const UsdMayaJobImportArgs& jobArgs, + const SdfAssetPath& originalAssetPath, + SdfAssetPath* resolvedAssetPath) +{ + const std::string& relativeMode = jobArgs.importRelativeTextures; + 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()) { + *resolvedAssetPath + = SdfAssetPath(TfStringReplace(absolutePath.generic_string(), "\\", "/")); + } + } else if (makeRelative) { + const std::string absPath = resolvedAssetPath->GetAssetPath(); + const std::string relToProject = UsdMayaUtilFileSystem::makeProjectRelatedPath(absPath); + if (!relToProject.empty()) { + *resolvedAssetPath = SdfAssetPath(TfStringReplace(relToProject, "\\", "/")); + } + } + + 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; + + // Note: do not modify to keep by reference since we will modify + // resolvedAssetPath multiple times, which would modify this + // filePath which we explicitly want not to change. + 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); + } + + // Note: do not modify to keep by reference since we will modify + // resolvedAssetPath multiple times, which would modify this + // unresolvedFilePath which we explicitly want not to change. + const std::string unresolvedFilePath = resolvedAssetPath.GetAssetPath(); + + // Handle UDIM texture files: + if (!handleUDIM(prim, depFn, unresolvedFilePath, &resolvedAssetPath)) + return false; + + if (!handleUSDZTexture(prim, depFn, jobArgs, filePath, unresolvedFilePath, &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..8315e341ba 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 | automatic | Selects how textures filenames are generated: absolute, relative or automatic. When automatic, the filename is relative if the source filename of the texture being imported is relative | ### 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..40d3bc371c --- /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..4b2945ebce --- /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 and capacity to be made relative: + expected = { + "Test:file1": ("Brazilian_rosewood_pxr128.png", True), + "Test:file2": ("RGB.png", True), + "Test:file3": ("normalSpiral.png", False), + } + + # 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 + canBeRelative = False + + for line in open(mayaFile, 'r', newline='\n'): + m = fileRE.match(line) + if m: + fileNode = m.group(1) + if fileNode in expected: + expectedFile, canBeRelative = 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' or not canBeRelative: + 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)