From c69a45bce1601264ffa270020c0f64b4fa09149e Mon Sep 17 00:00:00 2001 From: Sean Donnelly <23455376+seando-adsk@users.noreply.github.com> Date: Wed, 3 Feb 2021 16:38:16 -0500 Subject: [PATCH 1/6] MAYA-105566 - Saving In-Memory Edits to Layers Step 1 of 3: * Adding the layer manager node which is responsible for saving usd edits to the Maya file. --- lib/mayaUsd/base/tokens.h | 14 +- lib/mayaUsd/nodes/CMakeLists.txt | 10 + lib/mayaUsd/nodes/layerManager.cpp | 909 ++++++++++++++++++++++++ lib/mayaUsd/nodes/layerManager.h | 117 +++ lib/mayaUsd/nodes/proxyShapeBase.cpp | 33 +- lib/mayaUsd/nodes/proxyShapeBase.h | 9 + lib/mayaUsd/nodes/proxyShapePlugin.cpp | 18 + lib/mayaUsd/ufe/UsdStageMap.cpp | 11 + lib/mayaUsd/ufe/UsdStageMap.h | 7 + lib/mayaUsd/ufe/Utils.cpp | 2 + lib/mayaUsd/ufe/Utils.h | 5 + lib/mayaUsd/utils/CMakeLists.txt | 2 + lib/mayaUsd/utils/utilFileSystem.cpp | 39 + lib/mayaUsd/utils/utilFileSystem.h | 11 + lib/mayaUsd/utils/utilSerialization.cpp | 237 ++++++ lib/mayaUsd/utils/utilSerialization.h | 94 +++ plugin/adsk/plugin/ProxyShape.cpp | 19 + plugin/adsk/plugin/ProxyShape.h | 9 + plugin/adsk/plugin/plugin.cpp | 19 + 19 files changed, 1560 insertions(+), 5 deletions(-) create mode 100644 lib/mayaUsd/nodes/layerManager.cpp create mode 100644 lib/mayaUsd/nodes/layerManager.h create mode 100644 lib/mayaUsd/utils/utilSerialization.cpp create mode 100644 lib/mayaUsd/utils/utilSerialization.h diff --git a/lib/mayaUsd/base/tokens.h b/lib/mayaUsd/base/tokens.h index b5043dce1f..4c9382eaaa 100644 --- a/lib/mayaUsd/base/tokens.h +++ b/lib/mayaUsd/base/tokens.h @@ -28,9 +28,17 @@ PXR_NAMESPACE_OPEN_SCOPE // Tokens that are used as optionVars in MayaUSD // -#define MAYA_USD_OPTIONVAR_TOKENS \ - /* Always target a session layer on a mayaUsdProxy*/ \ - (mayaUsd_ProxyTargetsSessionLayerOnOpen) +// mayaUsd_ProxyTargetsSessionLayerOnOpen: Always target a session layer on a mayaUsdProxy +// mayaUsd_SaveLayerFormatArgBinaryOption: When saving as .usd, should the format be binary +// mayaUsd_SerializedUsdEditsLocation: Option for what to do with Usd edits when the current +// Maya scene is about to be saved. optionVar values are: +// 1: save all edits back to usd files. +// 2: export the dirty usd layers to Maya string attributes to be serialized to the Maya file. +// 3: ignore all Usd edits. +// mayaUsd_SerializedUsdEditsLocationPrompt: optionVar to force a prompt on every save +#define MAYA_USD_OPTIONVAR_TOKENS \ + (mayaUsd_ProxyTargetsSessionLayerOnOpen)(mayaUsd_SaveLayerFormatArgBinaryOption)( \ + mayaUsd_SerializedUsdEditsLocation)(mayaUsd_SerializedUsdEditsLocationPrompt) TF_DECLARE_PUBLIC_TOKENS(MayaUsdOptionVars, MAYAUSD_CORE_PUBLIC, MAYA_USD_OPTIONVAR_TOKENS); diff --git a/lib/mayaUsd/nodes/CMakeLists.txt b/lib/mayaUsd/nodes/CMakeLists.txt index 6ab76f3d39..9e8ae7572f 100644 --- a/lib/mayaUsd/nodes/CMakeLists.txt +++ b/lib/mayaUsd/nodes/CMakeLists.txt @@ -25,6 +25,16 @@ set(HEADERS usdPrimProvider.h ) +if (UFE_FOUND) + target_sources(${PROJECT_NAME} + PRIVATE + layerManager.cpp + ) + list(APPEND HEADERS + layerManager.h + ) +endif() + # ----------------------------------------------------------------------------- # promoted headers # ----------------------------------------------------------------------------- diff --git a/lib/mayaUsd/nodes/layerManager.cpp b/lib/mayaUsd/nodes/layerManager.cpp new file mode 100644 index 0000000000..16d5af8bf5 --- /dev/null +++ b/lib/mayaUsd/nodes/layerManager.cpp @@ -0,0 +1,909 @@ +// +// Copyright 2020 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 "layerManager.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace { +static std::recursive_mutex findNodeMutex; + +// Utility func to disconnect an array plug, and all it's element plugs, and all +// their child plugs. +// Not in Utils, because it's not generic - ie, doesn't handle general case +// where compound/array plugs may be nested arbitrarily deep... +MStatus disconnectCompoundArrayPlug(MPlug arrayPlug) +{ + MStatus status; + MPlug elemPlug; + MPlug srcPlug; + MPlugArray destPlugs; + MDGModifier dgmod; + + auto disconnectPlug = [&](MPlug plug) -> MStatus { + MStatus status; + srcPlug = plug.source(&status); + if (!srcPlug.isNull()) { + dgmod.disconnect(srcPlug, plug); + } + destPlugs.clear(); + plug.destinations(destPlugs, &status); + for (size_t i = 0; i < destPlugs.length(); ++i) { + dgmod.disconnect(plug, destPlugs[i]); + } + return status; + }; + + // Considered using numConnectedElements, but for arrays-of-compound attributes, not sure if + // this will also detect connections to a child-of-an-element... so just iterating through all + // plugs. Shouldn't be too many... + const size_t numElements = arrayPlug.evaluateNumElements(); + // Iterate over all elements... + for (size_t elemI = 0; elemI < numElements; ++elemI) { + elemPlug = arrayPlug.elementByPhysicalIndex(elemI, &status); + + // Disconnect the element compound attribute + disconnectPlug(elemPlug); + + // ...then disconnect any children + if (elemPlug.numConnectedChildren() > 0) { + for (size_t childI = 0; childI < elemPlug.numChildren(); ++childI) { + disconnectPlug(elemPlug.child(childI)); + } + } + } + return dgmod.doIt(); +} + +SdfFileFormatConstPtr +getFileFormatForLayer(const std::string& identifierVal, const std::string& serializedVal) +{ + // If there is serialized data and it does not start with "#usda" then the format is Sdf. + // Else we look at the file extension to determine what it should be, which could be Sdf, Usd, + // Usdc, or Usda. + + SdfFileFormatConstPtr fileFormat; + + if (!serializedVal.empty() && !TfStringStartsWith(serializedVal, "#usda ")) { + fileFormat = SdfFileFormat::FindById(SdfTextFileFormatTokens->Id); + } else { + // In order to make the layer reloadable by SdfLayer::Reload(), we need the + // correct file format from identifier. + if (TfStringEndsWith(identifierVal, ".usd")) { + fileFormat = SdfFileFormat::FindById(UsdUsdFileFormatTokens->Id); + } else if (TfStringEndsWith(identifierVal, ".usdc")) { + fileFormat = SdfFileFormat::FindById(UsdUsdcFileFormatTokens->Id); + } else if (TfStringEndsWith(identifierVal, ".sdf")) { + fileFormat = SdfFileFormat::FindById(SdfTextFileFormatTokens->Id); + } else { + fileFormat = SdfFileFormat::FindById(UsdUsdaFileFormatTokens->Id); + } + } + + return fileFormat; +} + +MayaUsd::LayerManager* findNode() +{ + MFnDependencyNode fn; + MItDependencyNodes iter(MFn::kPluginDependNode); + for (; !iter.isDone(); iter.next()) { + MObject mobj = iter.item(); + fn.setObject(mobj); + if (fn.typeId() == MayaUsd::LayerManager::typeId && !fn.isFromReferencedFile()) { + return static_cast(fn.userNode()); + } + } + return nullptr; +} + +void setNewProxyPath(const MString& proxyNodeName, const MString& newValue) +{ + MString script; + script.format("setAttr -type \"string\" ^1s.filePath \"^2s\"", proxyNodeName, newValue); + MGlobal::executeCommand( + script, + /*display*/ true, + /*undo*/ false); +} + +void convertAnonymousLayersRecursive( + SdfLayerRefPtr layer, + const std::string& basename, + UsdStageRefPtr stage) +{ + auto currentTarget = stage->GetEditTarget().GetLayer(); + + std::vector sublayers = layer->GetSubLayerPaths(); + for (size_t i = 0, n = sublayers.size(); i < n; ++i) { + auto subL = layer->Find(sublayers[i]); + if (subL) { + convertAnonymousLayersRecursive(subL, basename, stage); + + if (subL->IsAnonymous()) { + auto newLayer = UsdMayaSerialization::saveAnonymousLayer(subL, layer, basename); + if (subL == currentTarget) { + stage->SetEditTarget(newLayer); + } + } + } + } +} + +constexpr auto kSaveOptionUICmd = "usdFileSaveOptions(true);"; + +} // namespace + +namespace MAYAUSD_NS_DEF { + +class LayerDatabase : public TfWeakBase +{ +public: + LayerDatabase(); + ~LayerDatabase(); + + static LayerDatabase& instance(); + static void setBatchSaveDelegate(BatchSaveDelegate delegate); + static void prepareForWriteCheck(bool*, void*); + static void loadLayersPostRead(void*); + static void cleanUpNewScene(void*); + static void removeManagerNode(MayaUsd::LayerManager* lm = nullptr); + + bool getStagesToSave(); + bool supportedNodeType(MTypeId type); + void addSupportForNodeType(MTypeId type); + void removeSupportForNodeType(MTypeId type); + bool remapSubLayerPaths(SdfLayerHandle parentLayer); + bool addLayer(SdfLayerRefPtr layer, const std::string& identifier = std::string()); + bool removeLayer(SdfLayerRefPtr layer); + void removeAllLayers(); + + SdfLayerHandle findLayer(std::string identifier) const; + +private: + void registerCallbacks(); + void unregisterCallbacks(); + + void _addLayer(SdfLayerRefPtr layer, const std::string& identifier); + void onStageSet(const MayaUsdProxyStageSetNotice& notice); + + bool saveUsd(); + bool saveUsdToMayaFile(); + bool saveUsdToUsdFiles(); + void convertAnonymousLayers(MayaUsdProxyShapeBase* pShape, UsdStageRefPtr stage); + void saveSessionLayer(MayaUsdProxyShapeBase* pShape, UsdStageRefPtr stage); + + std::map _idToLayer; + TfNotice::Key _onStageSetKey; + std::set _supportedTypes; + std::vector _stagesToSave; + static MCallbackId preSaveCallbackId; + static MCallbackId preExportCallbackId; + static MCallbackId postNewCallbackId; + static MCallbackId preOpenCallbackId; + + static MayaUsd::BatchSaveDelegate _batchSaveDelegate; +}; + +MCallbackId LayerDatabase::preSaveCallbackId = 0; +MCallbackId LayerDatabase::preExportCallbackId = 0; +MCallbackId LayerDatabase::postNewCallbackId = 0; +MCallbackId LayerDatabase::preOpenCallbackId = 0; + +MayaUsd::BatchSaveDelegate LayerDatabase::_batchSaveDelegate = nullptr; + +/*static*/ +LayerDatabase& LayerDatabase::instance() +{ + static LayerDatabase sLayerDB; + sLayerDB.registerCallbacks(); + return sLayerDB; +} + +LayerDatabase::LayerDatabase() +{ + TfWeakPtr me(this); + _onStageSetKey = TfNotice::Register(me, &LayerDatabase::onStageSet); +} + +LayerDatabase::~LayerDatabase() +{ + if (_onStageSetKey.IsValid()) { + TfNotice::Revoke(_onStageSetKey); + } + + unregisterCallbacks(); +} + +void LayerDatabase::registerCallbacks() +{ + if (0 == preSaveCallbackId) { + preSaveCallbackId = MSceneMessage::addCallback( + MSceneMessage::kBeforeSaveCheck, LayerDatabase::prepareForWriteCheck); + preExportCallbackId = MSceneMessage::addCallback( + MSceneMessage::kBeforeExportCheck, LayerDatabase::prepareForWriteCheck); + postNewCallbackId + = MSceneMessage::addCallback(MSceneMessage::kAfterNew, LayerDatabase::cleanUpNewScene); + preOpenCallbackId = MSceneMessage::addCallback( + MSceneMessage::kBeforeOpen, LayerDatabase::cleanUpNewScene); + } +} + +void LayerDatabase::unregisterCallbacks() +{ + if (0 != preSaveCallbackId) { + MSceneMessage::removeCallback(preSaveCallbackId); + MSceneMessage::removeCallback(preExportCallbackId); + MSceneMessage::removeCallback(preOpenCallbackId); + + preSaveCallbackId = 0; + preExportCallbackId = 0; + preOpenCallbackId = 0; + } +} + +void LayerDatabase::addSupportForNodeType(MTypeId type) +{ + if (_supportedTypes.find(type.id()) == _supportedTypes.end()) { + _supportedTypes.insert(type.id()); + } +} + +void LayerDatabase::removeSupportForNodeType(MTypeId type) { _supportedTypes.erase(type.id()); } + +bool LayerDatabase::supportedNodeType(MTypeId type) +{ + return (_supportedTypes.find(type.id()) != _supportedTypes.end()); +} + +void LayerDatabase::onStageSet(const MayaUsdProxyStageSetNotice& notice) +{ + const MayaUsdProxyShapeBase& psb = notice.GetProxyShape(); + UsdStageRefPtr stage = psb.getUsdStage(); + if (stage) { + removeLayer(stage->GetRootLayer()); + removeLayer(stage->GetSessionLayer()); + } +} + +void LayerDatabase::setBatchSaveDelegate(BatchSaveDelegate delegate) +{ + _batchSaveDelegate = delegate; +} + +void LayerDatabase::prepareForWriteCheck(bool* retCode, void*) +{ + cleanUpNewScene(nullptr); + + if (LayerDatabase::instance().getStagesToSave()) { + + int dialogResult = true; + + if (MGlobal::kInteractive == MGlobal::mayaState()) { + MGlobal::executeCommand(kSaveOptionUICmd, dialogResult); + } + + if (dialogResult) { + dialogResult = LayerDatabase::instance().saveUsd(); + } + + *retCode = dialogResult; + } else { + *retCode = true; + } +} + +bool LayerDatabase::getStagesToSave() +{ + auto allStages = MayaUsd::ufe::getAllStages(); + bool checkSelection = (MFileIO::kExportTypeSelected == MFileIO::exportType()); + const UFE_NS::GlobalSelection::Ptr& ufeSelection = UFE_NS::GlobalSelection::get(); + + _stagesToSave.clear(); + for (const auto& stage : allStages) { + auto stagePath = MayaUsd::ufe::stagePath(stage); + if (!checkSelection + || (ufeSelection->contains(stagePath) || ufeSelection->containsAncestor(stagePath))) { + // Remove the leading "|world| component. + std::string strStagePath = stagePath.popHead().string(); + MDagPath proxyDagPath = UsdMayaUtil::nameToDagPath(strStagePath); + MFnDependencyNode fn(proxyDagPath.node()); + if (!fn.isFromReferencedFile() && supportedNodeType(fn.typeId())) { + SdfLayerHandleVector allLayers = stage->GetLayerStack(true); + for (auto layer : allLayers) { + if (layer->IsDirty()) { + _stagesToSave.push_back(stage); + break; + } + } + } + } + } + + return !_stagesToSave.empty(); +} + +bool LayerDatabase::saveUsd() +{ + bool result = true; + + auto opt = UsdMayaSerialization::serializeUsdEditsLocationOption(); + + if (UsdMayaSerialization::kIgnoreUSDEdits != opt) { + if (_batchSaveDelegate) { + result = _batchSaveDelegate(_stagesToSave); + } + + if (result) { + if (UsdMayaSerialization::kSaveToUSDFiles == opt) { + result = saveUsdToUsdFiles(); + } else { + result = saveUsdToMayaFile(); + } + } + } + + _stagesToSave.clear(); + return result; +} + +bool LayerDatabase::saveUsdToMayaFile() +{ + MayaUsd::LayerManager* lm = findNode(); + if (!lm) { + MDGModifier modifier; + MObject manager = modifier.createNode(MayaUsd::LayerManager::typeId); + modifier.doIt(); + + lm = static_cast(MFnDependencyNode(manager).userNode()); + } + + if (!lm) { + return false; + } + + MStatus status; + MDataBlock dataBlock = lm->_forceCache(); + MArrayDataHandle layersHandle = dataBlock.outputArrayValue(lm->layers, &status); + MArrayDataBuilder builder(&dataBlock, lm->layers, 1 /*maybe nb stages?*/, &status); + + bool atLeastOneDirty = false; + + MFnDependencyNode fn; + MItDependencyNodes iter(MFn::kPluginDependNode); + for (; !iter.isDone(); iter.next()) { + MObject mobj = iter.item(); + fn.setObject(mobj); + if (!fn.isFromReferencedFile() + && LayerDatabase::instance().supportedNodeType(fn.typeId())) { + MayaUsdProxyShapeBase* pShape = static_cast(fn.userNode()); + UsdStageRefPtr stage = pShape ? pShape->getUsdStage() : nullptr; + if (!stage) { + continue; + } + + bool thisStageHasDirtyLayer = false; + + std::string temp; + SdfLayerHandleVector allLayers = stage->GetLayerStack(true); + for (auto layer : allLayers) { + MDataHandle layersElemHandle = builder.addLast(&status); + MDataHandle idHandle = layersElemHandle.child(lm->identifier); + MDataHandle serializedHandle = layersElemHandle.child(lm->serialized); + MDataHandle anonHandle = layersElemHandle.child(lm->anonymous); + + idHandle.setString(UsdMayaUtil::convert(layer->GetIdentifier())); + anonHandle.setBool(layer->IsAnonymous()); + + if (layer->IsDirty()) { + atLeastOneDirty = true; + thisStageHasDirtyLayer = true; + layer->ExportToString(&temp); + } else { + temp.clear(); + } + serializedHandle.setString(UsdMayaUtil::convert(temp)); + } + layersHandle.set(builder); + + if (thisStageHasDirtyLayer) { + auto sessionLayer = stage->GetSessionLayer(); + auto usdIdentifier = sessionLayer->GetIdentifier(); + MString idString = UsdMayaUtil::convert(usdIdentifier); + + MPlug sessionLayerNamePlug(mobj, MayaUsdProxyShapeBase::sessionLayerNameAttr); + sessionLayerNamePlug.setValue(idString); + + auto rootLayer = stage->GetRootLayer(); + usdIdentifier = rootLayer->GetIdentifier(); + idString = UsdMayaUtil::convert(usdIdentifier); + + MPlug rootLayerNamePlug(mobj, MayaUsdProxyShapeBase::rootLayerNameAttr); + rootLayerNamePlug.setValue(idString); + } + } + } + layersHandle.setAllClean(); + dataBlock.setClean(lm->layers); + + if (!atLeastOneDirty) { + MDGModifier modifier; + modifier.deleteNode(lm->thisMObject()); + modifier.doIt(); + } + + return true; +} + +bool LayerDatabase::saveUsdToUsdFiles() +{ + MFnDependencyNode fn; + MItDependencyNodes iter(MFn::kPluginDependNode); + for (; !iter.isDone(); iter.next()) { + MObject mobj = iter.item(); + fn.setObject(mobj); + if (!fn.isFromReferencedFile() + && LayerDatabase::instance().supportedNodeType(fn.typeId())) { + MayaUsdProxyShapeBase* pShape = static_cast(fn.userNode()); + UsdStageRefPtr stage = pShape ? pShape->getUsdStage() : nullptr; + if (!stage) { + continue; + } + + convertAnonymousLayers(pShape, stage); + + SdfLayerHandleVector allLayers = stage->GetLayerStack(false); + for (auto layer : allLayers) { + layer->Save(); + } + } + } + + return true; +} + +void LayerDatabase::convertAnonymousLayers(MayaUsdProxyShapeBase* pShape, UsdStageRefPtr stage) +{ + SdfLayerHandle root = stage->GetRootLayer(); + std::string proxyName(pShape->name().asChar()); + + convertAnonymousLayersRecursive(root, proxyName, stage); + + if (root->IsAnonymous()) { + PXR_NS::SdfFileFormat::FileFormatArguments args; + args["format"] = UsdMayaSerialization::usdFormatArgOption(); + std::string newFileName = UsdMayaSerialization::generateUniqueFileName(proxyName); + root->Export(newFileName, "", args); + + setNewProxyPath(pShape->name(), UsdMayaUtil::convert(newFileName)); + } + + SdfLayerHandle session = stage->GetSessionLayer(); + if (!session->IsEmpty()) { + convertAnonymousLayersRecursive(session, proxyName, stage); + + saveSessionLayer(pShape, stage); + } +} + +void LayerDatabase::saveSessionLayer(MayaUsdProxyShapeBase* pShape, UsdStageRefPtr stage) +{ + MayaUsd::LayerManager* lm = findNode(); + if (!lm) { + MDGModifier modifier; + MObject manager = modifier.createNode(MayaUsd::LayerManager::typeId); + modifier.doIt(); + + lm = static_cast(MFnDependencyNode(manager).userNode()); + } + + if (!lm) + return; + + MStatus status; + MDataBlock dataBlock = lm->_forceCache(); + MArrayDataHandle layersHandle = dataBlock.outputArrayValue(lm->layers, &status); + MArrayDataBuilder builder(&dataBlock, lm->layers, 1 /*maybe nb stages?*/, &status); + + std::string temp; + SdfLayerHandle session = stage->GetSessionLayer(); + + MDataHandle layersElemHandle = builder.addLast(&status); + MDataHandle idHandle = layersElemHandle.child(lm->identifier); + MDataHandle serializedHandle = layersElemHandle.child(lm->serialized); + MDataHandle anonHandle = layersElemHandle.child(lm->anonymous); + + idHandle.setString(UsdMayaUtil::convert(session->GetIdentifier())); + anonHandle.setBool(true); + + session->ExportToString(&temp); + + serializedHandle.setString(UsdMayaUtil::convert(temp)); + + layersHandle.set(builder); + + layersHandle.setAllClean(); + dataBlock.setClean(lm->layers); +} + +void LayerDatabase::loadLayersPostRead(void*) +{ + MayaUsd::LayerManager* lm = findNode(); + if (!lm) + return; + + const char* identifierTempSuffix = "_tmp"; + MStatus status; + MPlug allLayersPlug(lm->thisMObject(), lm->layers); + MPlug singleLayerPlug; + MPlug idPlug; + MPlug anonymousPlug; + MPlug serializedPlug; + std::string identifierVal; + std::string serializedVal; + SdfLayerRefPtr layer; + std::vector createdLayers; + + const unsigned int numElements = allLayersPlug.numElements(); + for (unsigned int i = 0; i < numElements; ++i) { + layer = nullptr; + + singleLayerPlug = allLayersPlug.elementByPhysicalIndex(i, &status); + idPlug = singleLayerPlug.child(lm->identifier, &status); + anonymousPlug = singleLayerPlug.child(lm->anonymous, &status); + serializedPlug = singleLayerPlug.child(lm->serialized, &status); + + identifierVal = idPlug.asString(MDGContext::fsNormal, &status).asChar(); + if (identifierVal.empty()) { + MGlobal::displayError( + MString("Error - plug ") + idPlug.partialName(true) + "had empty identifier"); + continue; + } + + bool layerContainsEdits = true; + serializedVal = serializedPlug.asString(MDGContext::fsNormal, &status).asChar(); + if (serializedVal.empty()) { + layerContainsEdits = false; + } + + bool isAnon = anonymousPlug.asBool(MDGContext::fsNormal, &status); + if (isAnon) { + // Note that the new identifier will not match the old identifier - only the "tag" + // will be retained + layer + = SdfLayer::CreateAnonymous(SdfLayer::GetDisplayNameFromIdentifier(identifierVal)); + } else { + SdfLayerHandle layerHandle = SdfLayer::Find(identifierVal); + if (layerHandle) { + layer = layerHandle; + } else { + // TODO: currently, there is a small window here, after the find, and before the + // New, where another process might sneak in and create a layer with the same + // identifier, which could cause an error. This seems unlikely, but we have a + // discussion with Pixar to find a way to avoid this. + + SdfFileFormatConstPtr fileFormat + = getFileFormatForLayer(identifierVal, serializedVal); + + if (layerContainsEdits) { + // In order to make the layer reloadable by SdfLayer::Reload(), we hack the + // identifier with temp one on creation and call layer->SetIdentifier() + // again to set the timestamp: + layer = SdfLayer::New(fileFormat, identifierVal + identifierTempSuffix); + if (!layer) { + MGlobal::displayError( + MString("Error - failed to create new layer for identifier '") + + identifierVal.c_str() + "' for plug " + idPlug.partialName(true)); + continue; + } + layer->SetIdentifier( + identifierVal); // Make it reloadable by SdfLayer::Reload(true) + layer->Clear(); // Mark it dirty to make it reloadable by SdfLayer::Reload() + // without force=true + } else { + layer = SdfLayer::FindOrOpen(identifierVal); + } + } + } + + if (layer) { + if (layerContainsEdits) { + if (!layer->ImportFromString(serializedVal)) { + MGlobal::displayError( + MString("Failed to import serialized layer: ") + serializedVal.c_str()); + continue; + } + } + + LayerDatabase::instance().addLayer(layer, identifierVal); + createdLayers.push_back(layer); + } + } + + removeManagerNode(lm); + + for (auto it = createdLayers.begin(); it != createdLayers.end(); ++it) { + SdfLayerHandle lh = (*it); + LayerDatabase::instance().remapSubLayerPaths(lh); + } +} + +void LayerDatabase::cleanUpNewScene(void*) +{ + LayerDatabase::instance().removeAllLayers(); + LayerDatabase::removeManagerNode(); +} + +bool LayerDatabase::remapSubLayerPaths(SdfLayerHandle parentLayer) +{ + bool modifiedPaths = false; + std::vector paths = parentLayer->GetSubLayerPaths(); + for (size_t i = 0, n = paths.size(); i < n; ++i) { + SdfLayerRefPtr subLayer = findLayer(paths[i]); + if (subLayer) { + if (subLayer->GetIdentifier() != paths[i]) { + paths[i] = subLayer->GetIdentifier(); + modifiedPaths = true; + } + } + } + + if (modifiedPaths) { + parentLayer->SetSubLayerPaths(paths); + } + + return modifiedPaths; +} + +bool LayerDatabase::addLayer(SdfLayerRefPtr layer, const std::string& identifier) +{ + _addLayer(layer, layer->GetIdentifier()); + if (identifier != layer->GetIdentifier() && !identifier.empty()) { + _addLayer(layer, identifier); + } + + return true; +} + +void LayerDatabase::_addLayer(SdfLayerRefPtr layer, const std::string& identifier) +{ + auto insertIdResult = _idToLayer.emplace(identifier, layer); + if (!insertIdResult.second) { + insertIdResult.first->second = layer; + } +} + +bool LayerDatabase::removeLayer(SdfLayerRefPtr layer) +{ + std::vector paths = layer->GetSubLayerPaths(); + for (auto pathName : paths) { + SdfLayerRefPtr childLayer = findLayer(pathName); + if (childLayer) { + removeLayer(childLayer); + } + } + + auto iter = _idToLayer.begin(); + while (iter != _idToLayer.end()) { + if ((*iter).second == layer) + iter = _idToLayer.erase(iter); + else + iter++; + } + + return true; +} + +void LayerDatabase::removeAllLayers() { _idToLayer.clear(); } + +SdfLayerHandle LayerDatabase::findLayer(std::string identifier) const +{ + auto foundIdAndLayer = _idToLayer.find(identifier); + if (foundIdAndLayer != _idToLayer.end()) { + return foundIdAndLayer->second; + } + + return SdfLayerHandle(); +} + +void LayerDatabase::removeManagerNode(MayaUsd::LayerManager* lm) +{ + if (!lm) { + lm = findNode(); + } + if (!lm) { + return; + } + + MStatus status; + MPlug arrayPlug(lm->thisMObject(), lm->layers); + + // First, disconnect any connected attributes + disconnectCompoundArrayPlug(arrayPlug); + + // Then wipe the array attribute + MDataBlock dataBlock = lm->_forceCache(); + MArrayDataHandle layersArrayHandle = dataBlock.outputArrayValue(lm->layers, &status); + + MArrayDataBuilder builder(&dataBlock, lm->layers, 0, &status); + layersArrayHandle.set(builder); + layersArrayHandle.setAllClean(); + dataBlock.setClean(lm->layers); + + MDGModifier modifier; + modifier.deleteNode(lm->thisMObject()); + modifier.doIt(); +} + +//---------------------------------------------------------------------------------------------------------------------- + +const MString LayerManager::typeName("mayaUsdLayerManager"); +const MTypeId LayerManager::typeId(0x58000097); + +MObject LayerManager::layers = MObject::kNullObj; +MObject LayerManager::identifier = MObject::kNullObj; +MObject LayerManager::serialized = MObject::kNullObj; +MObject LayerManager::anonymous = MObject::kNullObj; + +/* static */ +void LayerManager::SetBatchSaveDelegate(BatchSaveDelegate delegate) +{ + LayerDatabase::setBatchSaveDelegate(delegate); +} + +/* static */ +void* LayerManager::creator() { return new LayerManager(); } + +/* static */ +MStatus LayerManager::initialize() +{ + try { + MStatus stat; + MFnTypedAttribute fn_str; + MFnStringData stringData; + + identifier = fn_str.create("identifier", "id", MFnData::kString, MObject::kNullObj, &stat); + CHECK_MSTATUS_AND_RETURN_IT(stat); + fn_str.setCached(true); + fn_str.setReadable(true); + fn_str.setStorable(true); + fn_str.setHidden(true); + stat = addAttribute(identifier); + CHECK_MSTATUS_AND_RETURN_IT(stat); + + serialized = fn_str.create("serialized", "szd", MFnData::kString, MObject::kNullObj, &stat); + CHECK_MSTATUS_AND_RETURN_IT(stat); + fn_str.setCached(true); + fn_str.setReadable(true); + fn_str.setStorable(true); + fn_str.setHidden(true); + stat = addAttribute(serialized); + CHECK_MSTATUS_AND_RETURN_IT(stat); + + MFnNumericAttribute fn_bool; + anonymous = fn_bool.create("anonymous", "ann", MFnNumericData::kBoolean, false, &stat); + CHECK_MSTATUS_AND_RETURN_IT(stat); + fn_bool.setCached(true); + fn_bool.setReadable(true); + fn_bool.setStorable(true); + fn_bool.setHidden(true); + stat = addAttribute(anonymous); + CHECK_MSTATUS_AND_RETURN_IT(stat); + + MFnCompoundAttribute fn_cmp; + layers = fn_cmp.create("layers", "lyr", &stat); + CHECK_MSTATUS_AND_RETURN_IT(stat); + + stat = fn_cmp.addChild(identifier); + CHECK_MSTATUS_AND_RETURN_IT(stat); + + stat = fn_cmp.addChild(serialized); + CHECK_MSTATUS_AND_RETURN_IT(stat); + + stat = fn_cmp.addChild(anonymous); + CHECK_MSTATUS_AND_RETURN_IT(stat); + + fn_cmp.setCached(true); + fn_cmp.setReadable(true); + fn_cmp.setWritable(true); + fn_cmp.setStorable(true); + fn_cmp.setConnectable(true); + fn_cmp.setHidden(true); + fn_cmp.setArray(true); + fn_cmp.setUsesArrayDataBuilder(true); + stat = addAttribute(layers); + CHECK_MSTATUS_AND_RETURN_IT(stat); + + } catch (const MStatus& status) { + return status; + } + + return MS::kSuccess; +} + +LayerManager::LayerManager() + : MPxNode() +{ +} + +LayerManager::~LayerManager() { } + +/* static */ +SdfLayerHandle LayerManager::findLayer(std::string identifier) +{ + std::lock_guard lock(findNodeMutex); + + LayerDatabase::loadLayersPostRead(nullptr); + + return LayerDatabase::instance().findLayer(identifier); +} + +/* static */ +void LayerManager::addSupportForNodeType(MTypeId type) +{ + return LayerDatabase::instance().addSupportForNodeType(type); +} + +/* static */ +void LayerManager::removeSupportForNodeType(MTypeId type) +{ + return LayerDatabase::instance().removeSupportForNodeType(type); +} + +bool LayerManager::supportedNodeType(MTypeId nodeId) +{ + return LayerDatabase::instance().supportedNodeType(nodeId); +} + +} // namespace MAYAUSD_NS_DEF diff --git a/lib/mayaUsd/nodes/layerManager.h b/lib/mayaUsd/nodes/layerManager.h new file mode 100644 index 0000000000..2689843d31 --- /dev/null +++ b/lib/mayaUsd/nodes/layerManager.h @@ -0,0 +1,117 @@ +// +// Copyright 2020 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 MAYA_USD_LAYER_MANAGER +#define MAYA_USD_LAYER_MANAGER + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace MAYAUSD_NS_DEF { + +typedef std::function&)> BatchSaveDelegate; + +/*! \brief Maya dependency node responsible for serializing unsaved Usd edits. + + In a pre-save or export callback, Maya will check if there are any proxy shapes of a supported + type that have UsdStages with dirty Layers in them. If stages are found, there are currently + three options for how to handle the Usd edits: + + 1. Save back to .usd files. + There are three steps to handling this option. + a) All anonymous layers must be saved to disk. If a batch save delegate has been installed, + and Maya is running in interactive mode, then a UI dialog can be displayed to provide a choice + of file names and locations for all anonymous layers. If Maya is not running in interactive + mode, or there is no installed delegate to handle it, then Maya will automatically choose name + and locations for all anonymous layers. + b) All file-backed Usd layers will be saved. + c) The Session Layer, if dirty, is handled as a special case. It will be exported to a string + and saved into the Maya file as an attribute on the LayerManager node. When reading back in + this file the Session Layer will be restored when recreating the Usd Stage. + + 2. Save into the Maya file. + With this option, if there are any Usd layers with unsaved edits, a single LayerManager + node will be created that stores the Usd identifiers of all layers under the parent Proxy Shape + as well as the dirty Usd layer itself exported to a string. Dirty layers will include any + anonymous layers, a Session layer with edits, and any file-backed Usd layers with edits that + have not been saved to disk. + + 3. Ignore all Usd edits. + With this option, Maya will not attempt to save any dirty Usd layers, assuming the user is + explicitly managing the state themselves. + + \note The LayerManager will only consider Usd stages that exist under a supported Proxy Shape + class derived from MayaUsdProxyShapeBase and that has requested the support by adding the + shapes MTypeId by calling LayerManager::addSupportForNodeType(MTypeId). +*/ +class MAYAUSD_CORE_PUBLIC LayerManager : public MPxNode +{ +public: + /*! \brief Set a callback function to handle saving of Usd edits. In a default build of the + plugin a delegate will be installed that posts a UI dialog that provides an opportunity + to choose file names and locations of all anonymous layers that need to be saved to disk. + */ + static void SetBatchSaveDelegate(BatchSaveDelegate delegate); + + /*! \brief Called by any MayaUsdProxyShapeBase derived class that wants to be included in the + LayerManager serialization of Usd edits. \param nodeId The MTypeId of the derived Proxy Shape + node that should be supported. + */ + static void addSupportForNodeType(MTypeId nodeId); + static void removeSupportForNodeType(MTypeId nodeId); + static bool supportedNodeType(MTypeId nodeId); + + /*! \brief Supported Proxy Shapes should call this to possibly retrieve their Root and Session + layers before calling Sdf::FindOrOpen. If a handle is found and returned then it will be the + recreated layer, and all sublayers, with edits from a previous Maya session and should be + used to initialize the Proxy Shape in a call to UsdStage::Open(). + */ + static SdfLayerHandle findLayer(std::string identifier); + + static const MString typeName; + static const MTypeId typeId; + + static void* creator(); + static MStatus initialize(); + + static MObject layers; + static MObject identifier; + static MObject serialized; + static MObject anonymous; + +protected: + LayerManager(); + ~LayerManager() override; +}; // LayerManager + +} // namespace MAYAUSD_NS_DEF + +#endif // MAYA_USD_LAYER_MANAGER diff --git a/lib/mayaUsd/nodes/proxyShapeBase.cpp b/lib/mayaUsd/nodes/proxyShapeBase.cpp index 9f41e27a43..396a5a0dd2 100644 --- a/lib/mayaUsd/nodes/proxyShapeBase.cpp +++ b/lib/mayaUsd/nodes/proxyShapeBase.cpp @@ -129,6 +129,8 @@ MObject MayaUsdProxyShapeBase::stageCacheIdAttr; MObject MayaUsdProxyShapeBase::drawRenderPurposeAttr; MObject MayaUsdProxyShapeBase::drawProxyPurposeAttr; MObject MayaUsdProxyShapeBase::drawGuidePurposeAttr; +MObject MayaUsdProxyShapeBase::sessionLayerNameAttr; +MObject MayaUsdProxyShapeBase::rootLayerNameAttr; // Output attributes MObject MayaUsdProxyShapeBase::outTimeAttr; MObject MayaUsdProxyShapeBase::outStageDataAttr; @@ -288,6 +290,22 @@ MStatus MayaUsdProxyShapeBase::initialize() retValue = addAttribute(outStageCacheIdAttr); CHECK_MSTATUS_AND_RETURN_IT(retValue); + sessionLayerNameAttr = typedAttrFn.create( + "outStageSessionLayerId", "oslid", MFnData::kString, MObject::kNullObj, &retValue); + typedAttrFn.setInternal(true); + typedAttrFn.setHidden(true); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + retValue = addAttribute(sessionLayerNameAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + rootLayerNameAttr = typedAttrFn.create( + "outStageRootLayerId", "orlid", MFnData::kString, MObject::kNullObj, &retValue); + typedAttrFn.setInternal(true); + typedAttrFn.setHidden(true); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + retValue = addAttribute(rootLayerNameAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + // // add attribute dependencies // @@ -420,6 +438,12 @@ MStatus MayaUsdProxyShapeBase::compute(const MPlug& plug, MDataBlock& dataBlock) return MS::kUnknownParameter; } +/* virtual */ +SdfLayerRefPtr MayaUsdProxyShapeBase::computeRootLayer(MDataBlock&, const std::string&) +{ + return nullptr; +} + /* virtual */ SdfLayerRefPtr MayaUsdProxyShapeBase::computeSessionLayer(MDataBlock&) { return nullptr; } @@ -548,8 +572,13 @@ MStatus MayaUsdProxyShapeBase::computeInStageDataCached(MDataBlock& dataBlock) UsdStageCacheContext ctx( UsdMayaStageCache::Get(loadSet == UsdStage::InitialLoadSet::LoadAll)); - if (SdfLayerRefPtr rootLayer = SdfLayer::FindOrOpen(fileString)) { - SdfLayerRefPtr sessionLayer = computeSessionLayer(dataBlock); + SdfLayerRefPtr sessionLayer = nullptr; + SdfLayerRefPtr rootLayer = computeRootLayer(dataBlock, fileString); + if (nullptr == rootLayer) + rootLayer = SdfLayer::FindOrOpen(fileString); + + if (rootLayer) { + sessionLayer = computeSessionLayer(dataBlock); bool targetSession = MGlobal::optionVarIntValue(UsdMayaUtil::convert( diff --git a/lib/mayaUsd/nodes/proxyShapeBase.h b/lib/mayaUsd/nodes/proxyShapeBase.h index 3ca8dde5a9..2fbceb2a24 100644 --- a/lib/mayaUsd/nodes/proxyShapeBase.h +++ b/lib/mayaUsd/nodes/proxyShapeBase.h @@ -114,6 +114,11 @@ class MayaUsdProxyShapeBase MAYAUSD_CORE_PUBLIC static MObject drawGuidePurposeAttr; + MAYAUSD_CORE_PUBLIC + static MObject sessionLayerNameAttr; + MAYAUSD_CORE_PUBLIC + static MObject rootLayerNameAttr; + // Output attributes MAYAUSD_CORE_PUBLIC static MObject outTimeAttr; @@ -283,6 +288,10 @@ class MayaUsdProxyShapeBase MAYAUSD_CORE_PUBLIC virtual SdfLayerRefPtr computeSessionLayer(MDataBlock&); + // Hook method for derived classes. This class returns a nullptr. + MAYAUSD_CORE_PUBLIC + virtual SdfLayerRefPtr computeRootLayer(MDataBlock&, const std::string&); + // Hook method for derived classes: can this object be soft selected? // This class returns false. MAYAUSD_CORE_PUBLIC diff --git a/lib/mayaUsd/nodes/proxyShapePlugin.cpp b/lib/mayaUsd/nodes/proxyShapePlugin.cpp index 12d12ed6ff..73900b00d1 100644 --- a/lib/mayaUsd/nodes/proxyShapePlugin.cpp +++ b/lib/mayaUsd/nodes/proxyShapePlugin.cpp @@ -36,6 +36,10 @@ #include #include +#if defined(WANT_UFE_BUILD) +#include +#endif + PXR_NAMESPACE_USING_DIRECTIVE namespace { @@ -103,6 +107,15 @@ MStatus MayaUsdProxyShapePlugin::initialize(MFnPlugin& plugin) MPxNode::kDeformerNode); CHECK_MSTATUS_AND_RETURN_IT(status); +#if defined(WANT_UFE_BUILD) + status = plugin.registerNode( + MayaUsd::LayerManager::typeName, + MayaUsd::LayerManager::typeId, + MayaUsd::LayerManager::creator, + MayaUsd::LayerManager::initialize); + CHECK_MSTATUS(status); +#endif + // Hybrid Hydra / VP2 rendering uses a draw override to draw the proxy // shape. The Pixar and MayaUsd plugins use the UsdMayaProxyDrawOverride, // so register it here. Native USD VP2 rendering uses a sub-scene override. @@ -192,6 +205,11 @@ MStatus MayaUsdProxyShapePlugin::finalize(MFnPlugin& plugin) status = plugin.deregisterNode(UsdMayaPointBasedDeformerNode::typeId); CHECK_MSTATUS(status); +#if defined(WANT_UFE_BUILD) + status = plugin.deregisterNode(MayaUsd::LayerManager::typeId); + CHECK_MSTATUS(status); +#endif + status = plugin.deregisterNode(UsdMayaStageNode::typeId); CHECK_MSTATUS(status); diff --git a/lib/mayaUsd/ufe/UsdStageMap.cpp b/lib/mayaUsd/ufe/UsdStageMap.cpp index 42f50b6fa6..8dd69fa325 100644 --- a/lib/mayaUsd/ufe/UsdStageMap.cpp +++ b/lib/mayaUsd/ufe/UsdStageMap.cpp @@ -122,6 +122,17 @@ Ufe::Path UsdStageMap::path(UsdStageWeakPtr stage) return Ufe::Path(); } +UsdStageMap::StageSet UsdStageMap::allStages() +{ + rebuildIfDirty(); + + StageSet stages; + for (const auto& pair : fObjectToStage) { + stages.insert(pair.second); + } + return stages; +} + void UsdStageMap::setDirty() { fObjectToStage.clear(); diff --git a/lib/mayaUsd/ufe/UsdStageMap.h b/lib/mayaUsd/ufe/UsdStageMap.h index 0095ac943e..2f086fd4b9 100644 --- a/lib/mayaUsd/ufe/UsdStageMap.h +++ b/lib/mayaUsd/ufe/UsdStageMap.h @@ -18,6 +18,8 @@ #include #include +#include +#include #include #include @@ -58,6 +60,8 @@ namespace ufe { class MAYAUSD_CORE_PUBLIC UsdStageMap { public: + typedef TfHashSet StageSet; + UsdStageMap() = default; ~UsdStageMap() = default; @@ -73,6 +77,9 @@ class MAYAUSD_CORE_PUBLIC UsdStageMap //! Return the ProxyShape node UFE path for the argument stage. Ufe::Path path(UsdStageWeakPtr stage); + //! Return all the USD stages. + StageSet allStages(); + //! Set the stage map as dirty. It will be cleared immediately, but //! only repopulated when stage info is requested. void setDirty(); diff --git a/lib/mayaUsd/ufe/Utils.cpp b/lib/mayaUsd/ufe/Utils.cpp index 5550e16959..a505287604 100644 --- a/lib/mayaUsd/ufe/Utils.cpp +++ b/lib/mayaUsd/ufe/Utils.cpp @@ -101,6 +101,8 @@ UsdStageWeakPtr getStage(const Ufe::Path& path) { return g_StageMap.stage(path); Ufe::Path stagePath(UsdStageWeakPtr stage) { return g_StageMap.path(stage); } +TfHashSet getAllStages() { return g_StageMap.allStages(); } + Ufe::PathSegment usdPathToUfePathSegment(const SdfPath& usdPath, int instanceIndex) { const Ufe::Rtid usdRuntimeId = getUsdRunTimeId(); diff --git a/lib/mayaUsd/ufe/Utils.h b/lib/mayaUsd/ufe/Utils.h index 05a092aeba..4e27340155 100644 --- a/lib/mayaUsd/ufe/Utils.h +++ b/lib/mayaUsd/ufe/Utils.h @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -55,6 +56,10 @@ UsdStageWeakPtr getStage(const Ufe::Path& path); MAYAUSD_CORE_PUBLIC Ufe::Path stagePath(UsdStageWeakPtr stage); +//! Return all the USD stages. +MAYAUSD_CORE_PUBLIC +TfHashSet getAllStages(); + //! Get the UFE path segment corresponding to the argument USD path. //! If an instanceIndex is provided, the path segment for a point instance with //! that USD path and index is returned. diff --git a/lib/mayaUsd/utils/CMakeLists.txt b/lib/mayaUsd/utils/CMakeLists.txt index eaf4e1f95c..43be1ce8c3 100644 --- a/lib/mayaUsd/utils/CMakeLists.txt +++ b/lib/mayaUsd/utils/CMakeLists.txt @@ -13,6 +13,7 @@ target_sources(${PROJECT_NAME} undoHelperCommand util.cpp utilFileSystem.cpp + utilSerialization.cpp ) set(HEADERS @@ -26,6 +27,7 @@ set(HEADERS undoHelperCommand.h util.h utilFileSystem.h + utilSerialization.h ) # ----------------------------------------------------------------------------- diff --git a/lib/mayaUsd/utils/utilFileSystem.cpp b/lib/mayaUsd/utils/utilFileSystem.cpp index dcacc235ec..90438a575e 100644 --- a/lib/mayaUsd/utils/utilFileSystem.cpp +++ b/lib/mayaUsd/utils/utilFileSystem.cpp @@ -16,11 +16,13 @@ #include "utilFileSystem.h" #include +#include #include #include #include +#include #include #include @@ -89,6 +91,30 @@ std::string UsdMayaUtilFileSystem::getMayaSceneFileDir() return std::string(); } +const char* getScenesFolderScript = R"( +global proc string UsdMayaUtilFileSystem_GetScenesFolder() +{ + string $workspaceLocation = `workspace -q -fn`; + string $scenesFolder = `workspace -q -fileRuleEntry "scene"`; + $sceneFolder = $workspaceLocation + "/" + $scenesFolder; + + return $sceneFolder; +} +UsdMayaUtilFileSystem_GetScenesFolder; +)"; + +std::string UsdMayaUtilFileSystem::getMayaWorkspaceScenesDir() +{ + MString scenesFolder; + MGlobal::executeCommand( + getScenesFolderScript, + scenesFolder, + /*display*/ false, + /*undo*/ false); + + return UsdMayaUtil::convert(scenesFolder); +} + std::string UsdMayaUtilFileSystem::resolveRelativePathWithinMayaContext( const MObject& proxyShape, const std::string& relativeFilePath) @@ -113,3 +139,16 @@ std::string UsdMayaUtilFileSystem::resolveRelativePathWithinMayaContext( return path.string(); } + +std::string UsdMayaUtilFileSystem::getUniqueFileName( + const std::string& dir, + const std::string& basename, + const std::string& ext) +{ + std::string fileNameModel = basename + "-%%%%%%." + ext; + + boost::filesystem::path pathModel(dir); + pathModel.append(fileNameModel); + + return boost::filesystem::unique_path(pathModel).generic_string(); +} diff --git a/lib/mayaUsd/utils/utilFileSystem.h b/lib/mayaUsd/utils/utilFileSystem.h index 479e372749..d2747ba30f 100644 --- a/lib/mayaUsd/utils/utilFileSystem.h +++ b/lib/mayaUsd/utils/utilFileSystem.h @@ -44,6 +44,17 @@ std::string getMayaReferencedFileDir(const MObject& proxyShapeNode); MAYAUSD_CORE_PUBLIC std::string getMayaSceneFileDir(); +/*! \brief returns the Maya workspace file rule entry for scenes + */ +MAYAUSD_CORE_PUBLIC +std::string getMayaWorkspaceScenesDir(); + +/*! \brief returns a unique file name + */ +MAYAUSD_CORE_PUBLIC +std::string +getUniqueFileName(const std::string& dir, const std::string& basename, const std::string& ext); + /*! \brief returns the aboluste path relative to the maya file */ MAYAUSD_CORE_PUBLIC diff --git a/lib/mayaUsd/utils/utilSerialization.cpp b/lib/mayaUsd/utils/utilSerialization.cpp new file mode 100644 index 0000000000..a3cde9e807 --- /dev/null +++ b/lib/mayaUsd/utils/utilSerialization.cpp @@ -0,0 +1,237 @@ +// +// Copyright 2021 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 "utilSerialization.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +class RecursionDetector +{ +public: + RecursionDetector() { } + void push(const std::string& path) { _paths.push_back(path); } + + void pop() { _paths.pop_back(); } + bool contains(const std::string& in_path) const + { + return !in_path.empty() + && std::find(_paths.cbegin(), _paths.cend(), in_path) != _paths.cend(); + } + + std::vector _paths; +}; + +namespace { + +std::string toForwardSlashes(const std::string& in_path) +{ + // it works better on windows if all the paths have forward slashes + auto path = in_path; + std::replace(path.begin(), path.end(), '\\', '/'); + return path; +} + +// contains the logic to get the right path to use for SdfLayer:::FindOrOpen +// from a sublayerpath. +// this path could be absolute, relative, or be an anon layer +std::string computePathToLoadSublayer( + const std::string& subLayerPath, + const std::string& anchor, + PXR_NS::ArResolver& resolver) +{ + std::string actualPath = subLayerPath; + if (resolver.IsRelativePath(subLayerPath)) { + auto subLayer = PXR_NS::SdfLayer::Find(subLayerPath); // note: finds in the cache + if (subLayer) { + if (!resolver.IsRelativePath(subLayer->GetIdentifier())) { + actualPath = toForwardSlashes(subLayer->GetRealPath()); + } + } else { + actualPath = resolver.AnchorRelativePath(anchor, subLayerPath); + } + } + return actualPath; +} + +void populateChildren( + SdfLayerRefPtr layer, + RecursionDetector* recursionDetector, + std::vector>& anonLayersToSave, + std::vector& dirtyLayersToSave) +{ + auto subPaths = layer->GetSubLayerPaths(); + auto& resolver = ArGetResolver(); + auto anchor = toForwardSlashes(layer->GetRealPath()); + + RecursionDetector defaultDetector; + if (!recursionDetector) { + recursionDetector = &defaultDetector; + } + recursionDetector->push(layer->GetRealPath()); + + for (auto const path : subPaths) { + std::string actualPath = computePathToLoadSublayer(path, anchor, resolver); + auto subLayer = SdfLayer::FindOrOpen(actualPath); + if (subLayer && !recursionDetector->contains(subLayer->GetRealPath())) { + populateChildren(subLayer, recursionDetector, anonLayersToSave, dirtyLayersToSave); + + if (subLayer->IsAnonymous()) { + anonLayersToSave.push_back(std::make_pair(subLayer, layer)); + } else if (subLayer->IsDirty()) { + dirtyLayersToSave.push_back(subLayer); + } + } + } + + recursionDetector->pop(); +} + +} // namespace + +std::string UsdMayaSerialization::suggestedStartFolder(UsdStageRefPtr stage) +{ + SdfLayerRefPtr root = stage ? stage->GetRootLayer() : nullptr; + if (!root && !root->IsAnonymous()) { + return root->GetRealPath(); + } + + return UsdMayaSerialization::getSceneFolder(); +} + +std::string UsdMayaSerialization::getSceneFolder() +{ + std::string fileDir = UsdMayaUtilFileSystem::getMayaSceneFileDir(); + if (fileDir.empty()) { + fileDir = UsdMayaUtilFileSystem::getMayaWorkspaceScenesDir(); + } + + return fileDir; +} + +std::string UsdMayaSerialization::generateUniqueFileName(const std::string& basename) +{ + std::string newFileName = UsdMayaUtilFileSystem::getUniqueFileName( + getSceneFolder(), + !basename.empty() ? basename : "anonymous", + UsdUsdFileFormatTokens->Id.GetText()); + return newFileName; +} + +std::string UsdMayaSerialization::usdFormatArgOption() +{ + bool binary = true; + auto formatArgsOption + = UsdMayaUtil::convert(MayaUsdOptionVars->mayaUsd_SaveLayerFormatArgBinaryOption); + if (MGlobal::optionVarExists(formatArgsOption)) { + binary = MGlobal::optionVarIntValue(formatArgsOption) != 0; + } else { + MGlobal::setOptionVarValue(formatArgsOption, 1); + } + return binary ? UsdUsdcFileFormatTokens->Id.GetText() : UsdUsdaFileFormatTokens->Id.GetText(); +} + +/* static */ +UsdMayaSerialization::USDUnsavedEditsOption UsdMayaSerialization::serializeUsdEditsLocationOption() +{ + bool optVarExists = true; + auto saveOptionVar + = UsdMayaUtil::convert(MayaUsdOptionVars->mayaUsd_SerializedUsdEditsLocation); + int saveOption = MGlobal::optionVarIntValue(saveOptionVar, &optVarExists); + + // Default is to save back to .usd files, set it to that if the optionVar doesn't exist yet. + // optionVar's are also just ints so make sure the value is a correct one. + // If we end up initializing the value then write it back to the optionVar itself. + if (!optVarExists || (saveOption < kSaveToUSDFiles || saveOption > kIgnoreUSDEdits)) { + saveOption = kSaveToUSDFiles; + MGlobal::setOptionVarValue(saveOptionVar, saveOption); + } + + if (saveOption == kSaveToMayaSceneFile) { + return kSaveToMayaSceneFile; + } else if (saveOption == kIgnoreUSDEdits) { + return kIgnoreUSDEdits; + } else { + return kSaveToUSDFiles; + } +} // namespace MAYAUSD_NS_DEF + +SdfLayerRefPtr UsdMayaSerialization::saveAnonymousLayer( + SdfLayerRefPtr anonLayer, + SdfLayerRefPtr parentLayer, + const std::string& basename, + std::string formatArg) +{ + std::string newFileName = UsdMayaSerialization::generateUniqueFileName(basename); + return saveAnonymousLayer(anonLayer, newFileName, parentLayer, formatArg); +} + +SdfLayerRefPtr UsdMayaSerialization::saveAnonymousLayer( + SdfLayerRefPtr anonLayer, + const std::string& path, + SdfLayerRefPtr parentLayer, + std::string formatArg) +{ + if (!anonLayer || !anonLayer->IsAnonymous()) { + return nullptr; + } + + PXR_NS::SdfFileFormat::FileFormatArguments args; + if (!formatArg.empty()) { + args["format"] = formatArg; + } else { + args["format"] = usdFormatArgOption(); + } + + anonLayer->Export(path, "", args); + + SdfLayerRefPtr newLayer = SdfLayer::FindOrOpen(path); + + if (parentLayer) { + parentLayer->GetSubLayerPaths().Replace( + anonLayer->GetIdentifier(), newLayer->GetIdentifier()); + } + + return newLayer; +} + +void UsdMayaSerialization::getLayersToSaveFromProxy( + UsdStageRefPtr stage, + stageLayersToSave& layersInfo) +{ + auto root = stage->GetRootLayer(); + + populateChildren(root, nullptr, layersInfo.anonLayers, layersInfo.dirtyFileBackedLayers); + if (root->IsAnonymous()) { + layersInfo.anonLayers.push_back(std::make_pair(root, nullptr)); + } else if (root->IsDirty()) { + layersInfo.dirtyFileBackedLayers.push_back(root); + } + + auto session = stage->GetSessionLayer(); + populateChildren(session, nullptr, layersInfo.anonLayers, layersInfo.dirtyFileBackedLayers); +} diff --git a/lib/mayaUsd/utils/utilSerialization.h b/lib/mayaUsd/utils/utilSerialization.h new file mode 100644 index 0000000000..6aa7056ba4 --- /dev/null +++ b/lib/mayaUsd/utils/utilSerialization.h @@ -0,0 +1,94 @@ +// +// Copyright 2021 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 +#include + +#include +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +/// General utility functions used when serializing Usd edits during a save operation +namespace UsdMayaSerialization { + +/*! \brief Helps suggest a folder to export anonymous layers to. Checks in order: + 1. File-backed root layer folder. + 2. Current Maya scene folder. + 3. Current Maya workspace scenes folder. + */ +MAYAUSD_CORE_PUBLIC +std::string suggestedStartFolder(UsdStageRefPtr stage); + +/*! \brief Queries Maya for the current workspace "scenes" folder. + */ +MAYAUSD_CORE_PUBLIC +std::string getSceneFolder(); + +MAYAUSD_CORE_PUBLIC +std::string generateUniqueFileName(const std::string& basename); + +/*! \brief Queries the Maya optionVar that decides what the internal format + of a .usd file should be, either "usdc" or "usda". + */ +MAYAUSD_CORE_PUBLIC +std::string usdFormatArgOption(); + +enum USDUnsavedEditsOption +{ + kSaveToUSDFiles = 1, + kSaveToMayaSceneFile, + kIgnoreUSDEdits +}; +/*! \brief Queries the Maya optionVar that decides which saving option Maya + shoudl use for Usd edits. + */ +MAYAUSD_CORE_PUBLIC +USDUnsavedEditsOption serializeUsdEditsLocationOption(); + +/*! \brief Save an anonymous layer to disk and update the sublayer path array + in the parent layer. + */ +MAYAUSD_CORE_PUBLIC +SdfLayerRefPtr saveAnonymousLayer( + SdfLayerRefPtr anonLayer, + SdfLayerRefPtr parentLayer, + const std::string& basename, + std::string formatArg = ""); + +/*! \brief Save an anonymous layer to disk and update the sublayer path array + in the parent layer. + */ +MAYAUSD_CORE_PUBLIC +SdfLayerRefPtr saveAnonymousLayer( + SdfLayerRefPtr anonLayer, + const std::string& path, + SdfLayerRefPtr parentLayer, + std::string formatArg = ""); + +struct stageLayersToSave +{ + std::vector> anonLayers; + std::vector dirtyFileBackedLayers; +}; + +/*! \brief Check the sublayer stack of the stage looking for any anonymnous + layers that will need to be saved. + */ +MAYAUSD_CORE_PUBLIC +void getLayersToSaveFromProxy(UsdStageRefPtr stage, stageLayersToSave& layersInfo); + +} // namespace UsdMayaSerialization diff --git a/plugin/adsk/plugin/ProxyShape.cpp b/plugin/adsk/plugin/ProxyShape.cpp index aef8af25f1..e499b75959 100644 --- a/plugin/adsk/plugin/ProxyShape.cpp +++ b/plugin/adsk/plugin/ProxyShape.cpp @@ -17,6 +17,11 @@ #include #include +#include + +#if defined(WANT_UFE_BUILD) +#include +#endif namespace MAYAUSD_NS_DEF { @@ -69,4 +74,18 @@ void ProxyShape::postConstructor() #endif } +#if defined(WANT_UFE_BUILD) +SdfLayerRefPtr ProxyShape::computeRootLayer(MDataBlock& dataBlock, const std::string& filePath) +{ + auto rootLayerName = dataBlock.inputValue(rootLayerNameAttr).asString(); + return LayerManager::findLayer(UsdMayaUtil::convert(rootLayerName)); +} + +SdfLayerRefPtr ProxyShape::computeSessionLayer(MDataBlock& dataBlock) +{ + auto sessionLayerName = dataBlock.inputValue(sessionLayerNameAttr).asString(); + return LayerManager::findLayer(UsdMayaUtil::convert(sessionLayerName)); +} +#endif + } // namespace MAYAUSD_NS_DEF diff --git a/plugin/adsk/plugin/ProxyShape.h b/plugin/adsk/plugin/ProxyShape.h index a21525ae2d..a135476c89 100644 --- a/plugin/adsk/plugin/ProxyShape.h +++ b/plugin/adsk/plugin/ProxyShape.h @@ -44,6 +44,15 @@ class ProxyShape : public MayaUsdProxyShapeBase void postConstructor() override; +protected: +#if defined(WANT_UFE_BUILD) + MAYAUSD_PLUGIN_PUBLIC + SdfLayerRefPtr computeRootLayer(MDataBlock&, const std::string&) override; + + MAYAUSD_PLUGIN_PUBLIC + SdfLayerRefPtr computeSessionLayer(MDataBlock&) override; +#endif + private: ProxyShape(); diff --git a/plugin/adsk/plugin/plugin.cpp b/plugin/adsk/plugin/plugin.cpp index 3f1cc6860e..c4b7a02c18 100644 --- a/plugin/adsk/plugin/plugin.cpp +++ b/plugin/adsk/plugin/plugin.cpp @@ -55,6 +55,7 @@ #endif #if defined(WANT_UFE_BUILD) +#include #include #ifdef UFE_V2_FEATURES_AVAILABLE @@ -71,6 +72,10 @@ #include #endif +#if defined(WANT_QT_BUILD) +#include +#endif + #endif #if defined(MAYAUSD_VERSION) @@ -315,6 +320,13 @@ MStatus initializePlugin(MObject obj) } } +#if defined(WANT_UFE_BUILD) + MayaUsd::LayerManager::addSupportForNodeType(MAYAUSD_NS::ProxyShape::typeId); +#if defined(WANT_QT_BUILD) + MayaUsd::LayerManager::SetBatchSaveDelegate(UsdLayerEditor::batchSaveLayersUIDelegate); +#endif +#endif + UsdMayaSceneResetNotice::InstallListener(); UsdMayaDiagnosticDelegate::InstallDelegate(); @@ -399,6 +411,13 @@ MStatus uninitializePlugin(MObject obj) CHECK_MSTATUS(status); #endif +#if defined(WANT_UFE_BUILD) + MayaUsd::LayerManager::removeSupportForNodeType(MAYAUSD_NS::ProxyShape::typeId); +#if defined(WANT_QT_BUILD) + MayaUsd::LayerManager::SetBatchSaveDelegate(nullptr); +#endif +#endif + UsdMayaSceneResetNotice::RemoveListener(); UsdMayaDiagnosticDelegate::RemoveDelegate(); From 43059b3cca2a5976a01dd8defb6efc6f87aa5f6b Mon Sep 17 00:00:00 2001 From: Sean Donnelly <23455376+seando-adsk@users.noreply.github.com> Date: Wed, 3 Feb 2021 16:48:14 -0500 Subject: [PATCH 2/6] MAYA-109073 - As a user, I'd like to have my .usd File Format (binary or ASCII) in the Layer Editor Step 2 of 3: * Renamed "Option" menu to "Options". * Added new submenu for save .usd file format. * No longer force Maya native file dialog when saving layers. MAYA-109066 - Improvements to the Bulk Save UI MAYA-109367 - Bulk Save UI supporting Multi proxyShapes Step 2 of 3: * Modified the save dialog to work with single stage and multiple stages. Combined the anonymous and dirty file backed layers into single dialog. --- lib/usd/ui/importDialog/TreeItem.cpp | 18 +- lib/usd/ui/importDialog/images/ui.qrc | 2 +- lib/usd/ui/layerEditor/CMakeLists.txt | 27 + .../layerEditor/batchSaveLayersUIDelegate.cpp | 39 ++ .../layerEditor/batchSaveLayersUIDelegate.h | 36 ++ lib/usd/ui/layerEditor/layerEditorWidget.cpp | 44 +- lib/usd/ui/layerEditor/layerTreeModel.cpp | 92 +--- lib/usd/ui/layerEditor/mayaSessionState.cpp | 63 +-- lib/usd/ui/layerEditor/mayaSessionState.h | 1 + lib/usd/ui/layerEditor/saveLayersDialog.cpp | 501 ++++++++++++------ lib/usd/ui/layerEditor/saveLayersDialog.h | 64 ++- lib/usd/ui/layerEditor/stringResources.h | 28 +- 12 files changed, 607 insertions(+), 308 deletions(-) create mode 100644 lib/usd/ui/layerEditor/batchSaveLayersUIDelegate.cpp create mode 100644 lib/usd/ui/layerEditor/batchSaveLayersUIDelegate.h diff --git a/lib/usd/ui/importDialog/TreeItem.cpp b/lib/usd/ui/importDialog/TreeItem.cpp index 34ce5ad7fb..d2cd266d2c 100644 --- a/lib/usd/ui/importDialog/TreeItem.cpp +++ b/lib/usd/ui/importDialog/TreeItem.cpp @@ -49,17 +49,19 @@ const QPixmap& TreeItem::checkImage() const if (fsCheckBoxOn == nullptr) { const TreeModel* treeModel = qobject_cast(model()); if (treeModel != nullptr) { - fsCheckBoxOn = treeModel->mayaQtUtil().createPixmap("checkboxOn.png"); - fsCheckBoxOnDisabled = treeModel->mayaQtUtil().createPixmap("checkboxOnDisabled.png"); - fsCheckBoxOff = treeModel->mayaQtUtil().createPixmap("checkboxOff.png"); - fsCheckBoxOffDisabled = treeModel->mayaQtUtil().createPixmap("checkboxOffDisabled.png"); + fsCheckBoxOn = treeModel->mayaQtUtil().createPixmap(":/ImportDialog/checkboxOn.png"); + fsCheckBoxOnDisabled + = treeModel->mayaQtUtil().createPixmap(":/ImportDialog/checkboxOnDisabled.png"); + fsCheckBoxOff = treeModel->mayaQtUtil().createPixmap(":/ImportDialog/checkboxOff.png"); + fsCheckBoxOffDisabled + = treeModel->mayaQtUtil().createPixmap(":/ImportDialog/checkboxOffDisabled.png"); } else { // The tree model should never be null, but we can recover here if it is. TF_RUNTIME_ERROR("Unexpected null tree model"); - fsCheckBoxOn = new QPixmap(":/checkboxOn.png"); - fsCheckBoxOnDisabled = new QPixmap(":/checkboxOnDisabled.png"); - fsCheckBoxOff = new QPixmap(":/checkboxOff.png"); - fsCheckBoxOffDisabled = new QPixmap(":/checkboxOffDisabled.png"); + fsCheckBoxOn = new QPixmap(":/ImportDialog/checkboxOn.png"); + fsCheckBoxOnDisabled = new QPixmap(":/ImportDialog/checkboxOnDisabled.png"); + fsCheckBoxOff = new QPixmap(":/ImportDialog/checkboxOff.png"); + fsCheckBoxOffDisabled = new QPixmap(":/ImportDialog/checkboxOffDisabled.png"); } } diff --git a/lib/usd/ui/importDialog/images/ui.qrc b/lib/usd/ui/importDialog/images/ui.qrc index 97c6a6ab31..68fa235810 100644 --- a/lib/usd/ui/importDialog/images/ui.qrc +++ b/lib/usd/ui/importDialog/images/ui.qrc @@ -1,5 +1,5 @@ - + checkboxOff_100.png checkboxOffDisabled_100.png checkboxOn_100.png diff --git a/lib/usd/ui/layerEditor/CMakeLists.txt b/lib/usd/ui/layerEditor/CMakeLists.txt index e41d1402b4..5a6eadf74e 100644 --- a/lib/usd/ui/layerEditor/CMakeLists.txt +++ b/lib/usd/ui/layerEditor/CMakeLists.txt @@ -54,3 +54,30 @@ target_sources(${PROJECT_NAME} warningDialogs.h ) +if (UFE_FOUND) + target_sources(${PROJECT_NAME} + PRIVATE + batchSaveLayersUIDelegate.cpp + ) + + # ----------------------------------------------------------------------------- + # promoted headers + # ----------------------------------------------------------------------------- + set(HEADERS + batchSaveLayersUIDelegate.h + ) + + mayaUsd_promoteHeaderList( + HEADERS + ${HEADERS} + BASEDIR + ${PROJECT_NAME}/ui + ) + + # ----------------------------------------------------------------------------- + # install + # ----------------------------------------------------------------------------- + install(FILES ${HEADERS} + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/${PROJECT_NAME}/ui + ) +endif() diff --git a/lib/usd/ui/layerEditor/batchSaveLayersUIDelegate.cpp b/lib/usd/ui/layerEditor/batchSaveLayersUIDelegate.cpp new file mode 100644 index 0000000000..bc95c2d00d --- /dev/null +++ b/lib/usd/ui/layerEditor/batchSaveLayersUIDelegate.cpp @@ -0,0 +1,39 @@ +// +// Copyright 2020 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 "batchSaveLayersUIDelegate.h" + +#include "saveLayersDialog.h" + +#include +#include + +#include + +bool UsdLayerEditor::batchSaveLayersUIDelegate(const std::vector& stages) +{ + if (MGlobal::kInteractive == MGlobal::mayaState()) { + auto opt = UsdMayaSerialization::serializeUsdEditsLocationOption(); + if (UsdMayaSerialization::kSaveToUSDFiles == opt) { + UsdLayerEditor::SaveLayersDialog dlg(nullptr, stages); + if (QDialog::Accepted != dlg.exec()) { + return false; + } + } + } + + return true; +} diff --git a/lib/usd/ui/layerEditor/batchSaveLayersUIDelegate.h b/lib/usd/ui/layerEditor/batchSaveLayersUIDelegate.h new file mode 100644 index 0000000000..11c1ad8fa7 --- /dev/null +++ b/lib/usd/ui/layerEditor/batchSaveLayersUIDelegate.h @@ -0,0 +1,36 @@ +// +// Copyright 2020 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 BATCH_SAVE_LAYERS_UI_H +#define BATCH_SAVE_LAYERS_UI_H + +#include +#include + +#include +#include + +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace UsdLayerEditor { + +MAYAUSD_UI_PUBLIC +bool batchSaveLayersUIDelegate(const std::vector&); + +} // namespace UsdLayerEditor + +#endif // BATCH_SAVE_LAYERS_UI_H diff --git a/lib/usd/ui/layerEditor/layerEditorWidget.cpp b/lib/usd/ui/layerEditor/layerEditorWidget.cpp index d1fae40b43..1110f8d7ff 100644 --- a/lib/usd/ui/layerEditor/layerEditorWidget.cpp +++ b/lib/usd/ui/layerEditor/layerEditorWidget.cpp @@ -25,8 +25,14 @@ #include "stageSelectorWidget.h" #include "stringResources.h" +#include +#include + +#include + #include #include +#include #include #include #include @@ -37,6 +43,8 @@ #include #include +PXR_NAMESPACE_USING_DIRECTIVE + namespace { using namespace UsdLayerEditor; @@ -112,7 +120,8 @@ void setupDefaultMenu(SessionState* in_sessionState, QMainWindow* in_parent) // the time to be created QObject::connect(createMenu, &QMenu::aboutToShow, in_parent, aboutToShowCallback); - auto optionMenu = menuBar->addMenu(StringResources::getAsQString(StringResources::kOption)); + auto optionMenu + = menuBar->addMenu(StringResources::getAsQString(StringResources::kOptions)); auto action = optionMenu->addAction( StringResources::getAsQString(StringResources::kAutoHideSessionLayer)); QObject::connect( @@ -120,6 +129,39 @@ void setupDefaultMenu(SessionState* in_sessionState, QMainWindow* in_parent) action->setCheckable(true); action->setChecked(in_sessionState->autoHideSessionLayer()); + auto usdSaveMenu = optionMenu->addMenu( + StringResources::getAsQString(StringResources::kUsdSaveFileFormat)); + auto formatGroup = new QActionGroup(usdSaveMenu); + formatGroup->setExclusive(true); + auto formatBinary + = formatGroup->addAction(StringResources::getAsQString(StringResources::kBinary)); + auto formatAscii + = formatGroup->addAction(StringResources::getAsQString(StringResources::kAscii)); + auto grpAnn = StringResources::getAsQString(StringResources::kSaveLayerUsdFileFormatAnn); + auto grpSbm = StringResources::getAsQString(StringResources::kSaveLayerUsdFileFormatSbm); + formatBinary->setCheckable(true); + formatBinary->setData(1); + formatBinary->setToolTip(grpAnn); + formatBinary->setStatusTip(grpSbm); + formatAscii->setCheckable(true); + formatAscii->setData(0); + formatAscii->setToolTip(grpAnn); + formatAscii->setStatusTip(grpSbm); + usdSaveMenu->addAction(formatBinary); + usdSaveMenu->addAction(formatAscii); + bool isBinary = true; + + auto formatArgsOption + = UsdMayaUtil::convert(MayaUsdOptionVars->mayaUsd_SaveLayerFormatArgBinaryOption); + if (MGlobal::optionVarExists(formatArgsOption)) { + isBinary = MGlobal::optionVarIntValue(formatArgsOption) != 0; + } + isBinary ? formatBinary->setChecked(true) : formatAscii->setChecked(true); + auto onFileFormatChanged = [=](QAction* action) { + MGlobal::setOptionVarValue(formatArgsOption, action->data().toInt()); + }; + QObject::connect(formatGroup, &QActionGroup::triggered, in_parent, onFileFormatChanged); + auto helpMenu = menuBar->addMenu(StringResources::getAsQString(StringResources::kHelp)); helpMenu->addAction( StringResources::getAsQString(StringResources::kHelpOnUSDLayerEditor), diff --git a/lib/usd/ui/layerEditor/layerTreeModel.cpp b/lib/usd/ui/layerEditor/layerTreeModel.cpp index e87a02da8f..105f0b25cf 100644 --- a/lib/usd/ui/layerEditor/layerTreeModel.cpp +++ b/lib/usd/ui/layerEditor/layerTreeModel.cpp @@ -410,81 +410,31 @@ LayerTreeModel::getAllAnonymousLayers(const LayerTreeItem* item /* = nullptr*/) void LayerTreeModel::saveStage(QWidget* in_parent) { - QString dialogTitle = StringResources::getAsQString(StringResources::kSaveStage); - QString message; - - const auto anonLayerItems = getAllAnonymousLayers(); - auto nbAnon = anonLayerItems.size(); - if (0 < nbAnon) { - if (1 < nbAnon) { - MString msg; - MString size; - size = nbAnon; - msg.format( - StringResources::getAsMString(StringResources::kToSaveTheStageAnonFilesWillBeSaved), - size); - message = MQtUtil::toQString(msg); - - } else { - message = StringResources::getAsQString( - StringResources::kToSaveTheStageAnonFileWillBeSaved); - } - - SaveLayersDialog dlg(dialogTitle, message, anonLayerItems, in_parent); - if (QDialog::Accepted == dlg.exec()) { - - if (!dlg.layersWithErrorPairs().isEmpty()) { - const QStringList& errors = dlg.layersWithErrorPairs(); - MString resultMsg; - for (int i = 0; i < errors.length() - 1; i += 2) { - MString errorMsg; - errorMsg.format( - StringResources::getAsMString(StringResources::kSaveAnonymousLayersErrors), - MQtUtil::toMString(errors[i]), - MQtUtil::toMString(errors[i + 1])); - resultMsg += errorMsg + "\n"; - } - - MGlobal::displayError(resultMsg); - - warningDialog( - StringResources::getAsQString(StringResources::kSaveAnonymousLayersErrorsTitle), - StringResources::getAsQString(StringResources::kSaveAnonymousLayersErrorsMsg)); - } else { - const auto layers = getAllNeedsSavingLayers(); - for (auto layer : layers) { - if (!layer->isAnonymous()) - layer->saveEditsNoPrompt(); - } - } - } - } else { - QString buttonText; - const auto layers = getAllNeedsSavingLayers(); - const auto layersQStringList = getLayerListAsQStringList(layers); - if (layers.size()) { - if (layers.size() == 1) { - message - = StringResources::getAsQString(StringResources::kToSaveTheStageFileWillBeSave); - buttonText = StringResources::getAsQString(StringResources::kSave); - } else { - MString msg; - MString size; - size = layers.size(); - msg.format( - StringResources::getAsMString(StringResources::kToSaveTheStageFilesWillBeSave), - size); - message = MQtUtil::toQString(msg); - buttonText = StringResources::getAsQString(StringResources::kSaveAll); + SaveLayersDialog dlg(_sessionState, in_parent); + if (QDialog::Accepted == dlg.exec()) { + + if (!dlg.layersWithErrorPairs().isEmpty()) { + const QStringList& errors = dlg.layersWithErrorPairs(); + MString resultMsg; + for (int i = 0; i < errors.length() - 1; i += 2) { + MString errorMsg; + errorMsg.format( + StringResources::getAsMString(StringResources::kSaveAnonymousLayersErrors), + MQtUtil::toMString(errors[i]), + MQtUtil::toMString(errors[i + 1])); + resultMsg += errorMsg + "\n"; } - message += " "; - message += StringResources::getAsQString(StringResources::kNotUndoable); + MGlobal::displayError(resultMsg); - if (confirmDialog(dialogTitle, message, &layersQStringList, &buttonText)) { - for (auto layer : layers) { + warningDialog( + StringResources::getAsQString(StringResources::kSaveAnonymousLayersErrorsTitle), + StringResources::getAsQString(StringResources::kSaveAnonymousLayersErrorsMsg)); + } else { + const auto layers = getAllNeedsSavingLayers(); + for (auto layer : layers) { + if (!layer->isAnonymous()) layer->saveEditsNoPrompt(); - } } } } diff --git a/lib/usd/ui/layerEditor/mayaSessionState.cpp b/lib/usd/ui/layerEditor/mayaSessionState.cpp index cfb0c0697f..4c6efb118a 100644 --- a/lib/usd/ui/layerEditor/mayaSessionState.cpp +++ b/lib/usd/ui/layerEditor/mayaSessionState.cpp @@ -16,6 +16,7 @@ #include "mayaSessionState.h" +#include "saveLayersDialog.h" #include "stringResources.h" #include @@ -28,7 +29,6 @@ #include #include -#include #include #include @@ -197,19 +197,24 @@ void MayaSessionState::nodeRenamedCB(MObject& obj, const MString& oldName, void* { if (oldName.length() != 0) { auto THIS = static_cast(clientData); - // this does not work: - // if OpenMaya.MFnDependencyNode(obj).typeName == PROXY_NODE_TYPE - if (obj.hasFn(MFn::kShape)) { - MDagPath dagPath; - MFnDagNode(obj).getPath(dagPath); - auto shapePath = dagPath.fullPathName(); - - StageEntry entry; - if (getStageEntry(&entry, shapePath)) { - QTimer::singleShot(0, [THIS, entry]() { - Q_EMIT THIS->stageRenamedSignal(entry._displayName, entry._stage); - }); - } + + // doing it on idle give time to the Load Stage to set a file name + QTimer::singleShot(0, [THIS, obj]() { THIS->nodeRenamedCBOnIdle(obj); }); + } +} + +void MayaSessionState::nodeRenamedCBOnIdle(const MObject& obj) +{ + // this does not work: + // if OpenMaya.MFnDependencyNode(obj).typeName == PROXY_NODE_TYPE + if (obj.hasFn(MFn::kShape)) { + MDagPath dagPath; + MFnDagNode(obj).getPath(dagPath); + auto shapePath = dagPath.fullPathName(); + + StageEntry entry; + if (getStageEntry(&entry, shapePath)) { + Q_EMIT stageRenamedSignal(entry._displayName, entry._stage); } } } @@ -226,35 +231,7 @@ bool MayaSessionState::saveLayerUI( std::string* out_filePath, std::string* out_pFormat) const { - MString fileSelected; - MGlobal::executeCommand( - MString("UsdLayerEditor_SaveLayerFileDialog"), - fileSelected, - /*display*/ true, - /*undo*/ false); - if (fileSelected.length() == 0) - return false; - - int binary = 0; - MGlobal::executeCommand( - MString("UsdLayerEditor_SaveLayerFileDialog_binary"), - binary, - /*display*/ false, - /*undo*/ false); - *out_filePath = fileSelected.asChar(); - - // figure out format - QFileInfo fileInfo(fileSelected.asChar()); - QString extension = fileInfo.suffix().toLower(); - - // unambiguous formats - if (extension == "usda" || extension == "usdc") { - *out_pFormat = extension.toStdString(); - } else { - *out_pFormat = binary ? "usdc" : "usda"; - } - - return true; + return SaveLayersDialog::saveLayerFilePathUI(*out_filePath, *out_pFormat); } std::vector diff --git a/lib/usd/ui/layerEditor/mayaSessionState.h b/lib/usd/ui/layerEditor/mayaSessionState.h index 5e2e3611b0..d0c2d5ae77 100644 --- a/lib/usd/ui/layerEditor/mayaSessionState.h +++ b/lib/usd/ui/layerEditor/mayaSessionState.h @@ -90,6 +90,7 @@ class MayaSessionState static void sceneClosingCB(void* clientData); void proxyShapeAddedCBOnIdle(const MObject& node); + void nodeRenamedCBOnIdle(const MObject& obj); // Notice listener method for proxy stage set void mayaUsdStageReset(const MayaUsdProxyStageSetNotice& notice); diff --git a/lib/usd/ui/layerEditor/saveLayersDialog.cpp b/lib/usd/ui/layerEditor/saveLayersDialog.cpp index e2a3be13ef..b86c3fa31e 100644 --- a/lib/usd/ui/layerEditor/saveLayersDialog.cpp +++ b/lib/usd/ui/layerEditor/saveLayersDialog.cpp @@ -9,78 +9,66 @@ #include "stringResources.h" #include "warningDialogs.h" -#include +#include +#include +#include + #include -#include +#include #include #include #include #include #include -#include #include #include #include -#include #include +#if defined(WANT_UFE_BUILD) +#include +#endif + PXR_NAMESPACE_USING_DIRECTIVE namespace { -using namespace UsdLayerEditor; - -// Starting at the given tree item, walk up the layer hierarchy looking for -// the first file backed layer. -// If we find a file backed layer then this will be the folder to save in. -// If we don't find a folder then we will ask the session layer for the -// current Maya scene and use that location. -// -QString suggestedStartFolder(const LayerTreeItem* treeItem) +template void moveAppendVector(std::vector& src, std::vector& dst) { - QString startPath; - - auto item = treeItem; - while (item != nullptr) { - if (!item->isAnonymous()) { - startPath = QFileInfo(item->layer()->GetRealPath().c_str()).dir().absolutePath(); - break; - } - item = item->parentLayerItem(); - } - - if (startPath.isEmpty()) { - startPath = treeItem->parentModel()->sessionState()->defaultLoadPath().c_str(); - } - - if (!startPath.isEmpty() && startPath.at(startPath.size() - 1) != QDir::separator()) { - startPath += QDir::separator(); + if (dst.empty()) { + dst = std::move(src); + } else { + dst.reserve(dst.size() + src.size()); + std::move(std::begin(src), std::end(src), std::back_inserter(dst)); + src.clear(); } - - return QDir::fromNativeSeparators(startPath); } -QString suggestedFileName(const LayerTreeItem* treeItem, const QString& startFolder) +using namespace UsdLayerEditor; + +void getDialogMessages(const int nbStages, const int nbAnonLayers, QString& msg1, QString& msg2) { - QString templateName = startFolder + QString::fromStdString(treeItem->displayName()) - + QString("_XXXXXX.") + QString(UsdUsdFileFormatTokens->Id.GetText()); - QTemporaryFile tFile(templateName); - if (tFile.open()) { - return tFile.fileName(); - } else { - return startFolder + QString::fromStdString(treeItem->displayName()) + QString(".") - + QString(UsdUsdFileFormatTokens->Id.GetText()); - } + MString msg, strNbStages, strNbAnonLayers; + strNbStages = nbStages; + strNbAnonLayers = nbAnonLayers; + msg.format( + StringResources::getAsMString(StringResources::kToSaveTheStageSaveAnonym), + strNbStages, + strNbAnonLayers); + msg1 = MQtUtil::toQString(msg); + + msg.format( + StringResources::getAsMString(StringResources::kToSaveTheStageSaveFiles), strNbStages); + msg2 = MQtUtil::toQString(msg); } -// TODO: helper class copied from Load Layers Dialog. -class MyLineEdit : public QLineEdit +class AnonLayerPathEdit : public QLineEdit { public: - MyLineEdit(QWidget* in_parent) + AnonLayerPathEdit(QWidget* in_parent) : QLineEdit(in_parent) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); @@ -91,8 +79,8 @@ class MyLineEdit : public QLineEdit auto hint = QLineEdit::sizeHint(); if (!text().isEmpty()) { QFontMetrics appFont = QApplication::fontMetrics(); - int pathWidth = appFont.boundingRect(text()).width() + 100; - hint.setWidth(DPIScale(pathWidth)); + int pathWidth = appFont.boundingRect(text()).width(); + hint.setWidth(pathWidth + DPIScale(100)); } return hint; } @@ -107,7 +95,9 @@ class SaveLayersDialog; class SaveLayerPathRow : public QWidget { public: - SaveLayerPathRow(SaveLayersDialog* in_parent, LayerTreeItem* in_treeItem); + SaveLayerPathRow( + SaveLayersDialog* in_parent, + const std::pair& in_layerPair); QString layerDisplayName() const; @@ -123,35 +113,46 @@ class SaveLayerPathRow : public QWidget void onRelativeButtonChecked(bool checked); public: - QString _initialStartFolder; - QString _absolutePath; - QString _formatTag; - SaveLayersDialog* _parent; - LayerTreeItem* _treeItem; - QLabel* _label; - QLineEdit* _pathEdit; - QAbstractButton* _openBrowser; + QString _initialStartFolder; + QString _absolutePath; + QString _formatTag; + SaveLayersDialog* _parent { nullptr }; + std::pair _layerPair; + QLabel* _label { nullptr }; + QLineEdit* _pathEdit { nullptr }; + QAbstractButton* _openBrowser { nullptr }; }; -SaveLayerPathRow::SaveLayerPathRow(SaveLayersDialog* in_parent, LayerTreeItem* in_treeItem) +SaveLayerPathRow::SaveLayerPathRow( + SaveLayersDialog* in_parent, + const std::pair& in_layerPair) : QWidget(in_parent) - , _formatTag("usdc") + , _formatTag(UsdMayaTranslatorTokens->UsdFileExtensionCrate.GetText()) , _parent(in_parent) - , _treeItem(in_treeItem) + , _layerPair(in_layerPair) { auto gridLayout = new QGridLayout(); QtUtils::initLayoutMargins(gridLayout); - QString displayName = _treeItem->displayName().c_str(); + // Since this is an anonymous layer, it should only be associated with a single stage. + std::string stageName; + const auto& stageLayers = in_parent->stageLayers(); + if (TF_VERIFY(1 == stageLayers.count(_layerPair.first))) { + auto search = stageLayers.find(_layerPair.first); + stageName = search->second; + } + + QString displayName = _layerPair.first->GetDisplayName().c_str(); _label = new QLabel(displayName); + _label->setToolTip(in_parent->buildTooltipForLayer(_layerPair.first)); gridLayout->addWidget(_label, 0, 0); - _initialStartFolder = suggestedStartFolder(_treeItem); - _absolutePath = suggestedFileName(_treeItem, _initialStartFolder); + _initialStartFolder = UsdMayaSerialization::getSceneFolder().c_str(); + _absolutePath = UsdMayaSerialization::generateUniqueFileName(stageName).c_str(); QFileInfo fileInfo(_absolutePath); QString suggestedFullPath = fileInfo.absoluteFilePath(); - _pathEdit = new MyLineEdit(this); + _pathEdit = new AnonLayerPathEdit(this); _pathEdit->setText(suggestedFullPath); connect(_pathEdit, &QLineEdit::textChanged, this, &SaveLayerPathRow::onTextChanged); gridLayout->addWidget(_pathEdit, 0, 1); @@ -184,9 +185,8 @@ void SaveLayerPathRow::setAbsolutePath(const std::string& path, const std::strin void SaveLayerPathRow::onOpenBrowser() { - auto sessionState = _treeItem->parentModel()->sessionState(); std::string fileName, formatTag; - if (sessionState->saveLayerUI(nullptr, &fileName, &formatTag)) { + if (SaveLayersDialog::saveLayerFilePathUI(fileName, formatTag)) { setAbsolutePath(fileName, formatTag); } } @@ -232,8 +232,11 @@ class SaveLayerPathRowArea : public QScrollArea if (0 < rowHint.height()) hint.rheight() += rowHint.height(); } - hint.rheight() += 100; + + // Extra padding (enough for 3.5 lines). hint.rwidth() += 100; + if (hint.height() < DPIScale(120)) + hint.setHeight(DPIScale(120)); } return hint; } @@ -244,62 +247,200 @@ class SaveLayerPathRowArea : public QScrollArea // namespace UsdLayerEditor { -SaveLayersDialog::SaveLayersDialog( - const QString& title, - const QString& message, - const std::vector& layerItems, - QWidget* in_parent) - : QDialog(in_parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) - , _layerItems(layerItems) +#if defined(WANT_UFE_BUILD) +SaveLayersDialog::SaveLayersDialog(QWidget* in_parent, const std::vector& stages) + : QDialog(in_parent) + , _sessionState(nullptr) { - setWindowTitle(title); + MString msg, nbStages; + + nbStages = stages.size(); + msg.format(StringResources::getAsMString(StringResources::kSaveXStages), nbStages); + setWindowTitle(MQtUtil::toQString(msg)); + + // For each stage collect the layers to save. + for (const auto& stage : stages) { + // Get the name of this stage. + auto stagePath = MayaUsd::ufe::stagePath(stage); + std::string stageName = !stagePath.empty() ? stagePath.back().string() : "Unknown"; + + getLayersToSave(stage, stageName); + } - _rowsLayout = new QVBoxLayout(); - int margin = DPIScale(5) + DPIScale(20); - _rowsLayout->setContentsMargins(margin, margin, margin, 0); - _rowsLayout->setSpacing(DPIScale(8)); - _rowsLayout->setAlignment(Qt::AlignTop); + QString msg1, msg2; + getDialogMessages( + static_cast(stages.size()), static_cast(_anonLayerPairs.size()), msg1, msg2); + buildDialog(msg1, msg2); +} +#endif - if (!message.isEmpty()) { - auto dialogMessage = new QLabel(message); - _rowsLayout->addWidget(dialogMessage); +SaveLayersDialog::SaveLayersDialog(SessionState* in_sessionState, QWidget* in_parent) + : QDialog(in_parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) + , _sessionState(in_sessionState) +{ + MString msg; + QString dialogTitle = StringResources::getAsQString(StringResources::kSaveStage); + if (TF_VERIFY(nullptr != _sessionState)) { + auto stage = _sessionState->stage(); + + auto stageList = _sessionState->allStages(); + for (auto const& entry : stageList) { + if (entry._stage == stage) { + std::string stageName = entry._displayName; + msg.format( + StringResources::getAsMString(StringResources::kSaveName), stageName.c_str()); + dialogTitle = MQtUtil::toQString(msg); + getLayersToSave(stage, stageName); + break; + } + } } + setWindowTitle(dialogTitle); - for (auto iter = _layerItems.crbegin(); iter != _layerItems.crend(); ++iter) { - auto row = new SaveLayerPathRow(this, (*iter)); - _rowsLayout->addWidget(row); + QString msg1, msg2; + getDialogMessages(1, static_cast(_anonLayerPairs.size()), msg1, msg2); + buildDialog(msg1, msg2); +} + +SaveLayersDialog ::~SaveLayersDialog() { QApplication::restoreOverrideCursor(); } + +void SaveLayersDialog::getLayersToSave(UsdStageRefPtr stage, const std::string& stageName) +{ + // Get the layers to save for this stage. + UsdMayaSerialization::stageLayersToSave stageLayersToSave; + UsdMayaSerialization::getLayersToSaveFromProxy(stage, stageLayersToSave); + + // Keep track of all the layers for this particular stage. + for (const auto& layerPairs : stageLayersToSave.anonLayers) { + _stageLayerMap.emplace(std::make_pair(layerPairs.first, stageName)); } + for (const auto& dirtyLayer : stageLayersToSave.dirtyFileBackedLayers) { + _stageLayerMap.emplace(std::make_pair(dirtyLayer, stageName)); + } + + // Add these layers to save to our member var for reference later. + // Note: we use a set for the dirty file back layers because they + // can come from multiple stages, but we only want them to + // appear once in the dialog. + moveAppendVector(stageLayersToSave.anonLayers, _anonLayerPairs); + _dirtyFileBackedLayers.insert( + std::begin(stageLayersToSave.dirtyFileBackedLayers), + std::end(stageLayersToSave.dirtyFileBackedLayers)); +} + +void SaveLayersDialog::buildDialog(const QString& msg1, const QString& msg2) +{ + const int mainMargin = DPIScale(20); // Ok/Cancel button area auto buttonsLayout = new QHBoxLayout(); - QtUtils::initLayoutMargins(buttonsLayout, DPIScale(20)); + QtUtils::initLayoutMargins(buttonsLayout, 0); buttonsLayout->addStretch(); - auto okButton = new QPushButton("Save Stage", this); + auto okButton + = new QPushButton(StringResources::getAsQString(StringResources::kSaveStages), this); connect(okButton, &QPushButton::clicked, this, &SaveLayersDialog::onSaveAll); okButton->setDefault(true); - auto cancelButton = new QPushButton("Cancel", this); + auto cancelButton + = new QPushButton(StringResources::getAsQString(StringResources::kCancel), this); connect(cancelButton, &QPushButton::clicked, this, &SaveLayersDialog::onCancel); buttonsLayout->addWidget(okButton); buttonsLayout->addWidget(cancelButton); - // setup a scroll area - auto dialogContentParent = new QWidget(); - dialogContentParent->setLayout(_rowsLayout); + const bool haveAnonLayers { !_anonLayerPairs.empty() }; + const bool haveFileBackedLayers { !_dirtyFileBackedLayers.empty() }; + SaveLayerPathRowArea* anonScrollArea { nullptr }; + SaveLayerPathRowArea* fileScrollArea { nullptr }; + const int margin { DPIScale(10) }; + + // Anonymous layers. + if (haveAnonLayers) { + auto anonLayout = new QVBoxLayout(); + anonLayout->setContentsMargins(margin, margin, margin, 0); + anonLayout->setSpacing(DPIScale(8)); + anonLayout->setAlignment(Qt::AlignTop); + for (auto iter = _anonLayerPairs.cbegin(); iter != _anonLayerPairs.cend(); ++iter) { + auto row = new SaveLayerPathRow(this, (*iter)); + anonLayout->addWidget(row); + } + + _anonLayersWidget = new QWidget(); + _anonLayersWidget->setLayout(anonLayout); + + // Setup the scroll area for anonymous layers. + anonScrollArea = new SaveLayerPathRowArea(); + anonScrollArea->setFrameShape(QFrame::NoFrame); + anonScrollArea->setBackgroundRole(QPalette::Midlight); + anonScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + anonScrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + anonScrollArea->setWidget(_anonLayersWidget); + anonScrollArea->setWidgetResizable(true); + } + + // File backed layers + if (haveFileBackedLayers) { + auto fileLayout = new QVBoxLayout(); + fileLayout->setContentsMargins(margin, margin, margin, 0); + fileLayout->setSpacing(DPIScale(8)); + fileLayout->setAlignment(Qt::AlignTop); + for (const auto& dirtyLayer : _dirtyFileBackedLayers) { + auto row = new QLabel(dirtyLayer->GetRealPath().c_str(), this); + row->setToolTip(buildTooltipForLayer(dirtyLayer)); + fileLayout->addWidget(row); + } - auto scrollArea = new SaveLayerPathRowArea(); - scrollArea->setFrameShape(QFrame::NoFrame); - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); - scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - scrollArea->setWidget(dialogContentParent); - scrollArea->setWidgetResizable(true); + _fileLayersWidget = new QWidget(); + _fileLayersWidget->setLayout(fileLayout); + + // Setup the scroll area for dirty file backed layers. + fileScrollArea = new SaveLayerPathRowArea(); + fileScrollArea->setFrameShape(QFrame::NoFrame); + fileScrollArea->setBackgroundRole(QPalette::Midlight); + fileScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + fileScrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + fileScrollArea->setWidget(_fileLayersWidget); + fileScrollArea->setWidgetResizable(true); + } - // add that scroll area as our single child + // Create the main layout for the dialog. auto topLayout = new QVBoxLayout(); - QtUtils::initLayoutMargins(topLayout); - topLayout->setSpacing(0); - topLayout->addWidget(scrollArea); + QtUtils::initLayoutMargins(topLayout, mainMargin); + topLayout->setSpacing(DPIScale(8)); + + if (nullptr != anonScrollArea) { + // Add the first message. + if (!msg1.isEmpty()) { + auto dialogMessage = new QLabel(msg1); + topLayout->addWidget(dialogMessage); + } + + // Then add the first scroll area (containing the anonymous layers) + topLayout->addWidget(anonScrollArea); + + // If we also have dirty file backed layers, add a separator. + if (haveFileBackedLayers) { + auto lineSep = new QFrame(); + lineSep->setFrameShape(QFrame::HLine); + lineSep->setLineWidth(DPIScale(1)); + QPalette pal(lineSep->palette()); + pal.setColor(QPalette::Base, QColor("#575757")); + lineSep->setPalette(pal); + lineSep->setBackgroundRole(QPalette::Base); + topLayout->addWidget(lineSep); + } + } - // then add the buttons + if (nullptr != fileScrollArea) { + // Add the second message. + if (!msg2.isEmpty()) { + auto dialogMessage = new QLabel(msg2); + topLayout->addWidget(dialogMessage); + } + + // Add the second scroll area (containing the file backed layers). + topLayout->addWidget(fileScrollArea); + } + + // Finally add the buttons. auto buttonArea = new QWidget(this); buttonArea->setLayout(buttonsLayout); buttonArea->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); @@ -309,6 +450,26 @@ SaveLayersDialog::SaveLayersDialog( setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); setSizeGripEnabled(true); + QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor)); +} + +QString SaveLayersDialog::buildTooltipForLayer(SdfLayerRefPtr layer) +{ + if (nullptr == layer) + return ""; + + // Disable word wrapping on tooltip. + QString tooltip = "

