diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index ee2c49c121..586604f859 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -248,6 +248,12 @@ if(UFE_FOUND) ufe/UsdObject3dHandler.cpp ufe/UsdUndoCreateGroupCommand.cpp ) + if(UFE_PREVIEW_VERSION_NUM GREATER_EQUAL 2009) + list(APPEND mayaUsd_src + ufe/UsdContextOps.cpp + ufe/UsdContextOpsHandler.cpp + ) + endif() endif() endif() @@ -388,6 +394,8 @@ if(UFE_FOUND) ufe/UsdAttribute.h ufe/UsdAttributes.h ufe/UsdAttributesHandler.h + ufe/UsdContextOps.h + ufe/UsdContextOpsHandler.h ufe/UsdObject3d.h ufe/UsdObject3dHandler.h ufe/UsdUndoCreateGroupCommand.h diff --git a/lib/ufe/Global.cpp b/lib/ufe/Global.cpp index a414aaf3ab..98973309df 100644 --- a/lib/ufe/Global.cpp +++ b/lib/ufe/Global.cpp @@ -30,6 +30,9 @@ // Note: must come after include of ufe files so we have the define. #include "UsdAttributesHandler.h" #include "UsdObject3dHandler.h" +#if UFE_PREVIEW_VERSION_NUM >= 2009 +#include "UsdContextOpsHandler.h" +#endif #else #include "UfeVersionCompat.h" #endif @@ -97,10 +100,17 @@ MStatus initialize() auto usdSceneItemOpsHandler = UsdSceneItemOpsHandler::create(); UFE_V2(auto usdAttributesHandler = UsdAttributesHandler::create();) UFE_V2(auto usdObject3dHandler = UsdObject3dHandler::create();) +#if UFE_PREVIEW_VERSION_NUM >= 2009 + UFE_V2(auto usdContextOpsHandler = UsdContextOpsHandler::create();) +#endif g_USDRtid = Ufe::RunTimeMgr::instance().register_( kUSDRunTimeName, usdHierHandler, usdTrans3dHandler, usdSceneItemOpsHandler - UFE_V2(, usdAttributesHandler, usdObject3dHandler)); + UFE_V2(, usdAttributesHandler, usdObject3dHandler) +#if UFE_PREVIEW_VERSION_NUM >= 2009 + UFE_V2(, usdContextOpsHandler) +#endif + ); #if !defined(NDEBUG) assert(g_USDRtid != 0); diff --git a/lib/ufe/UsdContextOps.cpp b/lib/ufe/UsdContextOps.cpp new file mode 100644 index 0000000000..7956bd38b7 --- /dev/null +++ b/lib/ufe/UsdContextOps.cpp @@ -0,0 +1,191 @@ +// +// 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 "UsdContextOps.h" +#include "Utils.h" + +#include +#include + +#include +#include "pxr/base/tf/diagnostic.h" + +#include + +#include + +namespace { + +//! \brief Undoable command for variant selection change +class SetVariantSelectionUndoableCommand : public Ufe::UndoableCommand +{ +public: + + SetVariantSelectionUndoableCommand( + const UsdPrim& prim, + const Ufe::ContextOps::ItemPath& itemPath + ) : fVarSet(prim.GetVariantSets().GetVariantSet(itemPath[1])), + fOldSelection(fVarSet.GetVariantSelection()), + fNewSelection(itemPath[2]) + {} + + void undo() override { fVarSet.SetVariantSelection(fOldSelection); } + + void redo() override { fVarSet.SetVariantSelection(fNewSelection); } + +private: + + UsdVariantSet fVarSet; + const std::string fOldSelection; + const std::string fNewSelection; +}; + +} + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdContextOps::UsdContextOps() + : Ufe::ContextOps() +{ +} + +UsdContextOps::~UsdContextOps() +{ +} + +/*static*/ +UsdContextOps::Ptr UsdContextOps::create() +{ + return std::make_shared(); +} + +void UsdContextOps::setItem(const UsdSceneItem::Ptr& item) +{ + fPrim = item->prim(); + fItem = item; +} + +const Ufe::Path& UsdContextOps::path() const +{ + return fItem->path(); +} + +//------------------------------------------------------------------------------ +// Ufe::ContextOps overrides +//------------------------------------------------------------------------------ + +Ufe::SceneItem::Ptr UsdContextOps::sceneItem() const +{ + return fItem; +} + +Ufe::ContextOps::Items UsdContextOps::getItems( + const Ufe::ContextOps::ItemPath& itemPath +) const +{ + Ufe::ContextOps::Items items; + if (itemPath.empty()) { + // Top-level items. Variant sets and visibility. + if (fPrim.HasVariantSets()) { + items.emplace_back( + "Variant Sets", "Variant Sets", Ufe::ContextItem::kHasChildren); + } + auto attributes = Ufe::Attributes::attributes(sceneItem()); + if (attributes) { + auto visibility = + std::dynamic_pointer_cast( + attributes->attribute("visibility")); + if (visibility) { + auto current = visibility->get(); + const std::string l = (current == "invisible") ? + std::string("Make Visible") : std::string("Make Invisible"); + items.emplace_back("Toggle Visibility", l); + } + } + } + else { + if (itemPath[0] == "Variant Sets") { + UsdVariantSets varSets = fPrim.GetVariantSets(); + std::vector varSetsNames; + varSets.GetNames(&varSetsNames); + + if (itemPath.size() == 1u) { + // Variant sets list. + for (auto i = varSetsNames.crbegin(); i != varSetsNames.crend(); ++i) { + + items.emplace_back(*i, *i, Ufe::ContextItem::kHasChildren); + } + } + else { + // Variants of a given variant set. Second item in the path is + // the variant set name. + assert(itemPath.size() == 2u); + + UsdVariantSet varSet = varSets.GetVariantSet(itemPath[1]); + auto selected = varSet.GetVariantSelection(); + + const auto varNames = varSet.GetVariantNames(); + for (const auto& vn : varNames) { + const bool checked(vn == selected); + items.emplace_back(vn, vn, Ufe::ContextItem::kNoChildren, + Ufe::ContextItem::kCheckable, checked, + Ufe::ContextItem::kExclusive); + } + } // Variants of a variant set + } // Variant sets + } // Top-level items + + return items; +} + +Ufe::UndoableCommand::Ptr UsdContextOps::doOpCmd(const ItemPath& itemPath) +{ + // Empty argument means no operation was specified, error. + if (itemPath.empty()) { + TF_CODING_ERROR("Empty path means no operation was specified"); + return nullptr; + } + + if (itemPath[0] == "Variant Sets") { + // Operation is to set a variant in a variant set. Need both the + // variant set and the variant as arguments to the operation. + if (itemPath.size() != 3u) { + TF_CODING_ERROR("Wrong number of arguments"); + return nullptr; + } + + // At this point we know we have enough arguments to execute the + // operation. + return std::make_shared( + fPrim, itemPath); + } // Variant sets + else if (itemPath[0] == "Toggle Visibility") { + auto attributes = Ufe::Attributes::attributes(sceneItem()); + assert(attributes); + auto visibility = std::dynamic_pointer_cast( + attributes->attribute("visibility")); + assert(visibility); + auto current = visibility->get(); + return visibility->setCmd( + current == "invisible" ? "inherited" : "invisible"); + } // Visibility + + return nullptr; +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdContextOps.h b/lib/ufe/UsdContextOps.h new file mode 100644 index 0000000000..deaf98464b --- /dev/null +++ b/lib/ufe/UsdContextOps.h @@ -0,0 +1,73 @@ +// +// 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. +// +#pragma once + +#include "../base/api.h" + +#include "UsdSceneItem.h" + +#include +#include + +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Interface for scene item context operations. +/*! + This class defines the interface that USD run-time implements to + provide contextual operation support (example Outliner context menu). + + This class is not copy-able, nor move-able. + + \see UFE ContextOps class documentation for more details +*/ +class MAYAUSD_CORE_PUBLIC UsdContextOps : public Ufe::ContextOps +{ +public: + typedef std::shared_ptr Ptr; + + UsdContextOps(); + ~UsdContextOps() override; + + // Delete the copy/move constructors assignment operators. + UsdContextOps(const UsdContextOps&) = delete; + UsdContextOps& operator=(const UsdContextOps&) = delete; + UsdContextOps(UsdContextOps&&) = delete; + UsdContextOps& operator=(UsdContextOps&&) = delete; + + //! Create a UsdContextOps. + static UsdContextOps::Ptr create(); + + void setItem(const UsdSceneItem::Ptr& item); + const Ufe::Path& path() const; + + // Ufe::ContextOps overrides + Ufe::SceneItem::Ptr sceneItem() const override; + Items getItems(const ItemPath& itemPath) const override; + Ufe::UndoableCommand::Ptr doOpCmd(const ItemPath& itemPath) override; + +private: + UsdSceneItem::Ptr fItem; + UsdPrim fPrim; + +}; // UsdContextOps + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdContextOpsHandler.cpp b/lib/ufe/UsdContextOpsHandler.cpp new file mode 100644 index 0000000000..4430c3262f --- /dev/null +++ b/lib/ufe/UsdContextOpsHandler.cpp @@ -0,0 +1,53 @@ +// +// 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 "UsdContextOpsHandler.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdContextOpsHandler::UsdContextOpsHandler() + : Ufe::ContextOpsHandler() +{ + fUsdContextOps = UsdContextOps::create(); +} + +UsdContextOpsHandler::~UsdContextOpsHandler() +{ +} + +/*static*/ +UsdContextOpsHandler::Ptr UsdContextOpsHandler::create() +{ + return std::make_shared(); +} + +//------------------------------------------------------------------------------ +// Ufe::ContextOpsHandler overrides +//------------------------------------------------------------------------------ + +Ufe::ContextOps::Ptr UsdContextOpsHandler::contextOps(const Ufe::SceneItem::Ptr& item) const +{ + UsdSceneItem::Ptr usdItem = std::dynamic_pointer_cast(item); +#if !defined(NDEBUG) + assert(usdItem); +#endif + fUsdContextOps->setItem(usdItem); + return fUsdContextOps; +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdContextOpsHandler.h b/lib/ufe/UsdContextOpsHandler.h new file mode 100644 index 0000000000..7f5a601532 --- /dev/null +++ b/lib/ufe/UsdContextOpsHandler.h @@ -0,0 +1,56 @@ +// +// 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. +// +#pragma once + +#include "../base/api.h" + +#include "UsdContextOps.h" + +#include + +//PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Interface to create a UsdContextOps interface object. +class MAYAUSD_CORE_PUBLIC UsdContextOpsHandler : public Ufe::ContextOpsHandler +{ +public: + typedef std::shared_ptr Ptr; + + UsdContextOpsHandler(); + ~UsdContextOpsHandler() override; + + // Delete the copy/move constructors assignment operators. + UsdContextOpsHandler(const UsdContextOpsHandler&) = delete; + UsdContextOpsHandler& operator=(const UsdContextOpsHandler&) = delete; + UsdContextOpsHandler(UsdContextOpsHandler&&) = delete; + UsdContextOpsHandler& operator=(UsdContextOpsHandler&&) = delete; + + //! Create a UsdContextOpsHandler. + static UsdContextOpsHandler::Ptr create(); + + // Ufe::ContextOpsHandler overrides + Ufe::ContextOps::Ptr contextOps(const Ufe::SceneItem::Ptr& item) const override; + +private: + UsdContextOps::Ptr fUsdContextOps; + +}; // UsdContextOpsHandler + +} // namespace ufe +} // namespace MayaUsd diff --git a/test/lib/ufe/CMakeLists.txt b/test/lib/ufe/CMakeLists.txt index eef035e08c..ba4964ee9a 100644 --- a/test/lib/ufe/CMakeLists.txt +++ b/test/lib/ufe/CMakeLists.txt @@ -51,6 +51,11 @@ if(CMAKE_UFE_V2_FEATURES_AVAILABLE) # testTransform3dTranslate.py testRename.py ) + if(UFE_PREVIEW_VERSION_NUM GREATER_EQUAL 2009) + list(APPEND test_script_files + testContextOps.py + ) + endif() endif() # copy ufe tests to ${CMAKE_CURRENT_BINARY_DIR} and run them from there diff --git a/test/lib/ufe/testContextOps.py b/test/lib/ufe/testContextOps.py new file mode 100644 index 0000000000..7aef836d92 --- /dev/null +++ b/test/lib/ufe/testContextOps.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python + +# +# 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. +# + +import maya.cmds as cmds +import maya.internal.ufeSupport.ufeCmdWrapper as ufeCmd + +from pxr import UsdGeom + +from ufeTestUtils import usdUtils, mayaUtils +import ufe + +import unittest + +class ContextOpsTestCase(unittest.TestCase): + '''Verify the ContextOps interface for the USD runtime.''' + + pluginsLoaded = False + + @classmethod + def setUpClass(cls): + if not cls.pluginsLoaded: + cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded() + + def setUp(self): + ''' Called initially to set up the maya test environment ''' + # Load plugins + self.assertTrue(self.pluginsLoaded) + + # Open top_layer.ma scene in test-samples + mayaUtils.openTopLayerScene() + + # Clear selection to start off. + ufe.GlobalSelection.get().clear() + + # Select Ball_35. + ball35Path = ufe.Path([ + mayaUtils.createUfePathSegment("|world|transform1|proxyShape1"), + usdUtils.createUfePathSegment("/Room_set/Props/Ball_35")]) + self.ball35Item = ufe.Hierarchy.createItem(ball35Path) + + ufe.GlobalSelection.get().append(self.ball35Item) + + # Create a ContextOps interface for it. + self.contextOps = ufe.ContextOps.contextOps(self.ball35Item) + + def testGetItems(self): + # The top-level context items are visibility (for all prims) and + # variant sets, for those prims that have them (such as Ball_35). + contextItems = self.contextOps.getItems([]) + contextItemStrings = [c.item for c in contextItems] + + self.assertIn('Variant Sets', contextItemStrings) + self.assertIn('Toggle Visibility', contextItemStrings) + + # Ball_35 has a single variant set, which has children. + contextItems = self.contextOps.getItems(['Variant Sets']) + contextItemStrings = [c.item for c in contextItems] + + self.assertListEqual(['shadingVariant'], contextItemStrings) + self.assertTrue(contextItems[0].hasChildren) + + # Initial shadingVariant is "Ball_8" + contextItems = self.contextOps.getItems( + ['Variant Sets', 'shadingVariant']) + self.assertGreater(len(contextItems), 1) + for c in contextItems: + self.assertFalse(c.hasChildren) + self.assertTrue(c.checkable) + if c.checked: + self.assertEqual(c.item, 'Ball_8') + + def testDoOp(self): + + # Change visibility, undo / redo. + cmd = self.contextOps.doOpCmd(['Toggle Visibility']) + self.assertIsNotNone(cmd) + + attrs = ufe.Attributes.attributes(self.ball35Item) + self.assertIsNotNone(attrs) + visibility = attrs.attribute(UsdGeom.Tokens.visibility) + self.assertIsNotNone(visibility) + + # Initially, Ball_35 has inherited visibility. + self.assertEqual(visibility.get(), UsdGeom.Tokens.inherited) + + ufeCmd.execute(cmd) + + self.assertEqual(visibility.get(), UsdGeom.Tokens.invisible) + + cmds.undo() + + self.assertEqual(visibility.get(), UsdGeom.Tokens.inherited) + + cmds.redo() + + self.assertEqual(visibility.get(), UsdGeom.Tokens.invisible) + + cmds.undo() + + # Change variant in variant set. + def shadingVariant(): + contextItems = self.contextOps.getItems( + ['Variant Sets', 'shadingVariant']) + + for c in contextItems: + if c.checked: + return c.item + + self.assertEqual(shadingVariant(), 'Ball_8') + + cmd = self.contextOps.doOpCmd( + ['Variant Sets', 'shadingVariant', 'Cue']) + self.assertIsNotNone(cmd) + + ufeCmd.execute(cmd) + + self.assertEqual(shadingVariant(), 'Cue') + + cmds.undo() + + self.assertEqual(shadingVariant(), 'Ball_8') + + cmds.redo() + + self.assertEqual(shadingVariant(), 'Cue') + + cmds.undo()