Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAYA-129059 - Add command to create stages. #3045

Merged
merged 1 commit into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/mayaUsd/ufe/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ if(CMAKE_UFE_V4_FEATURES_AVAILABLE)
UsdUndoAttributesCommands.cpp
UsdTransform3dRead.cpp
UsdUndoConnectionCommands.cpp
UsdUndoCreateStageWithNewLayerCommand.cpp
)
endif()

Expand Down Expand Up @@ -302,6 +303,7 @@ if(CMAKE_UFE_V4_FEATURES_AVAILABLE)
UsdUndoAttributesCommands.h
UsdTransform3dRead.h
UsdUndoConnectionCommands.h
UsdUndoCreateStageWithNewLayerCommand.h
)
endif()

Expand Down
211 changes: 211 additions & 0 deletions lib/mayaUsd/ufe/UsdUndoCreateStageWithNewLayerCommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
//
// Copyright 2023 Autodesk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "UsdUndoCreateStageWithNewLayerCommand.h"

#include <mayaUsd/ufe/UsdUndoRenameCommand.h>
#include <mayaUsd/ufe/Utils.h>
#include <mayaUsd/undo/OpUndoItems.h>

#include <ufe/hierarchy.h>

PXR_NAMESPACE_USING_DIRECTIVE

namespace MAYAUSD_NS_DEF {
namespace ufe {

UsdUndoCreateStageWithNewLayerCommand::UsdUndoCreateStageWithNewLayerCommand(
const Ufe::SceneItem::Ptr& parentItem)
: _parentItem(nullptr)
, _insertedChild(nullptr)
, _createTransformDagMod(MDagModifierUndoItem::create("Create transform"))
, _createProxyShapeDagMod(MDagModifierUndoItem::create("Create Stage with new Layer"))
, _success(false)
{
if (!TF_VERIFY(parentItem))
return;

_parentItem = parentItem;
}

UsdUndoCreateStageWithNewLayerCommand::~UsdUndoCreateStageWithNewLayerCommand() { }

UsdUndoCreateStageWithNewLayerCommand::Ptr
UsdUndoCreateStageWithNewLayerCommand::create(const Ufe::SceneItem::Ptr& parentItem)
{
if (!parentItem) {
return nullptr;
}

return std::make_shared<UsdUndoCreateStageWithNewLayerCommand>(parentItem);
}

Ufe::SceneItem::Ptr UsdUndoCreateStageWithNewLayerCommand::sceneItem() const
{
return _insertedChild;
}

void UsdUndoCreateStageWithNewLayerCommand::execute()
{
if (!_parentItem) {
return;
}

bool createTransformSuccess = false;
try {
// Get a MObject from the parent scene item.
// Note: If and only if the parent is the world node, MDagPath::transform() will set status
// to kInvalidParameter. In this case MObject::kNullObj is returned, which is a valid parent
// object. Thus, kInvalidParameter will not be treated as a failure.
MStatus status;
MDagPath parentDagPath = MayaUsd::ufe::ufeToDagPath(_parentItem->path());
MObject parentObject = parentDagPath.transform(&status);
if (status != MStatus::kInvalidParameter && MFAIL(status)) {
throw std::runtime_error("");
}

// Create a transform node.
// Note: It would be possible to create the transform and the proxy shape in one doIt() call
// of a single MDagModifier. However, doing so causes notifications to be sent in a
// different order, which triggers a `TF_VERIFY(g_StageMap.isDirty())` in
// StagesSubject::onStageSet(). Using a separate MDagModifier to create the transform seems
// more robust and avoids triggering the TF_VERIFY.
MObject transformObj;
transformObj = _createTransformDagMod.createNode("transform", parentObject, &status);
if (MFAIL(status)) {
throw std::runtime_error("");
}
TF_VERIFY(!transformObj.isNull());
status = _createTransformDagMod.doIt();
if (MFAIL(status)) {
throw std::runtime_error("");
}
createTransformSuccess = true;

// Create a proxy shape.
MObject proxyShape;
proxyShape = _createProxyShapeDagMod.createNode("mayaUsdProxyShape", transformObj, &status);
if (MFAIL(status)) {
throw std::runtime_error("");
}
TF_VERIFY(!proxyShape.isNull());

// Rename the transform and the proxy shape.
// Note: The transform is renamed twice. The first rename operation renames it from its
// default name "transform1" to "stage1". The number-suffix will be automatically
// incremented if necessary. The second rename operation renames it from "stageX" to
// "stage1". This doesn't do anything for the transform itself but it will adjust the
// number-suffix of the proxy shape according to the suffix of the transform, because they
// now share the common prefix "stage".
status = _createProxyShapeDagMod.renameNode(proxyShape, "stageShape1");
if (MFAIL(status)) {
throw std::runtime_error("");
}
status = _createProxyShapeDagMod.renameNode(transformObj, "stage1");
if (MFAIL(status)) {
throw std::runtime_error("");
}
status = _createProxyShapeDagMod.renameNode(transformObj, "stage1");
if (MFAIL(status)) {
throw std::runtime_error("");
}

// Get the global `time1` object and its `outTime` attribute.
MSelectionList selection;
selection.add("time1");
MObject time1;
status = selection.getDependNode(0, time1);
if (MFAIL(status)) {
throw std::runtime_error("");
}
MFnDependencyNode time1DepNodeFn(time1, &status);
if (MFAIL(status)) {
throw std::runtime_error("");
}
MObject time1OutTimeAttr = time1DepNodeFn.attribute("outTime", &status);
if (MFAIL(status)) {
throw std::runtime_error("");
}

// Get the `time` attribute of the newly created mayaUsdProxyShape.
MDagPath proxyShapeDagPath;
status = MDagPath::getAPathTo(proxyShape, proxyShapeDagPath);
if (MFAIL(status)) {
throw std::runtime_error("");
}
MFnDependencyNode proxyShapeDepNodeFn(proxyShapeDagPath.node(), &status);
if (MFAIL(status)) {
throw std::runtime_error("");
}
MObject proxyShapeTimeAttr = proxyShapeDepNodeFn.attribute("time", &status);
if (MFAIL(status)) {
throw std::runtime_error("");
}

// Connect `time1.outTime` to `proxyShapde.time`.
status = _createProxyShapeDagMod.connect(
time1, time1OutTimeAttr, proxyShape, proxyShapeTimeAttr);
if (MFAIL(status)) {
throw std::runtime_error("");
}

// Execute the operations.
status = _createProxyShapeDagMod.doIt();
if (MFAIL(status)) {
throw std::runtime_error("");
}
_success = true;

// Create a UFE scene item for the newly created mayaUsdProxyShape.
Ufe::Path proxyShapeUfePath = MayaUsd::ufe::dagPathToUfe(proxyShapeDagPath);
_insertedChild = Ufe::Hierarchy::createItem(proxyShapeUfePath);

// Refresh the cache of the stage map.
// When creating the proxy shape, the stage map gets dirtied and cleaned. Afterwards, the
// proxy shape is renamed. The stage map does not observe the Maya data model, so renaming
// does not dirty the stage map again. Thus, the cache is in an invalid state, where it
// contains the path of the proxy shape before it was renamed. Calling getProxyShape()
// refreshes the cache. See comments within UsdStageMap::proxyShape() for more details.
getProxyShape(proxyShapeUfePath);
} catch (const std::exception&) {
if (createTransformSuccess) {
_createTransformDagMod.undoIt();
}
}
}

void UsdUndoCreateStageWithNewLayerCommand::undo()
{
if (_success) {
_createProxyShapeDagMod.undoIt();
_createTransformDagMod.undoIt();
}
}

void UsdUndoCreateStageWithNewLayerCommand::redo()
{
if (_success) {
_createTransformDagMod.doIt();
_createProxyShapeDagMod.doIt();

// Refresh the cache of the stage map.
if (_insertedChild) {
getProxyShape(_insertedChild->path());
}
}
}

} // namespace ufe
} // namespace MAYAUSD_NS_DEF
74 changes: 74 additions & 0 deletions lib/mayaUsd/ufe/UsdUndoCreateStageWithNewLayerCommand.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// Copyright 2023 Autodesk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef MAYAUSD_UFE_CREATESTAGEWITHNEWLAYERCOMMAND_H
#define MAYAUSD_UFE_CREATESTAGEWITHNEWLAYERCOMMAND_H

