diff --git a/lib/mayaUsd/ufe/UsdShaderNodeDef.cpp b/lib/mayaUsd/ufe/UsdShaderNodeDef.cpp index 9bbdf4e26d..4e6bbf17a4 100644 --- a/lib/mayaUsd/ufe/UsdShaderNodeDef.cpp +++ b/lib/mayaUsd/ufe/UsdShaderNodeDef.cpp @@ -361,9 +361,7 @@ Ufe::InsertChildCommand::Ptr UsdShaderNodeDef::createNodeCmd( TF_AXIOM(fShaderNodeDef); UsdSceneItem::Ptr parentItem = std::dynamic_pointer_cast(parent); if (parentItem) { - if (parentItem->nodeType() == "Scope" - && parentItem->nodeName() - == UsdUndoAssignNewMaterialCommand::resolvedMaterialScopeName()) { + if (UsdUndoAddNewMaterialCommand::CompatiblePrim(parentItem)) { return UsdUndoAddNewMaterialCommand::create( parentItem, fShaderNodeDef->GetIdentifier()); } diff --git a/lib/mayaUsd/ufe/UsdUndoAddNewPrimCommand.cpp b/lib/mayaUsd/ufe/UsdUndoAddNewPrimCommand.cpp index bac255cbe4..780a34ec0d 100644 --- a/lib/mayaUsd/ufe/UsdUndoAddNewPrimCommand.cpp +++ b/lib/mayaUsd/ufe/UsdUndoAddNewPrimCommand.cpp @@ -22,6 +22,7 @@ #include #include +#include #include namespace { @@ -47,7 +48,11 @@ UsdUndoAddNewPrimCommand::UsdUndoAddNewPrimCommand( const UsdSceneItem::Ptr& usdSceneItem, const std::string& name, const std::string& type) +#ifdef UFE_V4_FEATURES_AVAILABLE + : Ufe::SceneItemResultUndoableCommand() +#else : Ufe::UndoableCommand() +#endif { // First get the stage from the proxy shape. auto ufePath = usdSceneItem->path(); @@ -115,6 +120,11 @@ std::string UsdUndoAddNewPrimCommand::commandString() const return std::string("CreatePrim ") + _primToken.GetText() + " " + Ufe::PathString::string(_newUfePath); } + +Ufe::SceneItem::Ptr UsdUndoAddNewPrimCommand::sceneItem() const +{ + return Ufe::Hierarchy::createItem(newUfePath()); +} #endif const Ufe::Path& UsdUndoAddNewPrimCommand::newUfePath() const { return _newUfePath; } diff --git a/lib/mayaUsd/ufe/UsdUndoAddNewPrimCommand.h b/lib/mayaUsd/ufe/UsdUndoAddNewPrimCommand.h index afe820e28c..e2498ccb62 100644 --- a/lib/mayaUsd/ufe/UsdUndoAddNewPrimCommand.h +++ b/lib/mayaUsd/ufe/UsdUndoAddNewPrimCommand.h @@ -32,7 +32,11 @@ namespace ufe { // This command is not restricted: it is always possible to create a new // prim even in weaker layer since the new prim, by the fact that it is new // cannot have an already-existing opinon that would shadow it. +#ifdef UFE_V4_FEATURES_AVAILABLE +class MAYAUSD_CORE_PUBLIC UsdUndoAddNewPrimCommand : public Ufe::SceneItemResultUndoableCommand +#else class MAYAUSD_CORE_PUBLIC UsdUndoAddNewPrimCommand : public Ufe::UndoableCommand +#endif { public: typedef std::shared_ptr Ptr; @@ -50,6 +54,7 @@ class MAYAUSD_CORE_PUBLIC UsdUndoAddNewPrimCommand : public Ufe::UndoableCommand PXR_NS::UsdPrim newPrim() const; UFE_V4(std::string commandString() const override;) + UFE_V4(Ufe::SceneItem::Ptr sceneItem() const override;) static UsdUndoAddNewPrimCommand::Ptr create(const UsdSceneItem::Ptr& usdSceneItem, const std::string& name, const std::string& type); diff --git a/lib/mayaUsd/ufe/UsdUndoMaterialCommands.cpp b/lib/mayaUsd/ufe/UsdUndoMaterialCommands.cpp index c0674f81b5..72aacf368a 100644 --- a/lib/mayaUsd/ufe/UsdUndoMaterialCommands.cpp +++ b/lib/mayaUsd/ufe/UsdUndoMaterialCommands.cpp @@ -15,7 +15,10 @@ // #include "UsdUndoMaterialCommands.h" +#include "private/UfeNotifGuard.h" + #include +#include #include #include #include @@ -39,10 +42,6 @@ namespace ufe { namespace { -// Pixar uses "Looks" to name the materials scope. The USD asset workgroup recommendations is to -// use "mtl" instead. So we will go with the WG recommendation when creating new material scopes. -static const std::string kDefaultMaterialScopeName("mtl"); - #ifdef UFE_V4_FEATURES_AVAILABLE bool connectShaderToMaterial( Ufe::SceneItem::Ptr shaderItem, @@ -87,6 +86,74 @@ bool connectShaderToMaterial( UsdShadeConnectableAPI::ConnectToSource(materialOutput, shaderOutput); return true; } + +//! Returns true if \p item is a materials scope. +bool isMaterialsScope(const Ufe::SceneItem::Ptr& item) +{ + if (!item) { + return false; + } + + // Must be a scope. + if (item->nodeType() != "Scope") { + return false; + } + + // With the magic name. + if (item->nodeName() == UsdMayaJobExportArgs::GetDefaultMaterialsScopeName()) { + return true; + } + + // Or with only materials inside + auto scopeHierarchy = Ufe::Hierarchy::hierarchy(item); + if (scopeHierarchy) { + for (auto&& child : scopeHierarchy->children()) { + if (child->nodeType() != "Material") { + // At least one non material + return false; + } + } + } + + return true; +} + +//! Searches the children of \p parentPath for a materials scope. Returns a null pointer if no +//! materials scope is found. +Ufe::SceneItem::Ptr getMaterialsScope(const Ufe::Path& parentPath) +{ + auto parent = Ufe::Hierarchy::createItem(parentPath); + if (!parent) { + return nullptr; + } + + auto parentHierarchy = Ufe::Hierarchy::hierarchy(parent); + if (!parentHierarchy) { + return nullptr; + } + + // Find an available materials scope name. + // Usually the materials scope will simply have the default name (e.g. "mtl"). However, if + // that name is used by a non-scope object, a number should be appended (e.g. "mtl1"). If + // this name is not available either, increment the number until an available name is found. + std::string scopeNamePrefix = UsdMayaJobExportArgs::GetDefaultMaterialsScopeName(); + std::string scopeName = scopeNamePrefix; + auto hasName + = [&scopeName](const Ufe::SceneItem::Ptr& item) { return item->nodeName() == scopeName; }; + Ufe::SceneItemList children = parentHierarchy->children(); + for (size_t i = 1;; ++i) { + auto childrenIterator = std::find_if(children.begin(), children.end(), hasName); + if (childrenIterator == children.end()) { + return nullptr; + } + if ((*childrenIterator)->nodeType() == "Scope") { + return *childrenIterator; + } + + // Name is already used by something that is not a scope. Try the next name. + scopeName = scopeNamePrefix + std::to_string(i); + } +} #endif bool _BindMaterialCompatiblePrim(const UsdPrim& usdPrim) @@ -96,7 +163,8 @@ bool _BindMaterialCompatiblePrim(const UsdPrim& usdPrim) // material or a shader. return false; } - if (UsdGeomScope(usdPrim) && usdPrim.GetName() == kDefaultMaterialScopeName) { + if (UsdGeomScope(usdPrim) + && usdPrim.GetName() == UsdMayaJobExportArgs::GetDefaultMaterialsScopeName()) { return false; } if (auto subset = UsdGeomSubset(usdPrim)) { @@ -276,15 +344,8 @@ Ufe::SceneItem::Ptr UsdUndoAssignNewMaterialCommand::insertedChild() const return {}; } -std::string UsdUndoAssignNewMaterialCommand::resolvedMaterialScopeName() -{ - return UsdMayaJobExportArgs::GetDefaultMaterialsScopeName(); -} - void UsdUndoAssignNewMaterialCommand::execute() { - std::string materialsScopeNamePrefix = resolvedMaterialScopeName(); - // Materials cannot be shared between stages. So we create a unique material per stage, // which can then be shared between any number of objects within that stage. for (const auto& selectedInStage : _stagesAndPaths) { @@ -302,63 +363,17 @@ void UsdUndoAssignNewMaterialCommand::execute() // // 1. Create the Scope "materials" if it does not exist: // - auto stagePath = selectedPaths[0].popSegment(); - auto stageHierarchy = Ufe::Hierarchy::hierarchy(Ufe::Hierarchy::createItem(stagePath)); - if (!stageHierarchy) { + auto stageItem + = UsdSceneItem::create(MayaUsd::ufe::stagePath(stage), stage->GetPseudoRoot()); + auto createMaterialsScopeCmd = UsdUndoCreateMaterialsScopeCommand::create(stageItem); + if (!createMaterialsScopeCmd) { markAsFailed(); return; } + createMaterialsScopeCmd->execute(); + _cmds->append(createMaterialsScopeCmd); - // Find an available materials scope name. - // Usually the materials scope will simply have the default name (e.g. "mtl"). However, if - // that name is used by a non-scope object, a number should be appended (e.g. "mtl1"). If - // this name is not available either, increment the number until an available name is found. - Ufe::SceneItem::Ptr materialsScope = nullptr; - std::string materialsScopeName = materialsScopeNamePrefix; - Ufe::SceneItemList children = stageHierarchy->children(); - for (size_t i = 1;; ++i) { - auto hasName = [&materialsScopeName](const Ufe::SceneItem::Ptr& item) { - return item->nodeName() == materialsScopeName; - }; - auto childrenIterator = std::find_if(children.begin(), children.end(), hasName); - if (childrenIterator == children.end()) { - break; - } - if ((*childrenIterator)->nodeType() == "Scope") { - materialsScope = *childrenIterator; - break; - } - - // Name is already used by something that is not a scope. Try the next name. - materialsScopeName = materialsScopeNamePrefix + std::to_string(i); - } - - if (!materialsScope) { - auto createScopeCmd = UsdUndoAddNewPrimCommand::create( - UsdSceneItem::create(MayaUsd::ufe::stagePath(stage), stage->GetPseudoRoot()), - materialsScopeName, - "Scope"); - if (!createScopeCmd) { - markAsFailed(); - return; - } - createScopeCmd->execute(); - _cmds->append(createScopeCmd); - auto scopePath = createScopeCmd->newUfePath(); - // The code automatically appends a "1". We need to rename: - auto itemOps = Ufe::SceneItemOps::sceneItemOps(Ufe::Hierarchy::createItem(scopePath)); - if (!itemOps) { - markAsFailed(); - return; - } - auto rename = itemOps->renameItemCmd(Ufe::PathComponent(materialsScopeName)); - if (!rename.undoableCommand) { - markAsFailed(); - return; - } - _cmds->append(rename.undoableCommand); - materialsScope = rename.item; - } + auto materialsScope = createMaterialsScopeCmd->sceneItem(); if (!materialsScope || materialsScope->path().empty()) { // The _createScopeCmd and/or _renameScopeCmd will have emitted errors. markAsFailed(); @@ -400,8 +415,8 @@ void UsdUndoAssignNewMaterialCommand::execute() // // 3. Create the Shader: // - UsdSceneItem::Ptr materialItem = std::dynamic_pointer_cast( - Ufe::Hierarchy::createItem(createMaterialCmd->newUfePath())); + UsdSceneItem::Ptr materialItem + = std::dynamic_pointer_cast(createMaterialCmd->sceneItem()); auto createShaderCmd = UsdUndoCreateFromNodeDefCommand::create( shaderNodeDef, materialItem, shaderNodeDef->GetFamily().GetString()); if (!createShaderCmd) { @@ -525,32 +540,8 @@ Ufe::SceneItem::Ptr UsdUndoAddNewMaterialCommand::insertedChild() const bool UsdUndoAddNewMaterialCommand::CompatiblePrim(const Ufe::SceneItem::Ptr& target) { - if (!target) { - return false; - } - - // Must be a scope. - if (target->nodeType() != "Scope") { - return false; - } - - // With the magic name. - if (target->nodeName() == UsdUndoAssignNewMaterialCommand::resolvedMaterialScopeName()) { - return true; - } - - // Or with only materials inside - auto scopeHierarchy = Ufe::Hierarchy::hierarchy(target); - if (scopeHierarchy) { - for (auto&& child : scopeHierarchy->children()) { - if (child->nodeType() != "Material") { - // At least one non material - return false; - } - } - } - - return true; + // Must be a materials scope. + return isMaterialsScope(target); } void UsdUndoAddNewMaterialCommand::execute() @@ -591,8 +582,7 @@ void UsdUndoAddNewMaterialCommand::execute() // // Create the Shader: // - auto materialItem = std::dynamic_pointer_cast( - Ufe::Hierarchy::createItem(_createMaterialCmd->newUfePath())); + auto materialItem = std::dynamic_pointer_cast(_createMaterialCmd->sceneItem()); _createShaderCmd = UsdUndoCreateFromNodeDefCommand::create( shaderNodeDef, materialItem, shaderNodeDef->GetFamily().GetString()); if (!_createShaderCmd) { @@ -650,6 +640,89 @@ void UsdUndoAddNewMaterialCommand::markAsFailed() } } +UsdUndoCreateMaterialsScopeCommand::UsdUndoCreateMaterialsScopeCommand( + const UsdSceneItem::Ptr& parentItem) + : _parentItem(nullptr) + , _insertedChild(nullptr) +{ + if (!parentItem || !parentItem->prim().IsActive()) + return; + + _parentItem = parentItem; + _insertedChild = getMaterialsScope(_parentItem->path()); +} + +UsdUndoCreateMaterialsScopeCommand::~UsdUndoCreateMaterialsScopeCommand() { } + +UsdUndoCreateMaterialsScopeCommand::Ptr +UsdUndoCreateMaterialsScopeCommand::create(const UsdSceneItem::Ptr& parentItem) +{ + // Changing the hierarchy of invalid items is not allowed. + if (!parentItem || !parentItem->prim().IsActive()) + return nullptr; + + return std::make_shared(parentItem); +} + +Ufe::SceneItem::Ptr UsdUndoCreateMaterialsScopeCommand::sceneItem() const { return _insertedChild; } + +void UsdUndoCreateMaterialsScopeCommand::execute() +{ + MayaUsd::ufe::InAddOrDeleteOperation ad; + + UsdUndoBlock undoBlock(&_undoableItem); + + if (_insertedChild || !_parentItem) { + return; + } + + // The AddNewPrimCommand automatically appends a "1" to the name, so it cannot create a + // scope with the desired name directly. Create a scope and rename it afterwards. + auto createScopeCmd = UsdUndoAddNewPrimCommand::create(_parentItem, "ScopeName", "Scope"); + if (!createScopeCmd) { + markAsFailed(); + return; + } + createScopeCmd->execute(); + + auto scopeItem = std::dynamic_pointer_cast(createScopeCmd->sceneItem()); + auto materialsScopeName = UsdMayaJobExportArgs::GetDefaultMaterialsScopeName(); + auto renameCmd = UsdUndoRenameCommand::create(scopeItem, materialsScopeName); + if (!renameCmd) { + markAsFailed(); + return; + } + renameCmd->execute(); + + scopeItem = renameCmd->renamedItem(); + if (!scopeItem || scopeItem->path().empty()) { + markAsFailed(); + return; + } + + _insertedChild = scopeItem; +} + +void UsdUndoCreateMaterialsScopeCommand::undo() +{ + MayaUsd::ufe::InAddOrDeleteOperation ad; + + _undoableItem.undo(); +} + +void UsdUndoCreateMaterialsScopeCommand::redo() +{ + MayaUsd::ufe::InAddOrDeleteOperation ad; + + _undoableItem.redo(); +} + +void UsdUndoCreateMaterialsScopeCommand::markAsFailed() +{ + MayaUsd::ufe::InAddOrDeleteOperation ad; + undo(); +} + #endif } // namespace ufe } // namespace MAYAUSD_NS_DEF diff --git a/lib/mayaUsd/ufe/UsdUndoMaterialCommands.h b/lib/mayaUsd/ufe/UsdUndoMaterialCommands.h index 66decce0b1..e672611935 100644 --- a/lib/mayaUsd/ufe/UsdUndoMaterialCommands.h +++ b/lib/mayaUsd/ufe/UsdUndoMaterialCommands.h @@ -123,9 +123,6 @@ class MAYAUSD_CORE_PUBLIC UsdUndoAssignNewMaterialCommand : public Ufe::InsertCh void undo() override; void redo() override; - // Returns the name of the material scope in this Maya session. - static std::string resolvedMaterialScopeName(); - private: void markAsFailed(); @@ -178,6 +175,45 @@ class MAYAUSD_CORE_PUBLIC UsdUndoAddNewMaterialCommand : public Ufe::InsertChild UsdUndoCreateFromNodeDefCommand::Ptr _createShaderCmd; }; // UsdUndoAddNewMaterialCommand + +//! \brief This command is used to create a materials scope under a specified parent item. A +//! materials scope is a USD Scope with a special name (usually "mtl"), which holds materials. By +//! convention, all materials should reside within such a scope. +class MAYAUSD_CORE_PUBLIC UsdUndoCreateMaterialsScopeCommand + : public Ufe::SceneItemResultUndoableCommand +{ +public: + typedef std::shared_ptr Ptr; + + UsdUndoCreateMaterialsScopeCommand(const UsdSceneItem::Ptr& parentItem); + ~UsdUndoCreateMaterialsScopeCommand() override; + + // Delete the copy/move constructors assignment operators. + UsdUndoCreateMaterialsScopeCommand(const UsdUndoCreateMaterialsScopeCommand&) = delete; + UsdUndoCreateMaterialsScopeCommand& operator=(const UsdUndoCreateMaterialsScopeCommand&) + = delete; + UsdUndoCreateMaterialsScopeCommand(UsdUndoCreateMaterialsScopeCommand&&) = delete; + UsdUndoCreateMaterialsScopeCommand& operator=(UsdUndoCreateMaterialsScopeCommand&&) = delete; + + //! Create a UsdUndoCreateMaterialsScopeCommand that creates a new materials scope under \p + //! parentItem. If there already is a materials scope under \p parentItem, the command will + //! not create a new materials scope but simply point to the existing one. + static UsdUndoCreateMaterialsScopeCommand::Ptr create(const UsdSceneItem::Ptr& parentItem); + + Ufe::SceneItem::Ptr sceneItem() const override; + + void execute() override; + void undo() override; + void redo() override; + +private: + void markAsFailed(); + + UsdSceneItem::Ptr _parentItem; + Ufe::SceneItem::Ptr _insertedChild; + + UsdUndoableItem _undoableItem; +}; // UsdUndoCreateMaterialsScopeCommand #endif } // namespace ufe