diff --git a/cmake/modules/FindUFE.cmake b/cmake/modules/FindUFE.cmake index ddd1a89674..a36da10440 100644 --- a/cmake/modules/FindUFE.cmake +++ b/cmake/modules/FindUFE.cmake @@ -11,6 +11,7 @@ # UFE_VERSION UFE version (major.minor.patch) from ufe.h # UFE_LIGHTS_SUPPORT Presence of UFE lights support # UFE_SCENE_SEGMENT_SUPPORT Presence of UFE scene segment support +# UFE_PREVIEW_FEATURES List of all features introduced gradually in the UFE preview version # find_path(UFE_INCLUDE_DIR @@ -82,6 +83,13 @@ find_library(UFE_LIBRARY NO_DEFAULT_PATH ) +# Gather all preview features that might be there or not into a single list: +list(APPEND UFE_PREVIEW_FEATURES ufe) + +if (UFE_INCLUDE_DIR AND EXISTS "${UFE_INCLUDE_DIR}/ufe/batchOpsHandler.h") + list(APPEND UFE_PREVIEW_FEATURES v4_BatchOps) +endif() + # Handle the QUIETLY and REQUIRED arguments and set UFE_FOUND to TRUE if # all listed variables are TRUE. include(FindPackageHandleStandardArgs) @@ -90,6 +98,7 @@ find_package_handle_standard_args(UFE REQUIRED_VARS UFE_INCLUDE_DIR UFE_LIBRARY + UFE_PREVIEW_FEATURES VERSION_VAR UFE_VERSION ) @@ -98,6 +107,7 @@ if(UFE_FOUND) message(STATUS "UFE include dir: ${UFE_INCLUDE_DIR}") message(STATUS "UFE library: ${UFE_LIBRARY}") message(STATUS "UFE version: ${UFE_VERSION}") + message(STATUS "UFE preview features: ${UFE_PREVIEW_FEATURES}") endif() set(UFE_LIGHTS_SUPPORT FALSE CACHE INTERNAL "ufeLights") diff --git a/lib/mayaUsd/render/vp2RenderDelegate/material.cpp b/lib/mayaUsd/render/vp2RenderDelegate/material.cpp index 8cc4c1b15c..ec85e3be6e 100644 --- a/lib/mayaUsd/render/vp2RenderDelegate/material.cpp +++ b/lib/mayaUsd/render/vp2RenderDelegate/material.cpp @@ -313,8 +313,9 @@ const std::set _mtlxTopoNodeSet = { // Conversion nodes: "convert", // Constants: they get inlined in the source. - "constant" - + "constant", + // Switch, unless all inputs are connected. + "switch" }; // Maps from a known Maya target color space name to the corresponding color correct category. diff --git a/lib/mayaUsd/ufe/CMakeLists.txt b/lib/mayaUsd/ufe/CMakeLists.txt index b0b165ef26..19a3b7d0cb 100644 --- a/lib/mayaUsd/ufe/CMakeLists.txt +++ b/lib/mayaUsd/ufe/CMakeLists.txt @@ -109,6 +109,20 @@ if (UFE_SCENE_SEGMENT_SUPPORT) ) endif() +if (v4_BatchOps IN_LIST UFE_PREVIEW_FEATURES) + message(STATUS "UFE_PREVIEW has V4 BatchOps support") + target_sources(${PROJECT_NAME} + PRIVATE + UsdBatchOpsHandler.cpp + UsdUndoDuplicateSelectionCommand.cpp + ) + + target_compile_definitions(${PROJECT_NAME} + PRIVATE + UFE_PREVIEW_BATCHOPS_SUPPORT=1 + ) +endif() + if(CMAKE_UFE_V4_FEATURES_AVAILABLE) if (${UFE_PREVIEW_VERSION_NUM} GREATER_EQUAL 4001) target_sources(${PROJECT_NAME} @@ -266,6 +280,13 @@ if (UFE_SCENE_SEGMENT_SUPPORT) ) endif() +if (v4_BatchOps IN_LIST UFE_PREVIEW_FEATURES) + list(APPEND HEADERS + UsdBatchOpsHandler.h + UsdUndoDuplicateSelectionCommand.h + ) +endif() + if(CMAKE_UFE_V4_FEATURES_AVAILABLE) if (${UFE_PREVIEW_VERSION_NUM} GREATER_EQUAL 4001) list(APPEND HEADERS diff --git a/lib/mayaUsd/ufe/Global.cpp b/lib/mayaUsd/ufe/Global.cpp index 04138d2426..9fab03669f 100644 --- a/lib/mayaUsd/ufe/Global.cpp +++ b/lib/mayaUsd/ufe/Global.cpp @@ -59,6 +59,9 @@ #if (UFE_PREVIEW_VERSION_NUM >= 4023) #include #endif +#if UFE_PREVIEW_BATCHOPS_SUPPORT +#include +#endif #if (UFE_PREVIEW_VERSION_NUM >= 4001) #include #endif @@ -201,6 +204,9 @@ MStatus initialize() #if (UFE_PREVIEW_VERSION_NUM >= 4023) handlers.uiNodeGraphNodeHandler = UsdUINodeGraphNodeHandler::create(); #endif +#if UFE_PREVIEW_BATCHOPS_SUPPORT + handlers.batchOpsHandler = UsdBatchOpsHandler::create(); +#endif #if (UFE_PREVIEW_VERSION_NUM >= 4001) handlers.nodeDefHandler = UsdShaderNodeDefHandler::create(); #endif diff --git a/lib/mayaUsd/ufe/UsdBatchOpsHandler.cpp b/lib/mayaUsd/ufe/UsdBatchOpsHandler.cpp new file mode 100644 index 0000000000..5ca710107d --- /dev/null +++ b/lib/mayaUsd/ufe/UsdBatchOpsHandler.cpp @@ -0,0 +1,48 @@ +// +// Copyright 2022 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 "UsdBatchOpsHandler.h" + +#include + +namespace MAYAUSD_NS_DEF { +namespace ufe { + +UsdBatchOpsHandler::UsdBatchOpsHandler() + : Ufe::BatchOpsHandler() +{ +} + +UsdBatchOpsHandler::~UsdBatchOpsHandler() { } + +/*static*/ +UsdBatchOpsHandler::Ptr UsdBatchOpsHandler::create() +{ + return std::make_shared(); +} + +//------------------------------------------------------------------------------ +// Ufe::BatchOpsHandler overrides +//------------------------------------------------------------------------------ + +Ufe::SelectionUndoableCommand::Ptr UsdBatchOpsHandler::duplicateSelectionCmd_( + const Ufe::Selection& selection, + const Ufe::ValueDictionary& duplicateOptions) +{ + return UsdUndoDuplicateSelectionCommand::create(selection, duplicateOptions); +} + +} // namespace ufe +} // namespace MAYAUSD_NS_DEF diff --git a/lib/mayaUsd/ufe/UsdBatchOpsHandler.h b/lib/mayaUsd/ufe/UsdBatchOpsHandler.h new file mode 100644 index 0000000000..1c2800a86b --- /dev/null +++ b/lib/mayaUsd/ufe/UsdBatchOpsHandler.h @@ -0,0 +1,58 @@ +#ifndef USDBATCHOPSHANDLER_H +#define USDBATCHOPSHANDLER_H + +// +// Copyright 2022 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 + +namespace MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Interface to create a UsdBatchOpsHandler interface object. +class MAYAUSD_CORE_PUBLIC UsdBatchOpsHandler : public Ufe::BatchOpsHandler +{ +public: + typedef std::shared_ptr Ptr; + + UsdBatchOpsHandler(); + ~UsdBatchOpsHandler() override; + + // Delete the copy/move constructors assignment operators. + UsdBatchOpsHandler(const UsdBatchOpsHandler&) = delete; + UsdBatchOpsHandler& operator=(const UsdBatchOpsHandler&) = delete; + UsdBatchOpsHandler(UsdBatchOpsHandler&&) = delete; + UsdBatchOpsHandler& operator=(UsdBatchOpsHandler&&) = delete; + + //! Create a UsdBatchOpsHandler. + static UsdBatchOpsHandler::Ptr create(); + + // Ufe::BatchOpsHandler overrides. + Ufe::SelectionUndoableCommand::Ptr duplicateSelectionCmd_( + const Ufe::Selection& selection, + const Ufe::ValueDictionary& duplicateOptions) override; +}; // UsdBatchOpsHandler + +} // namespace ufe +} // namespace MAYAUSD_NS_DEF + +#endif // USDBATCHOPSHANDLER_H diff --git a/lib/mayaUsd/ufe/UsdUndoDuplicateSelectionCommand.cpp b/lib/mayaUsd/ufe/UsdUndoDuplicateSelectionCommand.cpp new file mode 100644 index 0000000000..eb7b5886b8 --- /dev/null +++ b/lib/mayaUsd/ufe/UsdUndoDuplicateSelectionCommand.cpp @@ -0,0 +1,209 @@ +// +// Copyright 2022 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 "UsdUndoDuplicateSelectionCommand.h" + +#include "private/Utils.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace MAYAUSD_NS_DEF { +namespace ufe { + +namespace { + +bool shouldConnectExternalInputs(const Ufe::ValueDictionary& duplicateOptions) +{ + const auto itInputConnections = duplicateOptions.find("inputConnections"); + return itInputConnections != duplicateOptions.end() && itInputConnections->second.get(); +} + +} // namespace + +UsdUndoDuplicateSelectionCommand::UsdUndoDuplicateSelectionCommand( + const Ufe::Selection& selection, + const Ufe::ValueDictionary& duplicateOptions) + : Ufe::SelectionUndoableCommand() + , _copyExternalInputs(shouldConnectExternalInputs(duplicateOptions)) +{ + // TODO: MAYA-125854. If duplicating /a/b and /a/b/c, it would make sense to order the + // operations by SdfPath, and always check if the previously processed path is a prefix of the + // one currently being processed. In that case, a duplicate task is not necessary because the + // resulting SceneItem should be built by using SdfPath::ReplacePrefix on the current item to + // get its location in the previously duplicated parent item. + for (auto&& item : selection) { + if (UsdSceneItem::Ptr usdItem = std::dynamic_pointer_cast(item)) { + // Currently unordered_map since we need to streamline the targetItem override. + _perItemCommands[item->path()] = UsdUndoDuplicateCommand::create(usdItem); + } + } +} + +UsdUndoDuplicateSelectionCommand::~UsdUndoDuplicateSelectionCommand() { } + +UsdUndoDuplicateSelectionCommand::Ptr UsdUndoDuplicateSelectionCommand::create( + const Ufe::Selection& selection, + const Ufe::ValueDictionary& duplicateOptions) +{ + auto retVal = std::make_shared(selection, duplicateOptions); + if (retVal->_perItemCommands.empty()) { + // Could not find any item from this runtime in the selection. + return {}; + } + return retVal; +} + +void UsdUndoDuplicateSelectionCommand::execute() +{ + UsdUndoBlock undoBlock(&_undoableItem); + + for (auto&& duplicateItem : _perItemCommands) { + duplicateItem.second->execute(); + + auto dstSceneItem = duplicateItem.second->duplicatedItem(); + PXR_NS::UsdPrim srcPrim = ufePathToPrim(duplicateItem.first); + PXR_NS::UsdPrim dstPrim = std::dynamic_pointer_cast(dstSceneItem)->prim(); + + Ufe::Path stgPath = stagePath(dstPrim.GetStage()); + auto stageIt = _duplicatesMap.find(stgPath); + if (stageIt == _duplicatesMap.end()) { + stageIt = _duplicatesMap.insert({ stgPath, DuplicatePathsMap() }).first; + } + + // Make sure we are not tracking more than one duplicate per source. + TF_VERIFY(stageIt->second.count(srcPrim.GetPath()) == 0); + stageIt->second.insert({ srcPrim.GetPath(), dstPrim.GetPath() }); + } + + // Fixups were grouped by stage. + for (const auto& stageData : _duplicatesMap) { + PXR_NS::UsdStageWeakPtr stage(getStage(stageData.first)); + if (!stage) { + continue; + } + for (const auto& duplicatePair : stageData.second) { + // Cleanup relationships and connections on the duplicate. + for (auto p : UsdPrimRange(stage->GetPrimAtPath(duplicatePair.second))) { + for (auto& prop : p.GetProperties()) { + if (prop.Is()) { + PXR_NS::UsdAttribute attr = prop.As(); + PXR_NS::SdfPathVector sources; + attr.GetConnections(&sources); + if (updateSdfPathVector( + sources, duplicatePair, stageData.second, _copyExternalInputs)) { + if (sources.empty()) { + attr.ClearConnections(); + if (!attr.HasValue()) { + p.RemoveProperty(prop.GetName()); + } + } else { + attr.SetConnections(sources); + } + } + } else if (prop.Is()) { + PXR_NS::UsdRelationship rel = prop.As(); + PXR_NS::SdfPathVector targets; + rel.GetTargets(&targets); + // Currently always copying external relationships is the right move since + // duplicated geometries will keep their currently assigned material. We + // might need a case by case basis later as we deal with more complex + // relationships. + if (updateSdfPathVector(targets, duplicatePair, stageData.second, true)) { + if (targets.empty()) { + rel.ClearTargets(true); + } else { + rel.SetTargets(targets); + } + } + } + } + } + } + } +} + +Ufe::SceneItem::Ptr UsdUndoDuplicateSelectionCommand::targetItem(const Ufe::Path& sourcePath) const +{ + CommandMap::const_iterator it = _perItemCommands.find(sourcePath); + if (it == _perItemCommands.cend()) { + return {}; + } + return it->second->duplicatedItem(); +} + +bool UsdUndoDuplicateSelectionCommand::updateSdfPathVector( + PXR_NS::SdfPathVector& pathVec, + const DuplicatePathsMap::value_type& duplicatePair, + const DuplicatePathsMap& otherPairs, + const bool keepExternal) +{ + bool hasChanged = false; + std::list indicesToRemove; + for (size_t i = 0; i < pathVec.size(); ++i) { + const PXR_NS::SdfPath& path = pathVec[i]; + PXR_NS::SdfPath finalPath = path; + // Paths are lexicographically ordered, this means we can search quickly for bounds of + // candidate paths. + auto itPath = otherPairs.lower_bound(finalPath); + if (itPath != otherPairs.begin()) { + --itPath; + } + const auto endPath = otherPairs.upper_bound(finalPath); + bool isExternalPath = true; + for (; itPath != endPath; ++itPath) { + if (*itPath == duplicatePair) { + // That one was correctly processed by USD when duplicating. + isExternalPath + = !finalPath.HasPrefix(itPath->first) && !finalPath.HasPrefix(itPath->second); + continue; + } + finalPath = finalPath.ReplacePrefix(itPath->first, itPath->second); + if (path != finalPath) { + pathVec[i] = finalPath; + hasChanged = true; + isExternalPath = false; + break; + } + } + if (!keepExternal && isExternalPath) { + hasChanged = true; + indicesToRemove.push_front(i); + } + } + for (size_t toRemove : indicesToRemove) { + pathVec.erase(pathVec.cbegin() + toRemove); + } + return hasChanged; +} + +void UsdUndoDuplicateSelectionCommand::undo() { _undoableItem.undo(); } + +void UsdUndoDuplicateSelectionCommand::redo() { _undoableItem.redo(); } + +} // namespace ufe +} // namespace MAYAUSD_NS_DEF diff --git a/lib/mayaUsd/ufe/UsdUndoDuplicateSelectionCommand.h b/lib/mayaUsd/ufe/UsdUndoDuplicateSelectionCommand.h new file mode 100644 index 0000000000..788706cf9e --- /dev/null +++ b/lib/mayaUsd/ufe/UsdUndoDuplicateSelectionCommand.h @@ -0,0 +1,83 @@ +// +// Copyright 2022 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_USDUNDODUPLICATESELECTIONCOMMAND_H +#define MAYAUSD_UFE_USDUNDODUPLICATESELECTIONCOMMAND_H + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief UsdUndoDuplicateSelectionCommand +class MAYAUSD_CORE_PUBLIC UsdUndoDuplicateSelectionCommand : public Ufe::SelectionUndoableCommand +{ +public: + typedef std::shared_ptr Ptr; + + UsdUndoDuplicateSelectionCommand( + const Ufe::Selection& selection, + const Ufe::ValueDictionary& duplicateOptions); + ~UsdUndoDuplicateSelectionCommand() override; + + // Delete the copy/move constructors assignment operators. + UsdUndoDuplicateSelectionCommand(const UsdUndoDuplicateSelectionCommand&) = delete; + UsdUndoDuplicateSelectionCommand& operator=(const UsdUndoDuplicateSelectionCommand&) = delete; + UsdUndoDuplicateSelectionCommand(UsdUndoDuplicateSelectionCommand&&) = delete; + UsdUndoDuplicateSelectionCommand& operator=(UsdUndoDuplicateSelectionCommand&&) = delete; + + //! Create a UsdUndoDuplicateSelectionCommand from a USD prim and UFE path. + static Ptr + create(const Ufe::Selection& selection, const Ufe::ValueDictionary& duplicateOptions); + + void execute() override; + void undo() override; + void redo() override; + + Ufe::SceneItem::Ptr targetItem(const Ufe::Path& sourcePath) const override; + +private: + UsdUndoableItem _undoableItem; + const bool _copyExternalInputs; + + using CommandMap = std::unordered_map; + CommandMap _perItemCommands; + + // Fixup data: + using DuplicatePathsMap = std::map; + using DuplicatesMap = std::unordered_map; + DuplicatesMap _duplicatesMap; + + bool updateSdfPathVector( + PXR_NS::SdfPathVector& pathVec, + const DuplicatePathsMap::value_type& duplicatePair, + const DuplicatePathsMap& otherPairs, + const bool keepExternal); +}; // UsdUndoDuplicateSelectionCommand + +} // namespace ufe +} // namespace MAYAUSD_NS_DEF + +#endif diff --git a/test/lib/ufe/CMakeLists.txt b/test/lib/ufe/CMakeLists.txt index 5a8d400bfd..094ed2d049 100644 --- a/test/lib/ufe/CMakeLists.txt +++ b/test/lib/ufe/CMakeLists.txt @@ -61,6 +61,12 @@ if (UFE_SCENE_SEGMENT_SUPPORT) ) endif() +if (v4_BatchOps IN_LIST UFE_PREVIEW_FEATURES) + list(APPEND TEST_SCRIPT_FILES + testBatchOpsHandler.py + ) +endif() + if(CMAKE_UFE_V4_FEATURES_AVAILABLE) if (${UFE_PREVIEW_VERSION_NUM} GREATER_EQUAL 4020) list(APPEND TEST_SCRIPT_FILES diff --git a/test/lib/ufe/testBatchOpsHandler.py b/test/lib/ufe/testBatchOpsHandler.py new file mode 100644 index 0000000000..253aebcd19 --- /dev/null +++ b/test/lib/ufe/testBatchOpsHandler.py @@ -0,0 +1,368 @@ +#!/usr/bin/env python + +# +# Copyright 2022 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 mayaUtils +import ufeUtils +import usdUtils +import testUtils + +from maya import cmds + +from pxr import UsdShade, Sdf + +import os +import ufe +import unittest + +class BatchOpsHandlerTestCase(unittest.TestCase): + '''Test batch operations.''' + + pluginsLoaded = False + + @classmethod + def setUpClass(cls): + if not cls.pluginsLoaded: + cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded() + + def setUp(self): + self.assertTrue(self.pluginsLoaded) + + cmds.file(new=True, force=True) + + def testUfeDuplicateRelationships(self): + '''Test that a batched duplication using Ufe API allows relationship fixups.''' + + # Load a scene. + testFile = testUtils.getTestScene('MaterialX', 'BatchOpsTestScene.usda') + shapeNode,shapeStage = mayaUtils.createProxyFromFile(testFile) + + geomItem = ufeUtils.createUfeSceneItem(shapeNode, '/pPlane1') + self.assertIsNotNone(geomItem) + matItem = ufeUtils.createUfeSceneItem(shapeNode, '/mtl/ss3SG') + self.assertIsNotNone(matItem) + + batchOpsHandler = ufe.RunTimeMgr.instance().batchOpsHandler(matItem.runTimeId()) + self.assertIsNotNone(batchOpsHandler) + + # Duplicating without batching means the new plane will not see the new material: + dGeom = ufe.SceneItemOps.sceneItemOps(geomItem).duplicateItemCmd() + dMat = ufe.SceneItemOps.sceneItemOps(matItem).duplicateItemCmd() + + dGeomPrim = usdUtils.getPrimFromSceneItem(dGeom.item) + dGeomBindAPI = UsdShade.MaterialBindingAPI(dGeomPrim) + # Points to original ss3SG, we would like ss3SG1 + self.assertEqual(dGeomBindAPI.GetDirectBinding().GetMaterialPath(), Sdf.Path("/mtl/ss3SG")) + + dMat.undoableCommand.undo() + dGeom.undoableCommand.undo() + + sel = ufe.Selection() + sel.append(geomItem) + sel.append(matItem) + + cmd = batchOpsHandler.duplicateSelectionCmd(sel, {"inputConnections": True}) + cmd.execute() + + dGeomPrim = usdUtils.getPrimFromSceneItem(cmd.targetItem(geomItem.path())) + dGeomBindAPI = UsdShade.MaterialBindingAPI(dGeomPrim) + # Now seeing and using ss3SG1 + self.assertEqual(dGeomBindAPI.GetDirectBinding().GetMaterialPath(), Sdf.Path("/mtl/ss3SG1")) + + cmd.undo() + cmd.redo() + + dGeomPrim = usdUtils.getPrimFromSceneItem(cmd.targetItem(geomItem.path())) + dGeomBindAPI = UsdShade.MaterialBindingAPI(dGeomPrim) + # Now seeing and using ss3SG1 + self.assertEqual(dGeomBindAPI.GetDirectBinding().GetMaterialPath(), Sdf.Path("/mtl/ss3SG1")) + + cmd.undo() + + # And currently, as per spec, we are preserving external relationships even with + # inputConnections set to false. In Maya the shading group connections are + # always copied. Not sure this will work with other relationships. We will + # assess when we start seeing exclusive relationships. + sel = ufe.Selection() + sel.append(geomItem) + cmd = batchOpsHandler.duplicateSelectionCmd(sel, {"inputConnections": False}) + cmd.execute() + + dGeomPrim = usdUtils.getPrimFromSceneItem(cmd.targetItem(geomItem.path())) + dGeomBindAPI = UsdShade.MaterialBindingAPI(dGeomPrim) + # Still seeing and using the original material + self.assertEqual(dGeomBindAPI.GetDirectBinding().GetMaterialPath(), Sdf.Path("/mtl/ss3SG")) + + cmd.undo() + cmd.redo() + + dGeomPrim = usdUtils.getPrimFromSceneItem(cmd.targetItem(geomItem.path())) + dGeomBindAPI = UsdShade.MaterialBindingAPI(dGeomPrim) + # Still seeing and using the original material + self.assertEqual(dGeomBindAPI.GetDirectBinding().GetMaterialPath(), Sdf.Path("/mtl/ss3SG")) + + def testUfeDuplicateConnections(self): + '''Test that a batched duplication using Ufe API allows connection fixups.''' + + # Load a scene. + testFile = testUtils.getTestScene('MaterialX', 'BatchOpsTestScene.usda') + shapeNode,shapeStage = mayaUtils.createProxyFromFile(testFile) + + f3Item = ufeUtils.createUfeSceneItem(shapeNode, '/mtl/ss3SG/MayaNG_ss3SG/file3') + self.assertIsNotNone(f3Item) + f3tItem = ufeUtils.createUfeSceneItem(shapeNode, '/mtl/ss3SG/MayaNG_ss3SG/file3_MayafileTexture') + self.assertIsNotNone(f3tItem) + f3pItem = ufeUtils.createUfeSceneItem(shapeNode, '/mtl/ss3SG/MayaNG_ss3SG/place2dTexture3') + self.assertIsNotNone(f3pItem) + + connectionHandler = ufe.RunTimeMgr.instance().connectionHandler(f3Item.runTimeId()) + self.assertIsNotNone(connectionHandler) + batchOpsHandler = ufe.RunTimeMgr.instance().batchOpsHandler(f3Item.runTimeId()) + self.assertIsNotNone(batchOpsHandler) + + # Duplicating without batching: the duplicated nodes will not see each other. + dF3 = ufe.SceneItemOps.sceneItemOps(f3Item).duplicateItemCmd() + dF3t = ufe.SceneItemOps.sceneItemOps(f3tItem).duplicateItemCmd() + dF3p = ufe.SceneItemOps.sceneItemOps(f3pItem).duplicateItemCmd() + + connections = connectionHandler.sourceConnections(dF3t.item) + self.assertIsNotNone(connections) + conn = set(["{}.{} -> {}.{}".format(i.src.path, i.src.name, i.dst.path, i.dst.name) for i in connections.allConnections()]) + wrongButExpected = { + # Connections are pointing to the original file3 and place2dTexture3 nodes. We want them + # to link with the new nodes instead + '|world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/file3.outputs:out -> |world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/file3_MayafileTexture1.inputs:inColor', + '|world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/place2dTexture3.outputs:out -> |world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/file3_MayafileTexture1.inputs:uvCoord' + } + self.assertEqual(conn, wrongButExpected) + + dF3p.undoableCommand.undo() + dF3t.undoableCommand.undo() + dF3.undoableCommand.undo() + + sel = ufe.Selection() + sel.append(f3Item) + sel.append(f3tItem) + sel.append(f3pItem) + + cmd = batchOpsHandler.duplicateSelectionCmd(sel, {"inputConnections": True}) + cmd.execute() + + expectedF3t = { + # Connections are pointing to the new file4 and place2dTexture4 nodes. + '|world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/file4.outputs:out -> |world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/file3_MayafileTexture1.inputs:inColor', + '|world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/place2dTexture4.outputs:out -> |world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/file3_MayafileTexture1.inputs:uvCoord' + } + + def checkConnections1(self, cmd, f3Item, f3tItem, f3pItem, expectedF3t): + connections = connectionHandler.sourceConnections(cmd.targetItem(f3tItem.path())) + self.assertIsNotNone(connections) + conn = set(["{}.{} -> {}.{}".format(i.src.path, i.src.name, i.dst.path, i.dst.name) for i in connections.allConnections()]) + self.assertEqual(conn, expectedF3t) + self.assertEqual(str(cmd.targetItem(f3Item.path()).path()), '|world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/file4') + self.assertEqual(str(cmd.targetItem(f3pItem.path()).path()), '|world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/place2dTexture4') + + connections = connectionHandler.sourceConnections(cmd.targetItem(f3pItem.path())) + self.assertIsNotNone(connections) + conn = set(["{}.{} -> {}.{}".format(i.src.path, i.src.name, i.dst.path, i.dst.name) for i in connections.allConnections()]) + expectedF3p = { + # Connection pointing to the original node graph. + '|world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG.inputs:file3:varname -> |world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/place2dTexture4.inputs:geomprop', + } + self.assertEqual(conn, expectedF3p) + + checkConnections1(self, cmd, f3Item, f3tItem, f3pItem, expectedF3t) + + cmd.undo() + cmd.redo() + + checkConnections1(self, cmd, f3Item, f3tItem, f3pItem, expectedF3t) + + cmd.undo() + + # Last test, but with options that will also drop connections to SceneItems + # outside of the duplicated clique. + cmd = batchOpsHandler.duplicateSelectionCmd(sel, {"inputConnections": False}) + + cmd.execute() + + def checkConnections2(self, cmd, f3tItem, f3pItem, expectedF3t): + connections = connectionHandler.sourceConnections(cmd.targetItem(f3tItem.path())) + self.assertIsNotNone(connections) + conn = set(["{}.{} -> {}.{}".format(i.src.path, i.src.name, i.dst.path, i.dst.name) for i in connections.allConnections()]) + self.assertEqual(conn, expectedF3t) + + # The connection between the new place2dTexture and the original NodeGraph will be gone. + connections = connectionHandler.sourceConnections(cmd.targetItem(f3pItem.path())) + self.assertIsNotNone(connections) + self.assertEqual(len(connections.allConnections()), 0) + + checkConnections2(self, cmd, f3tItem, f3pItem, expectedF3t) + + cmd.undo() + cmd.redo() + + checkConnections2(self, cmd, f3tItem, f3pItem, expectedF3t) + + def testUfeDuplicateRelationshipsMaya(self): + '''Test that Maya uses relationship fixups.''' + + # Load a scene. + testFile = testUtils.getTestScene('MaterialX', 'BatchOpsTestScene.usda') + shapeNode,shapeStage = mayaUtils.createProxyFromFile(testFile) + + geomItem = ufeUtils.createUfeSceneItem(shapeNode, '/pPlane1') + self.assertIsNotNone(geomItem) + matItem = ufeUtils.createUfeSceneItem(shapeNode, '/mtl/ss3SG') + self.assertIsNotNone(matItem) + + ufe.GlobalSelection.get().clear() + ufe.GlobalSelection.get().append(geomItem) + ufe.GlobalSelection.get().append(matItem) + cmds.duplicate(inputConnections = True) + + dGeomPrim = usdUtils.getPrimFromSceneItem(ufe.GlobalSelection.get().front()) + dGeomBindAPI = UsdShade.MaterialBindingAPI(dGeomPrim) + # Now seeing and using ss3SG1 + self.assertEqual(dGeomBindAPI.GetDirectBinding().GetMaterialPath(), Sdf.Path("/mtl/ss3SG1")) + + cmds.undo() + cmds.redo() + + dGeomPrim = usdUtils.getPrimFromSceneItem(ufe.GlobalSelection.get().front()) + dGeomBindAPI = UsdShade.MaterialBindingAPI(dGeomPrim) + # Now seeing and using ss3SG1 + self.assertEqual(dGeomBindAPI.GetDirectBinding().GetMaterialPath(), Sdf.Path("/mtl/ss3SG1")) + + def testUfeDuplicateConnectionsMaya(self): + '''Test that a duplication using Maya does connection fixups.''' + + # Load a scene. + testFile = testUtils.getTestScene('MaterialX', 'BatchOpsTestScene.usda') + shapeNode,shapeStage = mayaUtils.createProxyFromFile(testFile) + + f3Item = ufeUtils.createUfeSceneItem(shapeNode, '/mtl/ss3SG/MayaNG_ss3SG/file3') + self.assertIsNotNone(f3Item) + f3tItem = ufeUtils.createUfeSceneItem(shapeNode, '/mtl/ss3SG/MayaNG_ss3SG/file3_MayafileTexture') + self.assertIsNotNone(f3tItem) + f3pItem = ufeUtils.createUfeSceneItem(shapeNode, '/mtl/ss3SG/MayaNG_ss3SG/place2dTexture3') + self.assertIsNotNone(f3pItem) + + connectionHandler = ufe.RunTimeMgr.instance().connectionHandler(f3Item.runTimeId()) + self.assertIsNotNone(connectionHandler) + + ufe.GlobalSelection.get().clear() + ufe.GlobalSelection.get().append(f3tItem) + ufe.GlobalSelection.get().append(f3Item) + ufe.GlobalSelection.get().append(f3pItem) + cmds.duplicate(inputConnections = True) + + expectedF3t = { + # Connections are pointing to the new file4 and place2dTexture4 nodes. + '|world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/file4.outputs:out -> |world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/file3_MayafileTexture1.inputs:inColor', + '|world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/place2dTexture4.outputs:out -> |world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/file3_MayafileTexture1.inputs:uvCoord' + } + + def checkState1(self, expectedF3t): + f3tDupItem, f3DupItem, f3pDupItem = tuple(ufe.GlobalSelection.get()) + connections = connectionHandler.sourceConnections(f3tDupItem) + self.assertIsNotNone(connections) + conn = set(["{}.{} -> {}.{}".format(i.src.path, i.src.name, i.dst.path, i.dst.name) for i in connections.allConnections()]) + self.assertEqual(conn, expectedF3t) + self.assertEqual(str(f3DupItem.path()), '|world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/file4') + self.assertEqual(str(f3pDupItem.path()), '|world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/place2dTexture4') + + connections = connectionHandler.sourceConnections(f3pDupItem) + self.assertIsNotNone(connections) + conn = set(["{}.{} -> {}.{}".format(i.src.path, i.src.name, i.dst.path, i.dst.name) for i in connections.allConnections()]) + expectedF3p = { + # Connection pointing to the original node graph. + '|world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG.inputs:file3:varname -> |world|stage|stageShape/mtl/ss3SG/MayaNG_ss3SG/place2dTexture4.inputs:geomprop', + } + self.assertEqual(conn, expectedF3p) + + checkState1(self, expectedF3t) + + cmds.undo() + cmds.redo() + + checkState1(self, expectedF3t) + + # Re-test, but using the default duplicate, that should not copy connections + # to original SceneItems. + cmds.undo() + cmds.duplicate() + + def checkState2(self, expectedF3t): + f3tDupItem, _, f3pDupItem = tuple(ufe.GlobalSelection.get()) + connections = connectionHandler.sourceConnections(f3tDupItem) + self.assertIsNotNone(connections) + conn = set(["{}.{} -> {}.{}".format(i.src.path, i.src.name, i.dst.path, i.dst.name) for i in connections.allConnections()]) + self.assertEqual(conn, expectedF3t) + + # The connection between the new place2dTexture and the original NodeGraph will be gone. + connections = connectionHandler.sourceConnections(f3pDupItem) + self.assertIsNotNone(connections) + self.assertEqual(len(connections.allConnections()), 0) + + checkState2(self, expectedF3t) + + cmds.undo() + cmds.redo() + + checkState2(self, expectedF3t) + + def testMeshWithEncapsulatedMaterial(self): + """Duplicate is a complex operation. Test an issue that was found out while debugging: + Connections on shaders deep in the hierarchy were losing their connections.""" + # Load a scene. + testFile = testUtils.getTestScene('MaterialX', 'BatchOpsTestScene.usda') + shapeNode,shapeStage = mayaUtils.createProxyFromFile(testFile) + + geomItem = ufeUtils.createUfeSceneItem(shapeNode, '/pPlane2') + self.assertIsNotNone(geomItem) + + batchOpsHandler = ufe.RunTimeMgr.instance().batchOpsHandler(geomItem.runTimeId()) + self.assertIsNotNone(batchOpsHandler) + + sel = ufe.Selection() + sel.append(geomItem) + + cmd = batchOpsHandler.duplicateSelectionCmd(sel, {"inputConnections": False}) + cmd.execute() + + def checkStatus(self, cmd, feomItem): + stage = usdUtils.getPrimFromSceneItem(geomItem).GetStage() + ss2 = UsdShade.Shader(stage.GetPrimAtPath(Sdf.Path("/pPlane7/mtl/ss2SG/ss2"))) + self.assertTrue(ss2.GetPrim().IsValid()) + base_color = ss2.GetInput("base_color") + self.assertEqual(base_color.GetAttr().GetPath(), Sdf.Path("/pPlane7/mtl/ss2SG/ss2.inputs:base_color")) + self.assertTrue(base_color.HasConnectedSource()) + connection = base_color.GetConnectedSources()[0][0] + self.assertEqual(connection.sourceName, "baseColor") + self.assertEqual(connection.source.GetPath(), Sdf.Path("/pPlane7/mtl/ss2SG/MayaNG_ss2SG")) + + checkStatus(self, cmd, geomItem) + + cmd.undo() + cmd.redo() + + checkStatus(self, cmd, geomItem) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/test/testSamples/MaterialX/BatchOpsTestScene.usda b/test/testSamples/MaterialX/BatchOpsTestScene.usda new file mode 100644 index 0000000000..f9a786b518 --- /dev/null +++ b/test/testSamples/MaterialX/BatchOpsTestScene.usda @@ -0,0 +1,385 @@ +#usda 1.0 +( + defaultPrim = "pPlane1" + metersPerUnit = 0.01 + upAxis = "Y" +) + +def Mesh "pPlane1" ( + prepend apiSchemas = ["MaterialBindingAPI"] + kind = "component" +) +{ + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, 0, -0.5), (0.5, 0, 0.5)] + int[] faceVertexCounts = [4] + int[] faceVertexIndices = [0, 1, 3, 2] + rel material:binding = + normal3f[] normals = [(0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0)] ( + interpolation = "faceVarying" + ) + point3f[] points = [(-0.5, 0, 0.5), (0.5, 0, 0.5), (-0.5, 0, -0.5), (0.5, 0, -0.5)] + color3f[] primvars:displayColor = [(0, 0, 0)] ( + customData = { + dictionary Maya = { + bool generated = 1 + } + } + ) + texCoord2f[] primvars:st = [(0, 0), (1, 0), (0, 1), (1, 1)] ( + customData = { + dictionary Maya = { + token name = "map1" + } + } + interpolation = "faceVarying" + ) + int[] primvars:st:indices = [0, 1, 3, 2] + uniform token subdivisionScheme = "none" +} + +def Mesh "pPlane2" ( + prepend apiSchemas = ["MaterialBindingAPI"] + kind = "component" +) +{ + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, 0, -0.5), (0.5, 0, 0.5)] + int[] faceVertexCounts = [4] + int[] faceVertexIndices = [0, 1, 3, 2] + rel material:binding = + normal3f[] normals = [(0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0)] ( + interpolation = "faceVarying" + ) + point3f[] points = [(-0.5, 0, 0.5), (0.5, 0, 0.5), (-0.5, 0, -0.5), (0.5, 0, -0.5)] + color3f[] primvars:displayColor = [(0, 0, 0)] ( + customData = { + dictionary Maya = { + bool generated = 1 + } + } + ) + texCoord2f[] primvars:st = [(0, 0), (1, 0), (0, 1), (1, 1)] ( + customData = { + dictionary Maya = { + token name = "map1" + } + } + interpolation = "faceVarying" + ) + int[] primvars:st:indices = [0, 1, 3, 2] + uniform token subdivisionScheme = "none" + double3 xformOp:translate = (-1.5, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Scope "mtl" { + def Material "ss2SG" ( + kind = "assembly" + ) + { + string inputs:file2:varname = "st" + token outputs:mtlx:surface.connect = + + def Shader "ss2" + { + uniform token info:id = "ND_standard_surface_surfaceshader" + float inputs:base = 1 + color3f inputs:base_color.connect = + float inputs:specular = 1 + float inputs:specular_roughness = 0.2 + token outputs:out + } + + def NodeGraph "MayaNG_ss2SG" + { + string inputs:file2:varname.connect = + color3f outputs:baseColor.connect = + + def Shader "file2" + { + uniform token info:id = "ND_image_color4" + asset inputs:file = @textures/normalSpiralA.png@ + string inputs:filtertype = "cubic" + float2 inputs:texcoord.connect = + string inputs:uaddressmode = "periodic" + string inputs:vaddressmode = "periodic" + color4f outputs:out + } + + def Shader "file2_MayafileTexture" + { + uniform token info:id = "MayaND_fileTexture_color4" + color4f inputs:defaultColor = (0.5, 0.5, 0.5, 1) + color4f inputs:inColor.connect = + color4f inputs:uvCoord.connect = + color4f outputs:outColor + } + + def Shader "MayaConvert_file2_MayafileTexture" + { + uniform token info:id = "ND_convert_color4_color3" + color4f inputs:in.connect = + color3f outputs:out + } + + def Shader "place2dTexture2" + { + uniform token info:id = "ND_geompropvalue_vector2" + string inputs:geomprop.connect = + float2 outputs:out + } + } + } + } +} + +def Mesh "pPlane3" ( + prepend apiSchemas = ["MaterialBindingAPI"] + kind = "component" +) +{ + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, 0, -0.5), (0.5, 0, 0.5)] + int[] faceVertexCounts = [4] + int[] faceVertexIndices = [0, 1, 3, 2] + rel material:binding = + normal3f[] normals = [(0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0)] ( + interpolation = "faceVarying" + ) + point3f[] points = [(-0.5, 0, 0.5), (0.5, 0, 0.5), (-0.5, 0, -0.5), (0.5, 0, -0.5)] + color3f[] primvars:displayColor = [(0, 0, 0)] ( + customData = { + dictionary Maya = { + bool generated = 1 + } + } + ) + texCoord2f[] primvars:st = [(0, 0), (1, 0), (0, 1), (1, 1)] ( + customData = { + dictionary Maya = { + token name = "map1" + } + } + interpolation = "faceVarying" + ) + int[] primvars:st:indices = [0, 1, 3, 2] + uniform token subdivisionScheme = "none" + double3 xformOp:translate = (1.5, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] +} + +def Mesh "pPlane4" ( + prepend apiSchemas = ["MaterialBindingAPI"] + kind = "component" +) +{ + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, 0, -0.5), (0.5, 0, 0.5)] + int[] faceVertexCounts = [4] + int[] faceVertexIndices = [0, 1, 3, 2] + rel material:binding = + normal3f[] normals = [(0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0)] ( + interpolation = "faceVarying" + ) + point3f[] points = [(-0.5, 0, 0.5), (0.5, 0, 0.5), (-0.5, 0, -0.5), (0.5, 0, -0.5)] + color3f[] primvars:displayColor = [(0, 0, 0)] ( + customData = { + dictionary Maya = { + bool generated = 1 + } + } + ) + texCoord2f[] primvars:st = [(0, 0), (1, 0), (0, 1), (1, 1)] ( + customData = { + dictionary Maya = { + token name = "map1" + } + } + interpolation = "faceVarying" + ) + int[] primvars:st:indices = [0, 1, 3, 2] + uniform token subdivisionScheme = "none" + double3 xformOp:translate = (-1.5, 0, 1.5) + uniform token[] xformOpOrder = ["xformOp:translate"] +} + +def Mesh "pPlane5" ( + prepend apiSchemas = ["MaterialBindingAPI"] + kind = "component" +) +{ + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, 0, -0.5), (0.5, 0, 0.5)] + int[] faceVertexCounts = [4] + int[] faceVertexIndices = [0, 1, 3, 2] + rel material:binding = + normal3f[] normals = [(0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0)] ( + interpolation = "faceVarying" + ) + point3f[] points = [(-0.5, 0, 0.5), (0.5, 0, 0.5), (-0.5, 0, -0.5), (0.5, 0, -0.5)] + color3f[] primvars:displayColor = [(0, 0, 0)] ( + customData = { + dictionary Maya = { + bool generated = 1 + } + } + ) + texCoord2f[] primvars:st = [(0, 0), (1, 0), (0, 1), (1, 1)] ( + customData = { + dictionary Maya = { + token name = "map1" + } + } + interpolation = "faceVarying" + ) + int[] primvars:st:indices = [0, 1, 3, 2] + uniform token subdivisionScheme = "none" + double3 xformOp:translate = (0, 0, 1.5) + uniform token[] xformOpOrder = ["xformOp:translate"] +} + +def Mesh "pPlane6" ( + prepend apiSchemas = ["MaterialBindingAPI"] + kind = "component" +) +{ + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, 0, -0.5), (0.5, 0, 0.5)] + int[] faceVertexCounts = [4] + int[] faceVertexIndices = [0, 1, 3, 2] + rel material:binding = + normal3f[] normals = [(0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0)] ( + interpolation = "faceVarying" + ) + point3f[] points = [(-0.5, 0, 0.5), (0.5, 0, 0.5), (-0.5, 0, -0.5), (0.5, 0, -0.5)] + color3f[] primvars:displayColor = [(0, 0, 0)] ( + customData = { + dictionary Maya = { + bool generated = 1 + } + } + ) + texCoord2f[] primvars:st = [(0, 0), (1, 0), (0, 1), (1, 1)] ( + customData = { + dictionary Maya = { + token name = "map1" + } + } + interpolation = "faceVarying" + ) + int[] primvars:st:indices = [0, 1, 3, 2] + uniform token subdivisionScheme = "none" + double3 xformOp:translate = (1.5, 0, 1.5) + uniform token[] xformOpOrder = ["xformOp:translate"] +} + +def Scope "mtl" { + def Material "ss3SG" ( + kind = "assembly" + ) + { + string inputs:file3:varname = "st" + token outputs:mtlx:surface.connect = + + def Shader "ss3" + { + uniform token info:id = "ND_standard_surface_surfaceshader" + float inputs:base = 1 + color3f inputs:base_color.connect = + float inputs:specular = 1 + float inputs:specular_roughness = 0.2 + token outputs:out + } + + def NodeGraph "MayaNG_ss3SG" + { + string inputs:file3:varname.connect = + color3f outputs:baseColor.connect = + + def Shader "file3" + { + uniform token info:id = "ND_image_color3" + asset inputs:file = @textures/Brazilian_rosewood_pxr128.png@ + string inputs:filtertype = "cubic" + float2 inputs:texcoord.connect = + string inputs:uaddressmode = "periodic" + string inputs:vaddressmode = "periodic" + color3f outputs:out + } + + def Shader "file3_MayafileTexture" + { + uniform token info:id = "MayaND_fileTexture_color3" + color3f inputs:defaultColor = (0.5, 0.5, 0.5) + color3f inputs:inColor.connect = + color3f inputs:uvCoord.connect = + color3f outputs:outColor + } + + def Shader "place2dTexture3" + { + uniform token info:id = "ND_geompropvalue_vector2" + string inputs:geomprop.connect = + float2 outputs:out + } + } + } + + def Material "ss4SG" ( + kind = "assembly" + ) + { + string inputs:file4:varname = "st" + token outputs:mtlx:surface.connect = + + def Shader "ss4" + { + uniform token info:id = "ND_standard_surface_surfaceshader" + float inputs:base = 1 + color3f inputs:base_color.connect = + float inputs:specular = 1 + float inputs:specular_roughness = 0.2 + token outputs:out + } + + def NodeGraph "MayaNG_ss4SG" + { + string inputs:file4:varname.connect = + color3f outputs:baseColor.connect = + + def Shader "file4" + { + uniform token info:id = "ND_image_color4" + asset inputs:file = @textures/grid.png@ + string inputs:filtertype = "cubic" + float2 inputs:texcoord.connect = + string inputs:uaddressmode = "periodic" + string inputs:vaddressmode = "periodic" + color4f outputs:out + } + + def Shader "file4_MayafileTexture" + { + uniform token info:id = "MayaND_fileTexture_color4" + color4f inputs:defaultColor = (0.5, 0.5, 0.5, 1) + color4f inputs:inColor.connect = + color4f inputs:uvCoord.connect = + color4f outputs:outColor + } + + def Shader "MayaConvert_file4_MayafileTexture" + { + uniform token info:id = "ND_convert_color4_color3" + color4f inputs:in.connect = + color3f outputs:out + } + + def Shader "place2dTexture4" + { + uniform token info:id = "ND_geompropvalue_vector2" + string inputs:geomprop.connect = + float2 outputs:out + } + } + } +}