#include <mayaUsd/base/api.h>

#include <maya/MDagModifier.h>
#include <ufe/undoableCommand.h>

namespace MAYAUSD_NS_DEF {
namespace ufe {

//! \brief This command is used to create a new empty stage in memory.
class MAYAUSD_CORE_PUBLIC UsdUndoCreateStageWithNewLayerCommand
: public Ufe::SceneItemResultUndoableCommand
{
public:
typedef std::shared_ptr<UsdUndoCreateStageWithNewLayerCommand> Ptr;

UsdUndoCreateStageWithNewLayerCommand(const Ufe::SceneItem::Ptr& parentItem);
~UsdUndoCreateStageWithNewLayerCommand() override;

// Delete the copy/move constructors assignment operators.
UsdUndoCreateStageWithNewLayerCommand(const UsdUndoCreateStageWithNewLayerCommand&) = delete;
UsdUndoCreateStageWithNewLayerCommand& operator=(const UsdUndoCreateStageWithNewLayerCommand&)
= delete;
UsdUndoCreateStageWithNewLayerCommand(UsdUndoCreateStageWithNewLayerCommand&&) = delete;
UsdUndoCreateStageWithNewLayerCommand& operator=(UsdUndoCreateStageWithNewLayerCommand&&)
= delete;

//! Create a UsdUndoCreateStageWithNewLayerCommand. Executing this command should produce the
//! following:
//! - Proxyshape
//! - Stage
//! - Session Layer
//! - Anonymous Root Layer (this is set as the target layer)
//! Since the proxy shape does not have a USD file associated (in the .filePath attribute), the
//! proxy shape base will create an empty stage in memory. This will create the session and root
//! layer as well.
static UsdUndoCreateStageWithNewLayerCommand::Ptr create(const Ufe::SceneItem::Ptr& parentItem);

Ufe::SceneItem::Ptr sceneItem() const override;

void execute() override;
void undo() override;
void redo() override;

private:
Ufe::SceneItem::Ptr _parentItem;
Ufe::SceneItem::Ptr _insertedChild;

MDagModifier& _createTransformDagMod;
MDagModifier& _createProxyShapeDagMod;
bool _success;
}; // UsdUndoCreateStageWithNewLayerCommand

} // namespace ufe
} // namespace MAYAUSD_NS_DEF