"; + tooltip += StringResources::getAsQString(StringResources::kUsedInStagesTooltip); + auto range = _stageLayerMap.equal_range(layer); + bool needComma = false; + for (auto it = range.first; it != range.second; ++it) { + if (needComma) + tooltip.append(", "); + tooltip.append(it->second.c_str()); + needComma = true; + } + return tooltip; } void SaveLayersDialog::onSaveAll() @@ -325,57 +486,52 @@ void SaveLayersDialog::onSaveAll() _problemLayers.clear(); _emptyLayers.clear(); - for (i = 0, count = _rowsLayout->count(); i < count; ++i) { - auto row = dynamic_cast(_rowsLayout->itemAt(i)->widget()); - auto item = row ? row->_treeItem : nullptr; - if (nullptr == item) - continue; - - QString path = row->pathToSaveAs(); - if (!path.isEmpty()) { - PXR_NS::SdfFileFormat::FileFormatArguments formatArgs; - formatArgs["format"] = row->usdFormatTag().toStdString(); - - auto sessionState = item->parentModel()->sessionState(); - auto sdfLayer = item->layer(); - auto qFileName = row->absolutePath(); - auto sFileName = qFileName.toStdString(); - - sdfLayer->Export(sFileName, "", formatArgs); - - if (item->isRootLayer()) { - newRoot = sFileName; - rootSessionState = sessionState; - } else { - // now replace the layer in the parent - auto parentItem = item->parentLayerItem(); - auto newLayer = SdfLayer::FindOrOpen(sFileName); - if (newLayer) { - bool setTarget = item->isTargetLayer(); - parentItem->layer()->GetSubLayerPaths().Replace( - sdfLayer->GetIdentifier(), newLayer->GetIdentifier()); - if (setTarget) { - sessionState->stage()->SetEditTarget(newLayer); - } - - _newPaths.append(QString::fromStdString(sdfLayer->GetDisplayName())); - _newPaths.append(qFileName); + // The anonymous layer section in the dialog can be empty. + if (nullptr != _anonLayersWidget) { + QLayout* anonLayout = _anonLayersWidget->layout(); + for (i = 0, count = anonLayout->count(); i < count; ++i) { + auto row = dynamic_cast(anonLayout->itemAt(i)->widget()); + if (!row || !row->_layerPair.first) + continue; + + QString path = row->pathToSaveAs(); + if (!path.isEmpty()) { + auto sdfLayer = row->_layerPair.first; + auto parentLayer = row->_layerPair.second; + auto qFileName = row->absolutePath(); + auto sFileName = qFileName.toStdString(); + + auto newLayer = UsdMayaSerialization::saveAnonymousLayer( + sdfLayer, sFileName, parentLayer, row->usdFormatTag().toStdString()); + + if (!parentLayer) { + newRoot = sFileName; + rootSessionState = _sessionState; } else { - _problemLayers.append(QString::fromStdString(sdfLayer->GetDisplayName())); - _problemLayers.append(qFileName); + if (newLayer) { + // if (item->isTargetLayer()) { + // sessionState->stage()->SetEditTarget(newLayer); + // } + + _newPaths.append(QString::fromStdString(sdfLayer->GetDisplayName())); + _newPaths.append(qFileName); + } else { + _problemLayers.append(QString::fromStdString(sdfLayer->GetDisplayName())); + _problemLayers.append(qFileName); + } } + } else { + _emptyLayers.append(row->layerDisplayName()); } - } else { - _emptyLayers.append(row->layerDisplayName()); } - } - if (rootSessionState) { - rootSessionState->rootLayerPathChanged(newRoot); + if (rootSessionState) { + rootSessionState->rootLayerPathChanged(newRoot); + } } accept(); -} +} // namespace UsdLayerEditor void SaveLayersDialog::onCancel() { reject(); } @@ -384,17 +540,20 @@ bool SaveLayersDialog::okToSave() int i, count; QStringList existingFiles; - for (i = 0, count = _rowsLayout->count(); i < count; ++i) { - auto row = dynamic_cast(_rowsLayout->itemAt(i)->widget()); - auto item = row ? row->_treeItem : nullptr; - if (nullptr == item) - continue; - - QString path = row->pathToSaveAs(); - if (!path.isEmpty()) { - QFileInfo fInfo(path); - if (fInfo.exists()) { - existingFiles.append(path); + // The anonymous layer section in the dialog can be empty. + if (nullptr != _anonLayersWidget) { + QLayout* anonLayout = _anonLayersWidget->layout(); + for (i = 0, count = anonLayout->count(); i < count; ++i) { + auto row = dynamic_cast(anonLayout->itemAt(i)->widget()); + if (nullptr == row) + continue; + + QString path = row->pathToSaveAs(); + if (!path.isEmpty()) { + QFileInfo fInfo(path); + if (fInfo.exists()) { + existingFiles.append(path); + } } } } @@ -415,4 +574,34 @@ bool SaveLayersDialog::okToSave() return true; } +/*static*/ +bool SaveLayersDialog::saveLayerFilePathUI(std::string& out_filePath, std::string& out_format) +{ + MString fileSelected; + MGlobal::executeCommand( + MString("UsdLayerEditor_SaveLayerFileDialog"), + fileSelected, + /*display*/ true, + /*undo*/ false); + if (fileSelected.length() == 0) + return false; + + out_filePath = fileSelected.asChar(); + + // figure out format + QFileInfo fileInfo(fileSelected.asChar()); + QString extension = fileInfo.suffix().toLower(); + + // unambiguous formats + if (extension == UsdMayaTranslatorTokens->UsdFileExtensionASCII.GetText() + || extension == UsdMayaTranslatorTokens->UsdFileExtensionCrate.GetText()) { + out_format = extension.toStdString(); + } else { + out_format = UsdMayaSerialization::usdFormatArgOption(); + } + + return true; +} + + } // namespace UsdLayerEditor diff --git a/lib/usd/ui/layerEditor/saveLayersDialog.h b/lib/usd/ui/layerEditor/saveLayersDialog.h index 89103fb6c2..cae2bc2d18 100644 --- a/lib/usd/ui/layerEditor/saveLayersDialog.h +++ b/lib/usd/ui/layerEditor/saveLayersDialog.h @@ -1,25 +1,43 @@ #ifndef SAVELAYERSDIALOG_H #define SAVELAYERSDIALOG_H +#include +#include + #include #include #include -class QScrollArea; -class QVBoxLayout; +#include +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +class QWidget; namespace UsdLayerEditor { -class LayerTreeItem; +class SessionState; class SaveLayersDialog : public QDialog { public: - SaveLayersDialog( - const QString& title, - const QString& message, - const std::vector& layerItems, - QWidget* in_parent); + typedef std::unordered_multimap stageLayerMap; + + // Create dialog using single stage (from session state). + SaveLayersDialog(SessionState* in_sessionState, QWidget* in_parent); + +#if defined(WANT_UFE_BUILD) + // Create dialog for bulk save using all stages. + SaveLayersDialog(QWidget* in_parent, const std::vector& stages); +#endif + + ~SaveLayersDialog(); + + // UI to get a file path to save a layer. + // As output returns the path and the file format (ex: "usda"). + static bool saveLayerFilePathUI(std::string& out_filePath, std::string& out_format); protected: void onSaveAll(); @@ -28,16 +46,30 @@ class SaveLayersDialog : public QDialog bool okToSave(); public: - const QStringList& layersSavedToPairs() const { return _newPaths; } - const QStringList& layersWithErrorPairs() const { return _problemLayers; } - const QStringList& layersNotSaved() const { return _emptyLayers; } + const QStringList& layersSavedToPairs() const { return _newPaths; } + const QStringList& layersWithErrorPairs() const { return _problemLayers; } + const QStringList& layersNotSaved() const { return _emptyLayers; } + const stageLayerMap& stageLayers() const { return _stageLayerMap; } + SessionState* sessionState() { return _sessionState; } + QString buildTooltipForLayer(SdfLayerRefPtr layer); private: - QStringList _newPaths; - QStringList _problemLayers; - QStringList _emptyLayers; - QVBoxLayout* _rowsLayout; - std::vector _layerItems; + void buildDialog(const QString& msg1, const QString& msg2); + void getLayersToSave(UsdStageRefPtr stage, const std::string& stageName); + +private: + typedef std::vector> layerPairs; + typedef std::unordered_set layerSet; + + QStringList _newPaths; + QStringList _problemLayers; + QStringList _emptyLayers; + QWidget* _anonLayersWidget { nullptr }; + QWidget* _fileLayersWidget { nullptr }; + layerPairs _anonLayerPairs; + layerSet _dirtyFileBackedLayers; + stageLayerMap _stageLayerMap; + SessionState* _sessionState; }; }; // namespace UsdLayerEditor diff --git a/lib/usd/ui/layerEditor/stringResources.h b/lib/usd/ui/layerEditor/stringResources.h index 0c29ceb81d..6d4742e118 100644 --- a/lib/usd/ui/layerEditor/stringResources.h +++ b/lib/usd/ui/layerEditor/stringResources.h @@ -42,14 +42,16 @@ MStringResourceId create(const char* key, const char* value); // ------------------------------------------------------------- -// Same keys definied in MEL file will have priority over C++ keys/values. +// Same keys defined in MEL file will have priority over C++ keys/values. // Those with higher priority will end up showing in the UI. // clang-format off const auto kAddNewLayer { create("kAddNewLayer", "Add a New Layer") }; const auto kAddSublayer { create("kAddSublayer", "Add sublayer") }; +const auto kAscii { create("kAscii", "ASCII") }; const auto kAutoHideSessionLayer { create("kAutoHideSessionLayer", "Auto-Hide Session Layer") }; +const auto kBinary { create("kBinary", "Binary") }; const auto kConvertToRelativePath { create("kConvertToRelativePath", "Convert to Relative Path") }; const auto kCancel { create("kCancel", "Cancel") }; const auto kClearLayerTitle { create("kclearLayerTitle", "Clear \"^1s\"") }; @@ -71,7 +73,7 @@ const auto kLayerPath { create("kLayerPath", "Layer Path:") }; const auto kMuteUnmuteLayer { create("kMuteUnmuteLayer", "Mute/unmute the layer. Muted layers are ignored by the stage.")}; const auto kNoLayers { create("kNoLayers", "No Layers") }; const auto kNotUndoable { create("kNotUndoable", "You can not undo this action.") }; -const auto kOption { create("kOption", "Option") }; +const auto kOptions { create("kOptions", "Options") }; const auto kPathNotFound { create("kPathNotFound", "Path not found: ") }; const auto kRealPath { create("kRealPath", "Real Path: ^1s") }; const auto kRemoveSublayer { create("kRemoveSublayer", "Remove sublayer") }; @@ -79,31 +81,33 @@ const auto kSave { create("kSave", "Save") }; const auto kSaveAll { create("kSaveAll", "Save All") }; const auto kSaveAllEditsInLayerStack { create("kSaveAllEditsInLayerStack", "Save all edits in the Layer Stack")}; const auto kSaveLayer { create("kSaveLayer", "Save Layer") }; +const auto kSaveName { create("kSaveName", "Save ^1s") }; const auto kSaveLayerSaveNestedAnonymLayer { create("kSaveLayerSaveNestedAnonymLayer", "To save ^1s, you must save your ^2s anonymous layer(s) that are nested under it.") }; const auto kSaveLayerWarnTitle { create("kSaveLayerWarnTitle", "Save ^1s") }; const auto kSaveLayerWarnMsg { create("kSaveLayerWarnMsg", "Saving edits to ^1s will overwrite your file.") }; const auto kSaveStage { create("kSaveStage", "Save Stage") }; -const auto kToSaveTheStageSaveAnonym { create("kToSaveTheStageSaveAnonym", "To save the stage, you must save your anonymous layer")}; -const auto kToSaveTheStageSaveAnonyms{ create("kToSaveTheStageSaveAnonyms", "To save the stage, you must save your ^1s anonymous layers")}; -const auto kToSaveTheStageFileWillBeSave { create("kToSaveTheStageFileWillBeSave", - "To save the stage, edits to the following file will be saved.") }; -const auto kToSaveTheStageFilesWillBeSave { create("kToSaveTheStageFilesWillBeSave", - "To save the stage, edits to the following ^1s files will be saved.") }; +const auto kSaveStages { create("kSaveStages", "Save Stage(s)") }; +const auto kSaveXStages { create("kSaveXStages", "Save ^1s Stage(s)") }; +const auto kToSaveTheStageSaveAnonym { create("kToSaveTheStageSaveAnonym", "To save the ^1s stage(s), save the following ^2s anonymous layer(s).") }; +const auto kToSaveTheStageSaveFiles { create("kToSaveTheStageSaveFiles", "To save the ^1s stage(s), the following existing file(s) will be overwritten.") }; +const auto kUsedInStagesTooltip { create("kUsedInStagesTooltip", "Used in Stages: ") }; + const auto kSetLayerAsTargetLayerTooltip { create("kSetLayerAsTargetLayerTooltip", "Set layer as target layer. Edits are added to the target layer.") }; +const auto kUsdSaveFileFormat { create("kUsdSaveFileFormat", "Save .usd File Format") }; const auto kUsdLayerIdentifier { create("kUsdLayerIdentifier", "USD Layer identifier: ^1s") }; const auto kUsdStage { create("kUsdStage", "USD Stage:") }; -const auto kToSaveTheStageAnonFileWillBeSaved { create("kToSaveTheStageAnonFileWillBeSaved", - "To save the stage, you must first save your anonymous layer. All file-backed layers will also be saved.\n") }; -const auto kToSaveTheStageAnonFilesWillBeSaved { create("kToSaveTheStageAnonFilesWillBeSaved", - "To save the stage, you must first save your ^1s anonymous layers. All file-backed layers will also be saved.\n") }; const auto kSaveAnonymousLayersErrorsTitle { create("kSaveAnonymousLayersErrorsTitle", "Save All Layers Error")}; const auto kSaveAnonymousLayersErrorsMsg { create("kSaveAnonymousLayersErrorsMsg", "Errors were encountered while saving layers. Check Script Editor for details.")}; const auto kSaveAnonymousLayersErrors { create("kSaveAnonymousLayersErrors", "Layer ^1s could not be saved to: ^2s")}; const auto kSaveAnonymousConfirmOverwriteTitle { create("kSaveAnonymousConfirmOverwriteTitle", "Confirm Overwrite")}; const auto kSaveAnonymousConfirmOverwrite { create("kSaveAnonymousConfirmOverwrite", "^1s file(s) already exist and will be overwritten. Do you want to continue?")}; +const auto kSaveLayerUsdFileFormatAnn{ create("kSaveLayerUsdFileFormatAnn", "Select whether the .usd file is written out in binary or ASCII. You can save a file in .usdc (binary) or .usda (ASCII) format. Manually entering a file name with an extension overrides the selection in this drop-down menu.") }; +const auto kSaveLayerUsdFileFormatSbm{ create("kSaveLayerUsdFileFormatSbm", "Select whether the .usd file is written out in binary or ASCII") }; + + // ------------------------------------------------------------- // Errors // ------------------------------------------------------------- From 3e5d7ef88f52a706130fe6b2a090805e3f034ad0 Mon Sep 17 00:00:00 2001 From: Sean Donnelly <23455376+seando-adsk@users.noreply.github.com> Date: Wed, 3 Feb 2021 16:57:52 -0500 Subject: [PATCH 3/6] MAYA-109102 - As a user, I'd like to have the Save USD options in the File - Save options MAYA-109081 - As a user, I'd like to have modal displaying my choices on how to save USD layers Step 3 of 3: * New MEL file/proc for adding USD options to the Maya File|Save{As}. * New MEL file for building the USD Save Options - both in Maya options and as standalone dialog. * Added warning text when choosing 2nd option. * Minor cleanup ** Moved import dialog images into their own resource namespace to avoid any potential conflicts with Maya images. ** Put MayaUsd proxy shape template strings into resource. ** Added a few missing strings in resource from maya-usd menu. ** Fixed LayerEditor what's new highlighting. --- lib/mayaUsd/resources/icons/CMakeLists.txt | 18 ++ .../resources/icons/saveOption1_100.png | Bin 0 -> 4973 bytes .../resources/icons/saveOption1_150.png | Bin 0 -> 9696 bytes .../resources/icons/saveOption1_200.png | Bin 0 -> 13260 bytes .../resources/icons/saveOption2_100.png | Bin 0 -> 5722 bytes .../resources/icons/saveOption2_150.png | Bin 0 -> 10261 bytes .../resources/icons/saveOption2_200.png | Bin 0 -> 13837 bytes .../resources/icons/saveOption3_100.png | Bin 0 -> 5307 bytes .../resources/icons/saveOption3_150.png | Bin 0 -> 9759 bytes .../resources/icons/saveOption3_200.png | Bin 0 -> 13312 bytes .../scripts/AEmayaUsdProxyShapeTemplate.mel | 16 +- plugin/adsk/scripts/CMakeLists.txt | 2 + .../adsk/scripts/mayaUSDRegisterStrings.mel | 37 ++- plugin/adsk/scripts/mayaUsdMenu.mel | 19 +- plugin/adsk/scripts/mayaUsd_fileOptions.mel | 25 ++ .../mayaUsd_layerEditorFileDialogs.mel | 56 +---- .../adsk/scripts/mayaUsd_pluginUICreation.mel | 2 + plugin/adsk/scripts/usdFileSaveOptions.mel | 216 ++++++++++++++++++ 18 files changed, 316 insertions(+), 75 deletions(-) create mode 100644 lib/mayaUsd/resources/icons/saveOption1_100.png create mode 100644 lib/mayaUsd/resources/icons/saveOption1_150.png create mode 100644 lib/mayaUsd/resources/icons/saveOption1_200.png create mode 100644 lib/mayaUsd/resources/icons/saveOption2_100.png create mode 100644 lib/mayaUsd/resources/icons/saveOption2_150.png create mode 100644 lib/mayaUsd/resources/icons/saveOption2_200.png create mode 100644 lib/mayaUsd/resources/icons/saveOption3_100.png create mode 100644 lib/mayaUsd/resources/icons/saveOption3_150.png create mode 100644 lib/mayaUsd/resources/icons/saveOption3_200.png create mode 100644 plugin/adsk/scripts/mayaUsd_fileOptions.mel create mode 100644 plugin/adsk/scripts/usdFileSaveOptions.mel diff --git a/lib/mayaUsd/resources/icons/CMakeLists.txt b/lib/mayaUsd/resources/icons/CMakeLists.txt index 6ff7b59823..b9d75db34d 100644 --- a/lib/mayaUsd/resources/icons/CMakeLists.txt +++ b/lib/mayaUsd/resources/icons/CMakeLists.txt @@ -45,3 +45,21 @@ if (CMAKE_UFE_V2_FEATURES_AVAILABLE) ) endforeach() endif() + +set(LIB_ICONS + saveOption1 + saveOption2 + saveOption3 +) +foreach(ICON_BASE ${LIB_ICONS}) + # The _100.png files need to be installed without the _100. This is the + # base icon name that is used. Maya will automatically choose the _150/_200 + # image if neeeded. + install(FILES "${ICON_BASE}_100.png" + DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/icons" + RENAME "${ICON_BASE}.png" + ) + install(FILES "${ICON_BASE}_150.png" "${ICON_BASE}_200.png" + DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/icons" + ) +endforeach() diff --git a/lib/mayaUsd/resources/icons/saveOption1_100.png b/lib/mayaUsd/resources/icons/saveOption1_100.png new file mode 100644 index 0000000000000000000000000000000000000000..04344adbf83f7a3df387faf7e09d356c0676cbae GIT binary patch literal 4973 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 literal 0 HcmV?d00001 diff --git a/lib/mayaUsd/resources/icons/saveOption1_150.png b/lib/mayaUsd/resources/icons/saveOption1_150.png new file mode 100644 index 0000000000000000000000000000000000000000..b00c31897973bad8962a519759f1c011606af838 GIT binary patch literal 9696 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 literal 0 HcmV?d00001 diff --git a/lib/mayaUsd/resources/icons/saveOption2_100.png b/lib/mayaUsd/resources/icons/saveOption2_100.png new file mode 100644 index 0000000000000000000000000000000000000000..ce536fc09a69257992e452169d6e0d3d938f4c94 GIT binary patch literal 5722 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 M07*qoM6N<$f&r`D-2eap literal 0 HcmV?d00001 diff --git a/lib/mayaUsd/resources/icons/saveOption2_150.png b/lib/mayaUsd/resources/icons/saveOption2_150.png new file mode 100644 index 0000000000000000000000000000000000000000..df556e9c1258a77d55c251c6eac8ff284ed4f4cb GIT binary patch literal 10261 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 b>8bw*AVGfUPUhZ700000NkvXXu0mjf`7KX3 literal 0 HcmV?d00001 diff --git a/lib/mayaUsd/resources/icons/saveOption2_200.png b/lib/mayaUsd/resources/icons/saveOption2_200.png new file mode 100644 index 0000000000000000000000000000000000000000..9664d08f6d5ce43c3264acfd7d6b6feb8aecc687 GIT binary patch literal 13837 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 R!=K&)yi?RrK+Bs3{U3HeNr(Ud literal 0 HcmV?d00001 diff --git a/lib/mayaUsd/resources/icons/saveOption3_100.png b/lib/mayaUsd/resources/icons/saveOption3_100.png new file mode 100644 index 0000000000000000000000000000000000000000..469a32944cb5a73b87c4dbf74d791ebd12a75bae GIT binary patch literal 5307 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 literal 0 HcmV?d00001 diff --git a/lib/mayaUsd/resources/icons/saveOption3_200.png b/lib/mayaUsd/resources/icons/saveOption3_200.png new file mode 100644 index 0000000000000000000000000000000000000000..b58766339de9b627d09a55690c4ba1d194e62d35 GIT binary patch literal 13312 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: You can choose multiple files."); register("kUsdFileOptions", "USD File Options"); - register("kUsdFileFormat", ".usd File Format: "); - + register("kUsdOptionsFrameLabel", "Universal Scene Description (USD) Options"); + register("kSaveOption2GBWarning", "Important: per layer, any data exceeding the limit of 2GB will not be saved."); + // load any localized resources loadPluginLanguageResources("mayaUsdPlugin", "mayaUsdPlugin.pres.mel"); } diff --git a/plugin/adsk/scripts/mayaUsdMenu.mel b/plugin/adsk/scripts/mayaUsdMenu.mel index 057ab958ac..c9d4db1f42 100644 --- a/plugin/adsk/scripts/mayaUsdMenu.mel +++ b/plugin/adsk/scripts/mayaUsdMenu.mel @@ -95,7 +95,7 @@ proc initRuntimeCommands() { if (!`runTimeCommand -exists mayaUsdCreateStageFromFileOptions`) { runTimeCommand -default true - -annotation "Create a USD Stage from an existing USD file options" + -annotation `getMayaUsdString("kMenuStageFromFileOptionsAnn")` -category "Menu items.Maya USD" -command "mayaUsd_createStageFromFileOptions" mayaUsdCreateStageFromFileOptions; @@ -104,8 +104,8 @@ proc initRuntimeCommands() { if (`exists mayaUsdLayerEditorWindow`) { if (!`runTimeCommand -exists mayaUsdOpenUsdLayerEditor`) { runTimeCommand -default true - -label "USD Layer Editor" - -annotation "Organize and edit USD data in layers" + -label `getMayaUsdString("kMenuLayerEditor")` + -annotation `getMayaUsdString("kMenuLayerEditorAnn")` -category "Menu items.Common.Windows.General Editors" -command "mayaUsdLayerEditorWindow mayaUsdLayerEditor" -image "USD_generic.png" @@ -138,16 +138,18 @@ global proc mayaUsdMenu_createMenuCallback() { // find the insertion point, after the Scene Management separator $sceneManagementDivider = findDividerByLabel($gMainCreateMenu, uiRes("m_ModCreateMenu.kCreateSceneMgt")); if ($sceneManagementDivider != "") { + string $subMenuLabel = `getMayaUsdString("kMenuStageSubMenu")`; + string $subMenuAnn = `getMayaUsdString("kMenuStageSubMenuAnn")`; $gMayaUsdCreateSubMenu = `menuItem -subMenu true -insertAfter $sceneManagementDivider -tearOff true - -label "Universal Scene Description (USD)" - -annotation "Create a USD stage" + -label $subMenuLabel + -annotation $subMenuAnn -image "USD_generic.png" -version $mayaVersion`; menuItem -runTimeCommand mayaUsdCreateStageWithNewLayer; menuItem -runTimeCommand mayaUsdCreateStageFromFile; menuItem -runTimeCommand mayaUsdCreateStageFromFileOptions -optionBox true; } else { - error "Could not create mayaUSD create menu"; + error `getMayaUsdString("kMenuStageCreateMenuError")`; } } } @@ -169,11 +171,12 @@ global proc mayaUsdMenu_windowMenuCallback() { global proc mayaUsdMenu_generalEditorsMenuCallback() { if (`exists mayaUsdLayerEditorWindow` && !(`menuItem -query -exists wmUsdLayerEditorMenuitem`)) { + string $mayaVersion = getMayaMajorVersion(); global string $gMayaUsdOpenUsdLayerEditorMenuItem; $gMayaUsdOpenUsdLayerEditorMenuItem = `menuItem -insertAfter wmNamespaceEditor -enableCommandRepeat false - -version "2021" + -version $mayaVersion -runTimeCommand mayaUsdOpenUsdLayerEditor wmUsdLayerEditorMenuitem`; } @@ -241,7 +244,7 @@ global proc mayaUsdMenu_layerEditorContextMenu(string $panelName) { if ($invalidLayer) { $cmd = makeCommand($panelName, "removeSubLayer"); - menuItem -label "Remove" -c $cmd; + menuItem -label `getMayaUsdString("kMenuRemove")` -c $cmd; return; // that's all we can support on invalid layers } diff --git a/plugin/adsk/scripts/mayaUsd_fileOptions.mel b/plugin/adsk/scripts/mayaUsd_fileOptions.mel new file mode 100644 index 0000000000..3ba9e5db7b --- /dev/null +++ b/plugin/adsk/scripts/mayaUsd_fileOptions.mel @@ -0,0 +1,25 @@ +// Copyright 2021 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. +// + +global proc UsdfileOptionsTabPage(string $action, string $topForm) +{ + if ( $action == "Save" || $action == "SaveAs" ) { + setParent $topForm; + frameLayout -label `getMayaUsdString("kUsdOptionsFrameLabel")` usdLayout; + + // Create options in non-standalone dialog. + usdFileSaveOptions(0); + } +} diff --git a/plugin/adsk/scripts/mayaUsd_layerEditorFileDialogs.mel b/plugin/adsk/scripts/mayaUsd_layerEditorFileDialogs.mel index eb18342008..038645886c 100644 --- a/plugin/adsk/scripts/mayaUsd_layerEditorFileDialogs.mel +++ b/plugin/adsk/scripts/mayaUsd_layerEditorFileDialogs.mel @@ -13,64 +13,12 @@ // limitations under the License. // -global proc string UsdLayerEditor_SaveLayerFileDialogOptions_Create(string $parent) { - setParent $parent; - - string $layout = `scrollLayout -childResizable true`; - - frameLayout -label `getMayaUsdString("kUsdFileOptions")` -collapsable false; - - radioButtonGrp -numberOfRadioButtons 2 - // adding some space at the end of label, otherwise it's too tight - -label `getMayaUsdString("kUsdFileFormat")` -labelArray2 "Binary" "ASCII" - -vertical - -ann `getMayaUsdString("kSaveLayerUsdFileFormatAnn")` - -sbm `getMayaUsdString("kSaveLayerUsdFileFormatSbm")` - formatSelector; - - return $layout; -} - -global proc int UsdLayerEditor_SaveLayerFileDialog_binary() { - string $varName = "UsdLayerEditor_SaveLayerFileDialogOptions_binary"; - if (!`optionVar -exists $varName`) { - optionVar -intValue $varName 1; - } - - return `optionVar -q $varName`; -} - -global proc UsdLayerEditor_SaveLayerFileDialogOptions_UIInit(string $parent, string $filterType) { - setParent $parent; - - int $binary = UsdLayerEditor_SaveLayerFileDialog_binary(); - if ($binary) { - radioButtonGrp -e -select 1 formatSelector; - } else { - radioButtonGrp -e -select 2 formatSelector; - } -} - -global proc UsdLayerEditor_SaveLayerFileDialogOptions_UICommit(string $parent) { - setParent $parent; - - string $varName = "UsdLayerEditor_SaveLayerFileDialogOptions_binary"; - optionVar -intValue $varName (`radioButtonGrp -q -select formatSelector` == 1); - - -} global proc string UsdLayerEditor_SaveLayerFileDialog() { - string $fileFilter = getMayaUsdString("kAllUsdFiles") + " (*.usd *.usda *.usdc );;*.usd;;*.usda;;*.usdc"; - - - // force non-native style, because otherwise you can't chose between ascii and binary for .usd + string $result[] = `fileDialog2 -fileMode 0 - -fileFilter $fileFilter -dialogStyle 2 - -optionsUICreate "UsdLayerEditor_SaveLayerFileDialogOptions_Create" - -optionsUIInit "UsdLayerEditor_SaveLayerFileDialogOptions_UIInit" - -optionsUICommit "UsdLayerEditor_SaveLayerFileDialogOptions_UICommit" + -fileFilter $fileFilter `; if (size($result) > 0) { diff --git a/plugin/adsk/scripts/mayaUsd_pluginUICreation.mel b/plugin/adsk/scripts/mayaUsd_pluginUICreation.mel index 865e5b45d5..fe77993991 100644 --- a/plugin/adsk/scripts/mayaUsd_pluginUICreation.mel +++ b/plugin/adsk/scripts/mayaUsd_pluginUICreation.mel @@ -5,6 +5,8 @@ global proc mayaUsd_pluginUICreation() source "mayaUsdMenu.mel"; mayaUsdMenu_loadui; + source "mayaUsd_fileOptions.mel"; + int $mjv = `about -majorVersion`; if (((2021 <= $mjv)) && (!`pluginInfo -q -loaded "ufeSupport"`)) { diff --git a/plugin/adsk/scripts/usdFileSaveOptions.mel b/plugin/adsk/scripts/usdFileSaveOptions.mel new file mode 100644 index 0000000000..bbf84c2dfc --- /dev/null +++ b/plugin/adsk/scripts/usdFileSaveOptions.mel @@ -0,0 +1,216 @@ +// Copyright 2021 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. +// + +proc setOptionVars(int $forceFactorySettings) +{ + if ($forceFactorySettings || !`optionVar -exists mayaUsd_SerializedUsdEditsLocation`) + optionVar -intValue mayaUsd_SerializedUsdEditsLocation 1; + + if ($forceFactorySettings || !`optionVar -exists mayaUsd_SerializedUsdEditsLocationPrompt`) + optionVar -intValue mayaUsd_SerializedUsdEditsLocationPrompt 1; +} + +global proc saveOptionsAndCloseUSDSaveOptionDialog(string $parent) +{ + setParent $parent; + + int $option = `radioButtonGrp -q -select USDSaveOptionsRadioButtons`; + optionVar -intValue mayaUsd_SerializedUsdEditsLocation $option; + + int $prompt = `checkBox -q -value USDSaveOptionsPromptCheckBox`; + optionVar -intValue mayaUsd_SerializedUsdEditsLocationPrompt (!$prompt); + + layoutDialog -dismiss "Save"; +} + +proc string usdFileSaveOptions_AddRadioButtons(int $standaloneDialog) +{ + int $saveOption = `optionVar -q mayaUsd_SerializedUsdEditsLocation`; + if ($saveOption < 1 || 3 < $saveOption) + { + $saveOption = 1; + } + + int $wCol1 = 142; + int $wCol2 = 310; + + if ($standaloneDialog) + { + $wCol1 = 380; + $wCol2 = 118; + } + + string $rowLayout = `rowLayout -nc 2 -cat 2 "left" 0 -cw2 $wCol1 $wCol2`; + + if (! $standaloneDialog) + { + text -label ""; + } + + string $colLayout = `columnLayout -adjustableColumn 1`; + + radioButtonGrp + -vertical + -numberOfRadioButtons 3 + -select $saveOption + + -label1 `getMayaUsdString("kSaveOption1")` + -annotation1 `getMayaUsdString("kSaveOptionAnn1")` + -onCommand1 ("USDSaveOptionsRadioButtonCB(" + $standaloneDialog + ", 1)") + + -label2 `getMayaUsdString("kSaveOption2")` + -annotation2 `getMayaUsdString("kSaveOptionAnn2")` + -onCommand2 ("USDSaveOptionsRadioButtonCB(" + $standaloneDialog + ", 2)") + + -label3 `getMayaUsdString("kSaveOption3")` + -annotation3 `getMayaUsdString("kSaveOptionAnn3")` + -onCommand3 ("USDSaveOptionsRadioButtonCB(" + $standaloneDialog + ", 3)") + + USDSaveOptionsRadioButtons; + + text -label "" USDSaveOptions2GBWarning; + if ($saveOption == 2) { + text -edit -label `getMayaUsdString("kSaveOption2GBWarning")` USDSaveOptions2GBWarning; + } + + if ($standaloneDialog) + { + setParent ..; // Back to rowlayout + image -image ("saveOption"+$saveOption+".png") usdSaveOptionImage; + + setParent $colLayout; // For "ask me" to align with radio buttons + } + + return $rowLayout; +} + +global proc showUSDSaveOptionsDialogUI() +{ + int $promptOption = `optionVar -q mayaUsd_SerializedUsdEditsLocationPrompt`; + + string $form = `setParent -q`; + formLayout -e -width 200 $form; + + string $unsavedText = getMayaUsdString("kSaveOptionUnsavedEdits"); + string $tUnsaved = `text -l $unsavedText`; + + string $options = usdFileSaveOptions_AddRadioButtons(1); + + setParent $form; + string $noPromptText = getMayaUsdString("kSaveOptionNoPrompt"); + string $noPromptAnn = getMayaUsdString("kSaveOptionNoPromptAnn"); + string $prompt = `checkBox -label $noPromptText + -ann $noPromptAnn + -value (!$promptOption) + USDSaveOptionsPromptCheckBox`; + + string $saveText = getMayaUsdString("kButtonSave"); + string $cancelText = getMayaUsdString("kButtonCancel"); + string $bSave = `button -l $saveText -c ("saveOptionsAndCloseUSDSaveOptionDialog(\"" + $form + "\")")`; + string $bCancel = `button -l $cancelText -c "layoutDialog -dismiss \"Cancel\""`; + + int $spacer = 10; + int $edge = 20; + + formLayout -edit + -attachForm $tUnsaved "top" $edge + -attachForm $tUnsaved "left" $edge + -attachNone $tUnsaved "bottom" + -attachNone $tUnsaved "right" + + -attachControl $options "top" $spacer $tUnsaved + -attachForm $options "left" $edge + -attachNone $options "bottom" + -attachNone $options "right" + + -attachNone $prompt "top" + -attachForm $prompt "left" $edge + -attachForm $prompt "bottom" $edge + -attachNone $prompt "right" + + -attachNone $bCancel "top" + -attachNone $bCancel "left" + -attachForm $bCancel "bottom" $edge + -attachForm $bCancel "right" $edge + + -attachNone $bSave "top" + -attachNone $bSave "left" + -attachForm $bSave "bottom" $edge + -attachControl $bSave "right" $spacer $bCancel + $form; +} + +global proc USDSaveOptionsRadioButtonCB(int $standaloneDialog, int $opt) +{ + // Update the image with selected save option. + if (`image -exists usdSaveOptionImage`) { + image -e -image ("saveOption" + $opt + ".png") usdSaveOptionImage; + } + + // Add or remove the warning text. + if ($opt == 2) { + text -edit -label `getMayaUsdString("kSaveOption2GBWarning")` USDSaveOptions2GBWarning; + } else { + text -edit -label "" USDSaveOptions2GBWarning; + } + + // When we came from the non-standalone dialog, we immediately save the optionVar. + // Note: we have no choice but to do that now since we aren't notified from + // the File|Save Options. + if (! $standaloneDialog) { + optionVar -intValue mayaUsd_SerializedUsdEditsLocation $opt; + } +} + +global proc int usdFileSaveOptions(int $standaloneDialog) +// +// Description: +// Called to display the Usd File Save Options. +// +// Input Arguments: +// $standaloneDialog - When true, the options are displayed in a standalone dialog. +// When false, then options are displayed within another dialog +// (such as the File|Save Options). +// +{ + // Make sure our optionvars are created. + setOptionVars(false); + + if ($standaloneDialog) + { + int $prompt = `optionVar -q mayaUsd_SerializedUsdEditsLocationPrompt`; + if ($prompt) + { + string $dlgTitle = getMayaUsdString("kSaveOptionTitle"); + string $dlgReturn = `layoutDialog -ui "showUSDSaveOptionsDialogUI" -title $dlgTitle`; + return ("Save" == $dlgReturn); + } + + return true; + } + else + { + usdFileSaveOptions_AddRadioButtons($standaloneDialog); + + int $promptOption = `optionVar -q mayaUsd_SerializedUsdEditsLocationPrompt`; + + checkBox + -label `getMayaUsdString("kSaveOptionAsk")` + -value $promptOption + -ann `getMayaUsdString("kSaveOptionAskAnn")` + -changeCommand "optionVar -intValue mayaUsd_SerializedUsdEditsLocationPrompt #1" + USDSaveOptionsAskCheckBox; + } +} From ea3f861fdfb8b5cc6219e5b6e47295aa664e6a5e Mon Sep 17 00:00:00 2001 From: fowlert Date: Mon, 8 Feb 2021 09:01:22 -0500 Subject: [PATCH 4/6] Implement check for saved edits to the base proxy --- lib/mayaUsd/nodes/proxyShapeBase.cpp | 28 ++++++++++++++++++++++++++++ plugin/adsk/plugin/ProxyShape.cpp | 18 ------------------ plugin/adsk/plugin/ProxyShape.h | 9 --------- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/lib/mayaUsd/nodes/proxyShapeBase.cpp b/lib/mayaUsd/nodes/proxyShapeBase.cpp index 47a6d39398..9d8873b866 100644 --- a/lib/mayaUsd/nodes/proxyShapeBase.cpp +++ b/lib/mayaUsd/nodes/proxyShapeBase.cpp @@ -94,6 +94,8 @@ #include #if defined(WANT_UFE_BUILD) +#include + #include #ifdef UFE_V2_FEATURES_AVAILABLE #include @@ -455,6 +457,30 @@ MStatus MayaUsdProxyShapeBase::compute(const MPlug& plug, MDataBlock& dataBlock) return MS::kUnknownParameter; } +#if defined(WANT_UFE_BUILD) +/* virtual */ +SdfLayerRefPtr MayaUsdProxyShapeBase::computeRootLayer(MDataBlock& dataBlock, const std::string&) +{ + if (LayerManager::supportedNodeType(MPxNode::typeId())) { + auto rootLayerName = dataBlock.inputValue(rootLayerNameAttr).asString(); + return LayerManager::findLayer(UsdMayaUtil::convert(rootLayerName)); + } else { + return nullptr; + } +} + +/* virtual */ +SdfLayerRefPtr MayaUsdProxyShapeBase::computeSessionLayer(MDataBlock& dataBlock) +{ + if (LayerManager::supportedNodeType(MPxNode::typeId())) { + auto sessionLayerName = dataBlock.inputValue(sessionLayerNameAttr).asString(); + return LayerManager::findLayer(UsdMayaUtil::convert(sessionLayerName)); + } else { + return nullptr; + } +} + +#else /* virtual */ SdfLayerRefPtr MayaUsdProxyShapeBase::computeRootLayer(MDataBlock&, const std::string&) { @@ -464,6 +490,8 @@ SdfLayerRefPtr MayaUsdProxyShapeBase::computeRootLayer(MDataBlock&, const std::s /* virtual */ SdfLayerRefPtr MayaUsdProxyShapeBase::computeSessionLayer(MDataBlock&) { return nullptr; } +#endif + MStatus MayaUsdProxyShapeBase::computeInStageDataCached(MDataBlock& dataBlock) { MStatus retValue = MS::kSuccess; diff --git a/plugin/adsk/plugin/ProxyShape.cpp b/plugin/adsk/plugin/ProxyShape.cpp index e499b75959..11c11d5a58 100644 --- a/plugin/adsk/plugin/ProxyShape.cpp +++ b/plugin/adsk/plugin/ProxyShape.cpp @@ -19,10 +19,6 @@ #include #include -#if defined(WANT_UFE_BUILD) -#include -#endif - namespace MAYAUSD_NS_DEF { // ======================================================== @@ -74,18 +70,4 @@ void ProxyShape::postConstructor() #endif } -#if defined(WANT_UFE_BUILD) -SdfLayerRefPtr ProxyShape::computeRootLayer(MDataBlock& dataBlock, const std::string& filePath) -{ - auto rootLayerName = dataBlock.inputValue(rootLayerNameAttr).asString(); - return LayerManager::findLayer(UsdMayaUtil::convert(rootLayerName)); -} - -SdfLayerRefPtr ProxyShape::computeSessionLayer(MDataBlock& dataBlock) -{ - auto sessionLayerName = dataBlock.inputValue(sessionLayerNameAttr).asString(); - return LayerManager::findLayer(UsdMayaUtil::convert(sessionLayerName)); -} -#endif - } // namespace MAYAUSD_NS_DEF diff --git a/plugin/adsk/plugin/ProxyShape.h b/plugin/adsk/plugin/ProxyShape.h index a135476c89..a21525ae2d 100644 --- a/plugin/adsk/plugin/ProxyShape.h +++ b/plugin/adsk/plugin/ProxyShape.h @@ -44,15 +44,6 @@ class ProxyShape : public MayaUsdProxyShapeBase void postConstructor() override; -protected: -#if defined(WANT_UFE_BUILD) - MAYAUSD_PLUGIN_PUBLIC - SdfLayerRefPtr computeRootLayer(MDataBlock&, const std::string&) override; - - MAYAUSD_PLUGIN_PUBLIC - SdfLayerRefPtr computeSessionLayer(MDataBlock&) override; -#endif - private: ProxyShape(); From 85a7da9567dc0617e2f3211c15fa7858012124d5 Mon Sep 17 00:00:00 2001 From: Krystian Ligenza Date: Wed, 10 Feb 2021 05:09:57 -0500 Subject: [PATCH 5/6] Applied clang-format --- lib/mayaUsd/nodes/layerManager.cpp | 1 - lib/usd/ui/layerEditor/saveLayersDialog.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/mayaUsd/nodes/layerManager.cpp b/lib/mayaUsd/nodes/layerManager.cpp index 16d5af8bf5..8f5eb3d353 100644 --- a/lib/mayaUsd/nodes/layerManager.cpp +++ b/lib/mayaUsd/nodes/layerManager.cpp @@ -47,7 +47,6 @@ #include #include #include - #include #include #include diff --git a/lib/usd/ui/layerEditor/saveLayersDialog.cpp b/lib/usd/ui/layerEditor/saveLayersDialog.cpp index b86c3fa31e..0d0f0b478d 100644 --- a/lib/usd/ui/layerEditor/saveLayersDialog.cpp +++ b/lib/usd/ui/layerEditor/saveLayersDialog.cpp @@ -603,5 +603,4 @@ bool SaveLayersDialog::saveLayerFilePathUI(std::string& out_filePath, std::strin return true; } - } // namespace UsdLayerEditor From f7823c872ebe8f30fca38e46bed7564d43f8ee71 Mon Sep 17 00:00:00 2001 From: fowlert Date: Wed, 10 Feb 2021 08:32:09 -0500 Subject: [PATCH 6/6] Removing an unused function in a namespace, which is a build error on OSX/Linux --- lib/usd/ui/layerEditor/layerTreeModel.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/usd/ui/layerEditor/layerTreeModel.cpp b/lib/usd/ui/layerEditor/layerTreeModel.cpp index 105f0b25cf..38a10f1523 100644 --- a/lib/usd/ui/layerEditor/layerTreeModel.cpp +++ b/lib/usd/ui/layerEditor/layerTreeModel.cpp @@ -42,15 +42,6 @@ namespace { const QString LAYER_EDITOR_MIME_TYPE = QStringLiteral("text/plain"); const QString LAYED_EDITOR_MIME_SEP = QStringLiteral(";"); -QStringList getLayerListAsQStringList(const UsdLayerEditor::LayerItemVector& layerItems) -{ - QStringList result; - for (auto item : layerItems) { - result.append(item->data(Qt::DisplayRole).value()); - } - return result; -} - } // namespace namespace UsdLayerEditor {