Skip to content

Commit

Permalink
Merge pull request #2885 from Autodesk/bailp/MAYA-127404/save-relativ…
Browse files Browse the repository at this point in the history
…e-sub-layers

MAYA-127404 sub-layers relative to their parent
  • Loading branch information
seando-adsk authored Mar 9, 2023
2 parents b128c0d + 260cb5a commit 31c401b
Show file tree
Hide file tree
Showing 16 changed files with 370 additions and 43 deletions.
3 changes: 3 additions & 0 deletions lib/mayaUsd/nodes/layerManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,9 @@ void LayerDatabase::convertAnonymousLayers(
if (root->IsAnonymous()) {
PXR_NS::SdfFileFormat::FileFormatArguments args;
std::string newFileName = MayaUsd::utils::generateUniqueFileName(proxyName);
if (UsdMayaUtilFileSystem::requireUsdPathsRelativeToMayaSceneFile()) {
newFileName = UsdMayaUtilFileSystem::getPathRelativeToMayaSceneFile(newFileName);
}
MayaUsd::utils::saveLayerWithFormat(root, newFileName);

MayaUsd::utils::setNewProxyPath(pShape->name(), UsdMayaUtil::convert(newFileName));
Expand Down
15 changes: 8 additions & 7 deletions lib/mayaUsd/ufe/UsdContextOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -481,13 +481,18 @@ class ToggleInstanceableStateCommand : public Ufe::UndoableCommand
bool _instanceable;
};

const std::string getTargetLayerFilePath(const UsdPrim& prim)
const PXR_NS::SdfLayerHandle getCurrentTargetLayer(const UsdPrim& prim)
{
auto stage = prim.GetStage();
if (!stage)
return {};

auto layer = stage->GetEditTarget().GetLayer();
return stage->GetEditTarget().GetLayer();
}

const std::string getTargetLayerFilePath(const UsdPrim& prim)
{
auto layer = getCurrentTargetLayer(prim);
if (!layer)
return {};

Expand All @@ -496,11 +501,7 @@ const std::string getTargetLayerFilePath(const UsdPrim& prim)

bool _prepareUSDReferenceTargetLayer(const UsdPrim& prim)
{
const char* script = "import mayaUsd_USDRootFileRelative as murel\n"
"murel.usdFileRelative.setRelativeFilePathRoot(r'''%s''')";

const std::string commandString = TfStringPrintf(script, getTargetLayerFilePath(prim).c_str());
return MGlobal::executePythonCommand(commandString.c_str());
return UsdMayaUtilFileSystem::prepareLayerSaveUILayer(getCurrentTargetLayer(prim), false);
}

// Ask SDF for all supported extensions:
Expand Down
81 changes: 81 additions & 0 deletions lib/mayaUsd/utils/utilFileSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,31 @@ std::string UsdMayaUtilFileSystem::getDir(const std::string& fullFilePath)
return ghc::filesystem::path(fullFilePath).parent_path().string();
}

UsdMayaUtilFileSystem::TemporaryCurrentDir::TemporaryCurrentDir(const std::string& newCurDir)
{
if (newCurDir.size() > 0) {
_previousCurDir = ghc::filesystem::current_path().string();
ghc::filesystem::current_path(newCurDir);
}
}

UsdMayaUtilFileSystem::TemporaryCurrentDir::~TemporaryCurrentDir()
{
try {
restore();
} catch (const std::exception&) {
// Ignore exception in destructor.
}
}

void UsdMayaUtilFileSystem::TemporaryCurrentDir::restore()
{
if (_previousCurDir.size() > 0) {
ghc::filesystem::current_path(_previousCurDir);
_previousCurDir.clear();
}
}

std::string UsdMayaUtilFileSystem::getMayaReferencedFileDir(const MObject& proxyShapeNode)
{
// Can not use MFnDependencyNode(proxyShapeNode).isFromReferencedFile() to test if it is
Expand Down Expand Up @@ -117,6 +142,15 @@ std::string UsdMayaUtilFileSystem::getMayaSceneFileDir()
return std::string();
}

std::string UsdMayaUtilFileSystem::getLayerFileDir(const PXR_NS::SdfLayerHandle& layer)
{
if (!layer)
return std::string();

const std::string layerFileName = layer->GetRealPath();
return UsdMayaUtilFileSystem::getDir(layerFileName);
}

std::pair<std::string, bool> UsdMayaUtilFileSystem::makePathRelativeTo(
const std::string& fileName,
const std::string& relativeToDir)
Expand Down Expand Up @@ -154,13 +188,60 @@ std::string UsdMayaUtilFileSystem::getPathRelativeToMayaSceneFile(const std::str
return relativePathAndSuccess.first;
}

std::string UsdMayaUtilFileSystem::getPathRelativeToLayerFile(
const std::string& fileName,
const PXR_NS::SdfLayerHandle& layer)
{
if (!layer)
return fileName;

const std::string layerDirPath = getLayerFileDir(layer);
auto relativePathAndSuccess = makePathRelativeTo(fileName, layerDirPath);

if (!relativePathAndSuccess.second) {
TF_WARN(
"File name (%s) cannot be resolved as relative to its parent layer directory (%s), "
"using the absolute path.",
fileName.c_str(),
layerDirPath.c_str());
}

return relativePathAndSuccess.first;
}

bool UsdMayaUtilFileSystem::prepareLayerSaveUILayer(
const PXR_NS::SdfLayerHandle& layer,
bool useSceneFileForRoot)
{
std::string layerFileDir;
if (layer) {
layerFileDir = getLayerFileDir(layer);
} else if (useSceneFileForRoot) {
layerFileDir = getMayaSceneFileDir();
}

const char* script = "import mayaUsd_USDRootFileRelative as murel\n"
"murel.usdFileRelative.setRelativeFilePathRoot(r'''%s''')";

const std::string commandString = TfStringPrintf(script, layerFileDir.c_str());
return MGlobal::executePythonCommand(commandString.c_str());
}

bool UsdMayaUtilFileSystem::requireUsdPathsRelativeToMayaSceneFile()
{
static const MString MAKE_PATH_RELATIVE_TO_SCENE_FILE = "mayaUsd_MakePathRelativeToSceneFile";
return MGlobal::optionVarExists(MAKE_PATH_RELATIVE_TO_SCENE_FILE)
&& MGlobal::optionVarIntValue(MAKE_PATH_RELATIVE_TO_SCENE_FILE);
}

bool UsdMayaUtilFileSystem::requireUsdPathsRelativeToParentLayer()
{
static const MString MAKE_PATH_RELATIVE_TO_PARENT_LAYER_FILE
= "mayaUsd_MakePathRelativeToParentLayer";
return MGlobal::optionVarExists(MAKE_PATH_RELATIVE_TO_PARENT_LAYER_FILE)
&& MGlobal::optionVarIntValue(MAKE_PATH_RELATIVE_TO_PARENT_LAYER_FILE);
}

bool UsdMayaUtilFileSystem::requireUsdPathsRelativeToEditTargetLayer()
{
static const MString MAKE_PATH_RELATIVE_TO_EDIT_TARGET_LAYER_FILE
Expand Down
50 changes: 48 additions & 2 deletions lib/mayaUsd/utils/utilFileSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <mayaUsd/base/api.h>

#include <pxr/pxr.h>
#include <pxr/usd/sdf/layer.h>

#include <maya/MObject.h>

Expand All @@ -34,6 +35,26 @@ std::string resolvePath(const std::string& filePath);
MAYAUSD_CORE_PUBLIC
std::string getDir(const std::string& fullFilePath);

/*! \brief Temporarily change the process current directory.
* If built with an empty string, the current directory will not be changed.
*/
class TemporaryCurrentDir
{
public:
MAYAUSD_CORE_PUBLIC
TemporaryCurrentDir(const std::string& newCurDir);

MAYAUSD_CORE_PUBLIC
~TemporaryCurrentDir();

// Restore the previous current directory immediately.
MAYAUSD_CORE_PUBLIC
void restore();

private:
std::string _previousCurDir;
};

/*! \brief Takes in two absolute file paths and computes a relative path of the first one
to second one.
Expand Down Expand Up @@ -66,23 +87,48 @@ std::string getMayaReferencedFileDir(const MObject& proxyShapeNode);
MAYAUSD_CORE_PUBLIC
std::string getMayaSceneFileDir();

/*! \brief returns the Maya workspace file rule entry for scenes
/*! \brief returns parent directory of the given layer.
*/
MAYAUSD_CORE_PUBLIC
std::string getMayaWorkspaceScenesDir();
std::string getLayerFileDir(const PXR_NS::SdfLayerHandle& layer);

/*! \brief returns the Maya workspace file rule entry for scenes
*/
MAYAUSD_CORE_PUBLIC std::string getMayaWorkspaceScenesDir();

/*! \brief takes in an absolute file path and returns the path relative to maya scene file.
When there is no scene file, the absolute (input) path will be returned.
*/
MAYAUSD_CORE_PUBLIC
std::string getPathRelativeToMayaSceneFile(const std::string& fileName);

/*! \brief takes in an absolute file path and returns the path relative to a USD layer.
When there is no layer or the layer has never been saved, then the absolute
(input) path will be returned.
*/
MAYAUSD_CORE_PUBLIC
std::string
getPathRelativeToLayerFile(const std::string& fileName, const PXR_NS::SdfLayerHandle& layer);

/*! \brief returns the flag specifying whether USD file paths should be saved as relative to Maya
* scene file
*/
MAYAUSD_CORE_PUBLIC
bool requireUsdPathsRelativeToMayaSceneFile();

/*! \brief prepares the UI used to save layers with the given layer file path, so that the UI
can potentially make the selected file name relative to that layer. If the layer is
null, the UI can either use the scene file or not make the file relative.
*/
MAYAUSD_CORE_PUBLIC
bool prepareLayerSaveUILayer(const PXR_NS::SdfLayerHandle& layer, bool useSceneFileForRoot);

/*! \brief returns the flag specifying whether USD file paths should be saved
* as relative to the given parent layer.
*/
MAYAUSD_CORE_PUBLIC
bool requireUsdPathsRelativeToParentLayer();

/*! \brief returns the flag specifying whether USD file paths should be saved
* as relative to the current edit target layer.
*/
Expand Down
52 changes: 48 additions & 4 deletions lib/mayaUsd/utils/utilSerialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -302,11 +302,30 @@ SdfLayerRefPtr saveAnonymousLayer(

saveLayerWithFormat(anonLayer, filePath, formatArg);

SdfLayerRefPtr newLayer = SdfLayer::FindOrOpen(filePath);
const bool isSubLayer = (parent._layerParent != nullptr);
std::string relativePathAnchor;

if (savePathAsRelative) {
if (isSubLayer) {
filePath
= UsdMayaUtilFileSystem::getPathRelativeToLayerFile(filePath, parent._layerParent);
relativePathAnchor = UsdMayaUtilFileSystem::getLayerFileDir(parent._layerParent);
} else {
filePath = UsdMayaUtilFileSystem::getPathRelativeToMayaSceneFile(filePath);
relativePathAnchor = UsdMayaUtilFileSystem::getMayaSceneFileDir();
}
}

// When the filePath was made relative, we need to help FindOrOpen to locate
// the sub-layers when using relative paths. We temporarily chande the
// current directory to the location the file path is relative to.
UsdMayaUtilFileSystem::TemporaryCurrentDir tempCurDir(relativePathAnchor);
SdfLayerRefPtr newLayer = SdfLayer::FindOrOpen(filePath);
tempCurDir.restore();

if (newLayer) {
if (parent._layerParent) {
parent._layerParent->GetSubLayerPaths().Replace(
anonLayer->GetIdentifier(), newLayer->GetIdentifier());
if (isSubLayer) {
updateSubLayer(parent._layerParent, anonLayer, filePath);
} else if (!parent._proxyPath.empty()) {
saveRootLayer(newLayer, parent._proxyPath, savePathAsRelative);
}
Expand All @@ -318,6 +337,31 @@ SdfLayerRefPtr saveAnonymousLayer(
return newLayer;
}

void updateSubLayer(
const SdfLayerRefPtr& parentLayer,
const SdfLayerRefPtr& oldSubLayer,
const std::string& newSubLayerPath)
{
if (!parentLayer)
return;

if (!oldSubLayer)
return;

// Note: we don't know if the old sub-layer was referenced with an absolute
// or relative path, so we try replacing both and its identifier.
SdfSubLayerProxy subLayers = parentLayer->GetSubLayerPaths();

subLayers.Replace(oldSubLayer->GetIdentifier(), newSubLayerPath);

const std::string oldAbsPath = oldSubLayer->GetRealPath();
subLayers.Replace(oldAbsPath, newSubLayerPath);

const std::string oldRelPath
= UsdMayaUtilFileSystem::getPathRelativeToLayerFile(oldAbsPath, parentLayer);
subLayers.Replace(oldRelPath, newSubLayerPath);
}

void ensureUSDFileExtension(std::string& filePath)
{
const std::string& extension = SdfFileFormat::GetFileExtension(filePath);
Expand Down
16 changes: 16 additions & 0 deletions lib/mayaUsd/utils/utilSerialization.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ struct StageLayersToSave
If the file path is empty then use the current file path of the layer.
If the format is empty then use the current user-selected USD format option
as defined by the usdFormatArgOption() function. (See above.)
If the file path is relative, then it can be made relative to either the scene
file (for the root layer) or its parent layer (for sub-layers). We assume the
caller voluntarily made the path relative.
*/
MAYAUSD_CORE_PUBLIC
bool saveLayerWithFormat(
Expand Down Expand Up @@ -125,6 +129,18 @@ PXR_NS::SdfLayerRefPtr saveAnonymousLayer(
LayerParent parent,
std::string formatArg = "");

/*! \brief Update the list of sub-layers with a new layer identity.
* The new sub-layer is identified by its path explicitly,
* because a given layer might get referenced through multiple
* different relative paths, so we cannot interrogate it about
* what its path is.
*/
MAYAUSD_CORE_PUBLIC
void updateSubLayer(
const SdfLayerRefPtr& parentLayer,
const SdfLayerRefPtr& oldSubLayer,
const std::string& newSubLayerPath);

/*! \brief Ensures that the filepath contains a valid USD extension.
*/
MAYAUSD_CORE_PUBLIC
Expand Down
32 changes: 23 additions & 9 deletions lib/usd/ui/layerEditor/layerTreeItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -362,31 +362,45 @@ void LayerTreeItem::saveAnonymousLayer()
auto sessionState = parentModel()->sessionState();

std::string fileName;
if (sessionState->saveLayerUI(nullptr, &fileName)) {
if (sessionState->saveLayerUI(nullptr, &fileName, parentLayer())) {

MayaUsd::utils::ensureUSDFileExtension(fileName);

// the path we have is an absolute path
const QString dialogTitle = StringResources::getAsQString(StringResources::kSaveLayer);
std::string formatTag = MayaUsd::utils::usdFormatArgOption();
if (saveSubLayer(dialogTitle, parentLayerItem(), layer(), fileName, formatTag)) {
printf("USD Layer written to %s\n", fileName.c_str());

// now replace the layer in the parent
if (isRootLayer()) {
sessionState->rootLayerPathChanged(
UsdMayaUtilFileSystem::requireUsdPathsRelativeToMayaSceneFile()
? UsdMayaUtilFileSystem::getPathRelativeToMayaSceneFile(fileName)
: fileName);
if (UsdMayaUtilFileSystem::requireUsdPathsRelativeToMayaSceneFile()) {
fileName = UsdMayaUtilFileSystem::getPathRelativeToMayaSceneFile(fileName);
}
sessionState->rootLayerPathChanged(fileName);
} else {
// now replace the layer in the parent
auto parentItem = parentLayerItem();

std::string relativePathAnchor;
if (parentItem && UsdMayaUtilFileSystem::requireUsdPathsRelativeToParentLayer()) {
fileName = UsdMayaUtilFileSystem::getPathRelativeToLayerFile(
fileName, parentItem->layer());
relativePathAnchor
= UsdMayaUtilFileSystem::getLayerFileDir(parentItem->layer());
}

// Now replace the layer in the parent
//
// When the filePath was made relative, we need to help FindOrOpen to locate
// the sub-layers when using relative paths. We temporarily chande the
// current directory to the location the file path is relative to.
UsdMayaUtilFileSystem::TemporaryCurrentDir tempCurDir(relativePathAnchor);
auto newLayer = SdfLayer::FindOrOpen(fileName);
tempCurDir.restore();

if (newLayer) {
bool setTarget = _isTargetLayer;
auto model = parentModel();
parentItem->layer()->GetSubLayerPaths().Replace(
layer()->GetIdentifier(), newLayer->GetIdentifier());
MayaUsd::utils::updateSubLayer(parentItem->layer(), layer(), fileName);
if (setTarget) {
sessionState->stage()->SetEditTarget(newLayer);
}
Expand Down
Loading

0 comments on commit 31c401b

Please sign in to comment.