#endif // MAYAUSD_UFE_CREATESTAGEWITHNEWLAYERCOMMAND_H
29 changes: 29 additions & 0 deletions lib/mayaUsd/ufe/wrapUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
#include <ufe/pathString.h>
#endif

#ifdef UFE_V4_FEATURES_AVAILABLE
#include <mayaUsd/ufe/UsdUndoCreateStageWithNewLayerCommand.h>

#include <ufe/undoableCommandMgr.h>
#endif

#include <boost/python.hpp>
#include <boost/python/def.hpp>

Expand Down Expand Up @@ -239,6 +245,25 @@ PXR_NS::TfTokenVector getProxyShapePurposes(const std::string& ufePathString)
return ufe::getProxyShapePurposes(path);
}

#ifdef UFE_V4_FEATURES_AVAILABLE
std::string createStageWithNewLayer(const std::string& parentPathString)
{
auto parentPath = Ufe::PathString::path(parentPathString);
auto parent = Ufe::Hierarchy::createItem(parentPath);
auto command = MayaUsd::ufe::UsdUndoCreateStageWithNewLayerCommand::create(parent);
if (!command) {
return "";
}

Ufe::UndoableCommandMgr::instance().executeCmd(command);
if (!command->sceneItem()) {
return "";
}

return Ufe::PathString::string(command->sceneItem()->path());
}
#endif

void wrapUtils()
{
#ifdef UFE_V2_FEATURES_AVAILABLE
Expand All @@ -247,6 +272,10 @@ void wrapUtils()
def("getNodeTypeFromRawItem", getNodeTypeFromRawItem);
#endif

#ifdef UFE_V4_FEATURES_AVAILABLE
def("createStageWithNewLayer", createStageWithNewLayer);
#endif

// Because mayaUsd and UFE have incompatible Python bindings that do not
// know about each other (provided by Boost Python and pybind11,
// respectively), we cannot pass in or return UFE objects such as Ufe::Path
Expand Down
17 changes: 12 additions & 5 deletions plugin/adsk/scripts/mayaUsd_createStageWithNewLayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#

import maya.cmds as cmds
import mayaUsd

def createStageWithNewLayer():
"""For use in aggregating USD (Assembly) and creating layer structures (Layout)
Expand All @@ -29,8 +30,14 @@ def createStageWithNewLayer():
# Simply create a proxy shape. Since it does not have a USD file associated
# (in the .filePath attribute), the proxy shape base will create an empty
# stage in memory. This will create the session and root layer as well.
shapeNode = cmds.createNode('mayaUsdProxyShape', skipSelect=True, name='stageShape1')
cmds.connectAttr('time1.outTime', shapeNode+'.time')
cmds.select(shapeNode, replace=True)
fullPath = cmds.ls(shapeNode, long=True)
return fullPath[0]
if hasattr(mayaUsd, 'ufe') and hasattr(mayaUsd.ufe, 'createStageWithNewLayer'):
shapeNode = mayaUsd.ufe.createStageWithNewLayer('|world')
cmds.select(shapeNode, replace=True)
return shapeNode
else:
shapeNode = cmds.createNode('mayaUsdProxyShape', skipSelect=True, name='stageShape1')
cmds.connectAttr('time1.outTime', shapeNode+'.time')
cmds.select(shapeNode, replace=True)
fullPath = cmds.ls(shapeNode, long=True)
return fullPath[0]

Loading