From c90379b5bfe195eb95d088c33a53865b3b244540 Mon Sep 17 00:00:00 2001 From: debloip-adsk <145056365+debloip-adsk@users.noreply.github.com> Date: Tue, 27 Aug 2024 11:22:14 -0400 Subject: [PATCH 1/4] HYDRA-1160 : Fix crash when traversing prototype prims without prototype roots for highlighting (#159) * HYDRA-1160 : Handle optional case in instancing traversal * HYDRA-1160 : Add test * HYDRA-1160 : Remove extra whitespace --- ...pWireframeSelectionHighlightSceneIndex.cpp | 10 +++++--- .../cpp/testDataProducerExample.py | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/flowViewport/sceneIndex/fvpWireframeSelectionHighlightSceneIndex.cpp b/lib/flowViewport/sceneIndex/fvpWireframeSelectionHighlightSceneIndex.cpp index 7e664f7fe1..2b9408feaf 100644 --- a/lib/flowViewport/sceneIndex/fvpWireframeSelectionHighlightSceneIndex.cpp +++ b/lib/flowViewport/sceneIndex/fvpWireframeSelectionHighlightSceneIndex.cpp @@ -104,9 +104,13 @@ SdfPathVector _GetInstancingRelatedPaths(const HdSceneIndexPrim& prim, Fvp::Sele instancingRelatedPaths.push_back(instancerPath); } - auto protoRootPaths = instancedBy.GetPrototypeRoots()->GetTypedValue(0); - for (const auto& protoRootPath : protoRootPaths) { - instancingRelatedPaths.push_back(protoRootPath); + // Having a prototype root is not a hard requirement (a single prim being instanced + // does not need to specify itself as its own prototype root). + if (instancedBy.GetPrototypeRoots()) { + auto protoRootPaths = instancedBy.GetPrototypeRoots()->GetTypedValue(0); + for (const auto& protoRootPath : protoRootPaths) { + instancingRelatedPaths.push_back(protoRootPath); + } } } diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testDataProducerExample.py b/test/lib/mayaUsd/render/mayaToHydra/cpp/testDataProducerExample.py index 89cd192009..6a0aad63ea 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/testDataProducerExample.py +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testDataProducerExample.py @@ -58,6 +58,9 @@ def cube000PathString(self): def cube222PathString(self): return '|transform1|' + self._locator + ',/cube_2_2_2' + def cubePrototypePathString(self): + return '|transform1|' + self._locator + ',/cube_' + def test_Pick(self): # Pick an exterior cube to ensure we don't pick a hidden one. cmds.mayaHydraCppTest(self.cube222PathString(), f='TestUsdPicking.pick') @@ -132,5 +135,26 @@ def assertTranslationAlmostEqual(expected): cmds.redo() assertTranslationAlmostEqual([3, 4, 5]) + def test_SelectPrototype(self): + # Enable instancing + cmds.setAttr(self._locator + '.cubesUseInstancing', True) + + # Clear selection + sn = ufe.GlobalSelection.get() + sn.clear() + + # Empty Maya selection, therefore no fully selected path in the scene + # index. + cmds.mayaHydraCppTest(f='TestSelection.fullySelectedPaths') + + # Add cube to selection + item = ufe.Hierarchy.createItem(ufe.PathString.path(self.cubePrototypePathString())) + sn.append(item) + + # Item added to the Maya selection, it should be fully selected in the + # scene index. + cmds.mayaHydraCppTest( + self.cubePrototypePathString(), f='TestSelection.fullySelectedPaths') + if __name__ == '__main__': fixturesUtils.runTests(globals()) From f496ea99bfb693026550029331c9d8b76cf017c9 Mon Sep 17 00:00:00 2001 From: roopavr-adsk <134624277+roopavr-adsk@users.noreply.github.com> Date: Fri, 30 Aug 2024 09:16:40 -0400 Subject: [PATCH 2/4] HYDRA-951 : Add Maya command plugin to query build info (#163) * Add Maya command plugin to querry build info * fix indentation * Update copyright year * misc * Removed duplicate command args. Updated tests * Remove help string from viewCommand plugin cmd. * Move help string to a Markdown file * Update test file --- doc/mayaHydraCommands.md | 117 ++++++++++++++++++ lib/mayaHydra/hydraExtensions/mhBuildInfo.cpp | 3 +- .../hydraExtensions/mhBuildInfo.h.src | 4 +- lib/mayaHydra/mayaPlugin/CMakeLists.txt | 1 + lib/mayaHydra/mayaPlugin/plugin.cpp | 13 ++ .../mayaPlugin/pluginBuildInfoCommand.cpp | 113 +++++++++++++++++ .../mayaPlugin/pluginBuildInfoCommand.h | 37 ++++++ lib/mayaHydra/mayaPlugin/viewCommand.cpp | 46 +------ .../render/mayaToHydra/testMtohCommand.py | 29 ++--- 9 files changed, 303 insertions(+), 60 deletions(-) create mode 100644 doc/mayaHydraCommands.md create mode 100644 lib/mayaHydra/mayaPlugin/pluginBuildInfoCommand.cpp create mode 100644 lib/mayaHydra/mayaPlugin/pluginBuildInfoCommand.h diff --git a/doc/mayaHydraCommands.md b/doc/mayaHydraCommands.md new file mode 100644 index 0000000000..5a4a7e33c5 --- /dev/null +++ b/doc/mayaHydraCommands.md @@ -0,0 +1,117 @@ +# Maya command plugins used in MayaHydra + +# MayaHydra Command Plugin + +The `MayaHydra` command plugin provides a set of utilities for interacting with the Hydra rendering framework within Maya. Below are the available flags and their descriptions. + +## Basic Usage + +```bash +mayaHydra [flags] +``` +Available Flags +``` +-listDelegates / -ld: +``` +Returns the names of available scene delegates. +``` +-listRenderers / -lr: +``` +Returns the names of available render delegates. +``` +-listActiveRenderers / -lar: +``` +Returns the names of render delegates that are in use in at least one viewport. + +Renderer-Specific Commands +``` +-renderer / -r [RENDERER]: +``` +Specifies the renderer to target for the commands below. +``` +-getRendererDisplayName / -gn: +``` +Returns the display name for the given render delegate. +``` +-createRenderGlobals / -crg: +``` +Creates the render globals, optionally targeting a specific renderer. +``` +-userDefaults / -ud: +``` +A flag for -createRenderGlobals to restore user defaults on create. +``` +-updateRenderGlobals / -urg [ATTRIBUTE]: +``` +Forces the update of the render globals for the viewport, optionally targeting a specific renderer or setting. + +Advanced / Debugging Flags +To access advanced and debugging flags, use the -verbose / -v flag. + +Debug Flags +``` +-listRenderIndex / -lri -r [RENDERER]: +``` +Returns a list of all the rprims in the render index for the given render delegate. +``` +-visibleOnly / -vo: +``` +Affects the behavior of -listRenderIndex. If provided, only visible items in the render index are returned. +``` +-sceneDelegateId / -sid [SCENE_DELEGATE] -r [RENDERER]: +``` +Returns the path ID corresponding to the given render delegate and scene delegate pair. + +# MayaHydra Versioning and Build Information Flags + +The following flags are used to retrieve versioning and build information for the `MayaHydra` plugin. Each flag has both a short and a long form. + +## Usage Example + +To retrieve the full version of the `MayaHydra` plugin, use the following command: + +```bash +mayaHydraBuildInfo [-flags] +``` +## Version Information + +- **`-mjv` / `-majorVersion`**: + Returns the major version number of the plugin. + +- **`-mnv` / `-minorVersion`**: + Returns the minor version number of the plugin. + +- **`-pv` / `-patchVersion`**: + Returns the patch version number of the plugin. + +- **`-v` / `-version`**: + Returns the full version string of the plugin, which may include major, minor, and patch version numbers. + +## Build Information + +This information is expected to be set by a parent build system that has access and/or generates the following information. + +- **`-c` / `-cutIdentifier`**: + Returns the cut identifier associated with this build of the plugin. + +- **`-bn` / `-buildNumber`**: + Returns the build number for the plugin, typically representing the incremental number assigned during the build process. + +- **`-gc` / `-gitCommit`**: + Returns the Git commit hash that the build is based on, useful for tracing the exact source code used. + +- **`-gb` / `-gitBranch`**: + Returns the Git branch name that the build was created from. + +- **`-bd` / `-buildDate`**: + Returns the date on which the plugin was built. + + +### Summary +- **Version Information Flags**: Cover major, minor, and patch version details, as well as the full version string. +- **Build Information Flags**: Include cut identifier, build number, Git commit, Git branch, and build date. +- **Usage Examples**: Show how to retrieve specific pieces of information using the provided flags. + +This Markdown document provides a clear and concise reference for users who need to access versioning and build information for the `MayaHydra` plugin. + + diff --git a/lib/mayaHydra/hydraExtensions/mhBuildInfo.cpp b/lib/mayaHydra/hydraExtensions/mhBuildInfo.cpp index 2f538cb383..9db308208b 100644 --- a/lib/mayaHydra/hydraExtensions/mhBuildInfo.cpp +++ b/lib/mayaHydra/hydraExtensions/mhBuildInfo.cpp @@ -1,5 +1,5 @@ // -// Copyright 2023 Autodesk, Inc. All rights reserved. +// Copyright 2024 Autodesk, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ namespace MAYAHYDRA_NS_DEF { int MhBuildInfo::buildNumber() { return MAYAHYDRA_BUILD_NUMBER; } const char* MhBuildInfo::gitCommit() { return MAYAHYDRA_GIT_COMMIT; } const char* MhBuildInfo::gitBranch() { return MAYAHYDRA_GIT_BRANCH; } +const char* MhBuildInfo::cutId() { return MAYAHYDRA_CUT_ID; } const char* MhBuildInfo::buildDate() { return MAYAHYDRA_BUILD_DATE; } } // namespace MAYAHYDRA_NS_DEF diff --git a/lib/mayaHydra/hydraExtensions/mhBuildInfo.h.src b/lib/mayaHydra/hydraExtensions/mhBuildInfo.h.src index d6c9eb152f..bf0e4ce2b6 100644 --- a/lib/mayaHydra/hydraExtensions/mhBuildInfo.h.src +++ b/lib/mayaHydra/hydraExtensions/mhBuildInfo.h.src @@ -1,5 +1,5 @@ // -// Copyright 2023 Autodesk, Inc. All rights reserved. +// Copyright 2024 Autodesk, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ #define MAYAHYDRA_GIT_COMMIT "${MAYAHYDRA_GIT_COMMIT}" #define MAYAHYDRA_GIT_BRANCH "${MAYAHYDRA_GIT_BRANCH}" #define MAYAHYDRA_BUILD_DATE ${MAYAHYDRA_BUILD_DATE} +#define MAYAHYDRA_CUT_ID "${MAYAHYDRA_CUT_ID}" namespace MAYAHYDRA_NS_DEF { @@ -33,6 +34,7 @@ public: static int buildNumber(); static const char* gitCommit(); static const char* gitBranch(); + static const char* cutId(); static const char* buildDate(); }; diff --git a/lib/mayaHydra/mayaPlugin/CMakeLists.txt b/lib/mayaHydra/mayaPlugin/CMakeLists.txt index 67c9b74968..904d23c284 100644 --- a/lib/mayaHydra/mayaPlugin/CMakeLists.txt +++ b/lib/mayaHydra/mayaPlugin/CMakeLists.txt @@ -16,6 +16,7 @@ target_sources(${TARGET_NAME} renderOverride.cpp tokens.cpp viewCommand.cpp + pluginBuildInfoCommand.cpp ) set(HEADERS diff --git a/lib/mayaHydra/mayaPlugin/plugin.cpp b/lib/mayaHydra/mayaPlugin/plugin.cpp index 6551e2fa33..8624510a47 100644 --- a/lib/mayaHydra/mayaPlugin/plugin.cpp +++ b/lib/mayaHydra/mayaPlugin/plugin.cpp @@ -20,6 +20,7 @@ #include "renderGlobals.h" #include "renderOverride.h" #include "viewCommand.h" +#include "pluginBuildInfoCommand.h" #include @@ -150,6 +151,13 @@ PLUGIN_EXPORT MStatus initializePlugin(MObject obj) return ret; } + if (!plugin.registerCommand( + MayaHydraPluginInfoCommand::commandName, MayaHydraPluginInfoCommand::creator, MayaHydraPluginInfoCommand::createSyntax)) { + ret = MS::kFailure; + ret.perror("Error registering MayaHydraPluginInfo command!"); + return ret; + } + if (auto* renderer = MHWRender::MRenderer::theRenderer()) { for (const auto& desc : MayaHydra::MtohGetRendererDescriptions()) { auto mtohRenderer = std::make_unique(desc); @@ -235,5 +243,10 @@ PLUGIN_EXPORT MStatus uninitializePlugin(MObject obj) ret.perror("Error deregistering mayaHydra command!"); } + if (!plugin.deregisterCommand(MayaHydraPluginInfoCommand::commandName)) { + ret = MS::kFailure; + ret.perror("Error deregistering MayaHydraPluginInfo command!"); + } + return ret; } diff --git a/lib/mayaHydra/mayaPlugin/pluginBuildInfoCommand.cpp b/lib/mayaHydra/mayaPlugin/pluginBuildInfoCommand.cpp new file mode 100644 index 0000000000..50445c775f --- /dev/null +++ b/lib/mayaHydra/mayaPlugin/pluginBuildInfoCommand.cpp @@ -0,0 +1,113 @@ +// +// Copyright 2024 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 "pluginBuildInfoCommand.h" + +#include + +#include +#include + +#define XSTR(x) STR(x) +#define STR(x) #x + +namespace MAYAHYDRA_NS_DEF { + +const MString MayaHydraPluginInfoCommand::commandName("mayaHydraBuildInfo"); + +namespace { + +// Versioning and build information. +constexpr auto kMajorVersion = "-mjv"; +constexpr auto kMajorVersionLong = "-majorVersion"; + +constexpr auto kMinorVersion = "-mnv"; +constexpr auto kMinorVersionLong = "-minorVersion"; + +constexpr auto kPatchVersion = "-pv"; +constexpr auto kPatchVersionLong = "-patchVersion"; + +constexpr auto kVersion = "-v"; +constexpr auto kVersionLong = "-version"; + +constexpr auto kCutId = "-c"; +constexpr auto kCutIdLong = "-cutIdentifier"; + +constexpr auto kBuildNumber = "-bn"; +constexpr auto kBuildNumberLong = "-buildNumber"; + +constexpr auto kGitCommit = "-gc"; +constexpr auto kGitCommitLong = "-gitCommit"; + +constexpr auto kGitBranch = "-gb"; +constexpr auto kGitBranchLong = "-gitBranch"; + +constexpr auto kBuildDate = "-bd"; +constexpr auto kBuildDateLong = "-buildDate"; + +} // namespace + +MSyntax MayaHydraPluginInfoCommand::createSyntax() +{ + MSyntax syntax; + syntax.enableQuery(false); + syntax.enableEdit(false); + + // Versioning and build information flags. + syntax.addFlag(kMajorVersion, kMajorVersionLong); + syntax.addFlag(kMinorVersion, kMinorVersionLong); + syntax.addFlag(kPatchVersion, kPatchVersionLong); + syntax.addFlag(kVersion, kVersionLong); + syntax.addFlag(kCutId, kCutIdLong); + syntax.addFlag(kBuildNumber, kBuildNumberLong); + syntax.addFlag(kGitCommit, kGitCommitLong); + syntax.addFlag(kGitBranch, kGitBranchLong); + syntax.addFlag(kBuildDate, kBuildDateLong); + + return syntax; +} + +MStatus MayaHydraPluginInfoCommand::doIt(const MArgList& args) +{ + MStatus st; + MArgParser argData(syntax(), args, &st); + if (!st) + return st; + + if (argData.isFlagSet(kMajorVersion)) { + setResult(MAYAHYDRA_MAJOR_VERSION); // int + } else if (argData.isFlagSet(kMinorVersion)) { + setResult(MAYAHYDRA_MINOR_VERSION); // int + } else if (argData.isFlagSet(kPatchVersion)) { + setResult(MAYAHYDRA_PATCH_LEVEL); // int + } else if (argData.isFlagSet(kVersion)) { + setResult(XSTR(MAYAHYDRA_VERSION)); // convert to string + } else if (argData.isFlagSet(kCutId)) { + setResult(MhBuildInfo::cutId()); + } else if (argData.isFlagSet(kBuildNumber)) { + setResult(MhBuildInfo::buildNumber()); + } else if (argData.isFlagSet(kGitCommit)) { + setResult(MhBuildInfo::gitCommit()); + } else if (argData.isFlagSet(kGitBranch)) { + setResult(MhBuildInfo::gitBranch()); + } else if (argData.isFlagSet(kBuildDate)) { + setResult(MhBuildInfo::buildDate()); + } + + return MS::kSuccess; +} + +} // namespace MAYAHYDRA_NS_DEF diff --git a/lib/mayaHydra/mayaPlugin/pluginBuildInfoCommand.h b/lib/mayaHydra/mayaPlugin/pluginBuildInfoCommand.h new file mode 100644 index 0000000000..27fdeffed3 --- /dev/null +++ b/lib/mayaHydra/mayaPlugin/pluginBuildInfoCommand.h @@ -0,0 +1,37 @@ +// +// Copyright 2024 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 MAYAHYDRA_PULINGINFO_CMD_H +#define MAYAHYDRA_PULINGINFO_CMD_H + +#include +#include + +namespace MAYAHYDRA_NS_DEF { + +class MayaHydraPluginInfoCommand : public MPxCommand +{ +public: + static void* creator() { return new MayaHydraPluginInfoCommand(); } + static MSyntax createSyntax(); + + static const MString commandName; + + MStatus doIt(const MArgList& args) override; +}; + +} // namespace MAYAHYDRA_NS_DEF +#endif // MAYAHYDRA_PULINGINFO_CMD_H diff --git a/lib/mayaHydra/mayaPlugin/viewCommand.cpp b/lib/mayaHydra/mayaPlugin/viewCommand.cpp index 960c517b9f..db8309cb70 100644 --- a/lib/mayaHydra/mayaPlugin/viewCommand.cpp +++ b/lib/mayaHydra/mayaPlugin/viewCommand.cpp @@ -65,9 +65,6 @@ constexpr auto _updateRenderGlobalsLong = "-updateRenderGlobals"; constexpr auto _help = "-h"; constexpr auto _helpLong = "-help"; -constexpr auto _verbose = "-v"; -constexpr auto _verboseLong = "-verbose"; - constexpr auto _listRenderIndex = "-lri"; constexpr auto _listRenderIndexLong = "-listRenderIndex"; @@ -103,40 +100,8 @@ constexpr auto _rendererIdLong = "-renderer"; constexpr auto _userDefaultsId = "-u"; constexpr auto _userDefaultsIdLong = "-userDefaults"; -constexpr auto _helpText = R"HELP( -Maya to Hydra utility function. -Usage: mayaHydra [flags] --listDelegates/-ld : Returns the names of available scene delegates. --listRenderers/-lr : Returns the names of available render delegates. --listActiveRenderers/-lar : Returns the names of render delegates that are in - use in at least one viewport. - --renderer/-r [RENDERER]: Renderer to target for the commands below. --getRendererDisplayName/-gn : Returns the display name for the given render delegate. --createRenderGlobals/-crg: Creates the render globals, optionally targetting a - specific renderer. --userDefaults/-ud: Flag for createRenderGlobals to restore user defaults on create. --updateRenderGlobals/-urg [ATTRIBUTE]: Forces the update of the render globals - for the viewport, optionally targetting a specific renderer or setting. -)HELP"; - -constexpr auto _helpNonVerboseText = R"HELP( -Use -verbose/-v to see advanced / debugging flags - -)HELP"; - -constexpr auto _helpVerboseText = R"HELP( -Debug flags: - --listRenderIndex/-lri -r [RENDERER]: Returns a list of all the rprims in the - render index for the given render delegate. - --visibleOnly/-vo: Flag which affects the behavior of -listRenderIndex - if - given, then only visible items in the render index are returned. - --sceneDelegateId/-sid [SCENE_DELEGATE] -r [RENDERER]: Returns the path id - corresponding to the given render delegate / scene delegate pair. - +constexpr auto _helpText = R"HELP(For details on args usage please see +https://github.com/Autodesk/maya-hydra/tree/dev/doc/mayaHydraCommads.md )HELP"; } // namespace @@ -165,8 +130,6 @@ MSyntax MtohViewCmd::createSyntax() syntax.addFlag(_help, _helpLong); - syntax.addFlag(_verbose, _verboseLong); - // Debug / testing flags syntax.addFlag(_listRenderIndex, _listRenderIndexLong); @@ -243,11 +206,6 @@ MStatus MtohViewCmd::doIt(const MArgList& args) setResult(MString(dn.c_str())); } else if (db.isFlagSet(_help)) { MString helpText = _helpText; - if (db.isFlagSet(_verbose)) { - helpText += _helpVerboseText; - } else { - helpText += _helpNonVerboseText; - } MGlobal::displayInfo(helpText); } else if (db.isFlagSet(_createRenderGlobals)) { bool userDefaults = db.isFlagSet(_userDefaultsId); diff --git a/test/lib/mayaUsd/render/mayaToHydra/testMtohCommand.py b/test/lib/mayaUsd/render/mayaToHydra/testMtohCommand.py index f7d0e77d8e..4de5b12b7c 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/testMtohCommand.py +++ b/test/lib/mayaUsd/render/mayaToHydra/testMtohCommand.py @@ -110,22 +110,23 @@ def test_createRenderGlobals(self): "defaultRenderGlobals.mtohMotionSampleStart")) def test_versionInfo(self): - self.assertGreaterEqual(cmds.mayaHydra(majorVersion=True), 0) - self.assertGreaterEqual(cmds.mayaHydra(mjv=True), 0) - self.assertGreaterEqual(cmds.mayaHydra(minorVersion=True), 0) - self.assertGreaterEqual(cmds.mayaHydra(mnv=True), 0) - self.assertGreaterEqual(cmds.mayaHydra(patchVersion=True), 0) - self.assertGreaterEqual(cmds.mayaHydra(pv=True), 0) + self.assertGreaterEqual(cmds.mayaHydraBuildInfo(majorVersion=True), 0) + self.assertGreaterEqual(cmds.mayaHydraBuildInfo(mjv=True), 0) + self.assertGreaterEqual(cmds.mayaHydraBuildInfo(minorVersion=True), 0) + self.assertGreaterEqual(cmds.mayaHydraBuildInfo(mnv=True), 0) + self.assertGreaterEqual(cmds.mayaHydraBuildInfo(patchVersion=True), 0) + self.assertGreaterEqual(cmds.mayaHydraBuildInfo(pv=True), 0) def test_buildInfo(self): - self.assertGreaterEqual(cmds.mayaHydra(buildNumber=True), 0) - self.assertGreaterEqual(cmds.mayaHydra(bn=True), 0) - self.assertNotEqual(cmds.mayaHydra(gitCommit=True), '') - self.assertNotEqual(cmds.mayaHydra(gc=True), '') - self.assertNotEqual(cmds.mayaHydra(gitBranch=True), '') - self.assertNotEqual(cmds.mayaHydra(gb=True), '') - self.assertNotEqual(cmds.mayaHydra(buildDate=True), '') - self.assertNotEqual(cmds.mayaHydra(bd=True), '') + self.assertGreaterEqual(cmds.mayaHydraBuildInfo(buildNumber=True), 0) + self.assertGreaterEqual(cmds.mayaHydraBuildInfo(bn=True), 0) + self.assertNotEqual(cmds.mayaHydraBuildInfo(gitCommit=True), '') + self.assertNotEqual(cmds.mayaHydraBuildInfo(gc=True), '') + self.assertNotEqual(cmds.mayaHydraBuildInfo(gitBranch=True), '') + self.assertNotEqual(cmds.mayaHydraBuildInfo(gb=True), '') + self.assertNotEqual(cmds.mayaHydraBuildInfo(buildDate=True), '') + self.assertNotEqual(cmds.mayaHydraBuildInfo(bd=True), '') + self.assertNotEqual(cmds.mayaHydraBuildInfo(cutIdentifier=True), "DEVBLD") if __name__ == '__main__': fixturesUtils.runTests(globals()) From 7ee23a821729e043101d4d156d17fe750dd5abe5 Mon Sep 17 00:00:00 2001 From: debloip-adsk <145056365+debloip-adsk@users.noreply.github.com> Date: Fri, 30 Aug 2024 09:31:15 -0400 Subject: [PATCH 3/4] HYDRA-1102 : Fix instances selections being all wiped on an instancer when only one is removed (#158) * HYDRA-1102 : Handle removing only specific selections rather than removing all * HYDRA-1102 : Add test * HYDRA-1102 : Remove unused function * HYDRA-1102 : Add past-the-end iterator check * HYDRA-1102 : Add comment in test * HYDRA-1102 : Fix missing edge case handling --- lib/flowViewport/fvpUtils.cpp | 16 +++ lib/flowViewport/fvpUtils.h | 2 + .../sceneIndex/fvpPathInterface.h | 7 +- .../sceneIndex/fvpSelectionSceneIndex.cpp | 2 +- .../selection/fvpPrefixPathMapper.cpp | 4 +- lib/flowViewport/selection/fvpSelection.cpp | 76 ++++++++++----- lib/flowViewport/selection/fvpSelection.h | 13 +-- .../sceneIndex/mayaHydraSceneIndex.cpp | 6 +- .../sceneIndex/registration.cpp | 26 ++--- .../testPointInstancingWireframeHighlight.cpp | 97 +++++++++++++++++++ .../testPointInstancingWireframeHighlight.py | 5 + 11 files changed, 189 insertions(+), 65 deletions(-) diff --git a/lib/flowViewport/fvpUtils.cpp b/lib/flowViewport/fvpUtils.cpp index 1ddc1ec73b..c0f4cfd1f6 100644 --- a/lib/flowViewport/fvpUtils.cpp +++ b/lib/flowViewport/fvpUtils.cpp @@ -15,6 +15,7 @@ #include "fvpUtils.h" +#include #include namespace FVP_NS_DEF { @@ -39,4 +40,19 @@ PXR_NS::HdDataSourceBaseHandle createFullySelectedDataSource() return PXR_NS::HdDataSourceBase::Cast(selectionBuilder.Build()); } +PXR_NS::HdDataSourceBaseHandle createInstanceSelectionDataSource(const PXR_NS::SdfPath& instancerPrimPath, int instanceIndex) +{ + PXR_NS::HdInstanceIndicesSchema::Builder instanceIndicesBuilder; + instanceIndicesBuilder.SetInstancer(PXR_NS::HdRetainedTypedSampledDataSource::New(instancerPrimPath)); + instanceIndicesBuilder.SetInstanceIndices(PXR_NS::HdRetainedTypedSampledDataSource>::New({instanceIndex})); + PXR_NS::HdSelectionSchema::Builder selectionBuilder; + // Instancer is expected to be marked "fully selected" even if only certain instances are selected, + // based on USD's _AddToSelection function in selectionSceneIndexObserver.cpp : + // https://github.com/PixarAnimationStudios/OpenUSD/blob/f7b8a021ce3d13f91a0211acf8a64a8b780524df/pxr/imaging/hdx/selectionSceneIndexObserver.cpp#L212-L251 + selectionBuilder.SetFullySelected(PXR_NS::HdRetainedTypedSampledDataSource::New(true)); + auto instanceIndicesDataSource = PXR_NS::HdDataSourceBase::Cast(instanceIndicesBuilder.Build()); + selectionBuilder.SetNestedInstanceIndices(PXR_NS::HdRetainedSmallVectorDataSource::New(1, &instanceIndicesDataSource)); + return PXR_NS::HdDataSourceBase::Cast(selectionBuilder.Build()); +} + } // namespace FVP_NS_DEF diff --git a/lib/flowViewport/fvpUtils.h b/lib/flowViewport/fvpUtils.h index 2e349f303f..b0a2929ac9 100644 --- a/lib/flowViewport/fvpUtils.h +++ b/lib/flowViewport/fvpUtils.h @@ -91,6 +91,8 @@ class PrimvarDataSource final : public PXR_NS::HdContainerDataSource PXR_NS::HdDataSourceBaseHandle FVP_API createFullySelectedDataSource(); +PXR_NS::HdDataSourceBaseHandle FVP_API createInstanceSelectionDataSource(const PXR_NS::SdfPath& instancerPrimPath, int instanceIndex); + } // namespace FVP_NS_DEF #endif // FVP_UTILS_H diff --git a/lib/flowViewport/sceneIndex/fvpPathInterface.h b/lib/flowViewport/sceneIndex/fvpPathInterface.h index 1a4615eba1..1d42cea41e 100644 --- a/lib/flowViewport/sceneIndex/fvpPathInterface.h +++ b/lib/flowViewport/sceneIndex/fvpPathInterface.h @@ -35,7 +35,12 @@ namespace FVP_NS_DEF { struct PrimSelection { PXR_NS::SdfPath primPath; - PXR_NS::HdDataSourceBaseHandle selectionDataSource; + std::optional instanceIndex; + + inline bool operator==(const PrimSelection &rhs) const { + return primPath == rhs.primPath + && instanceIndex == rhs.instanceIndex; + } }; // Using TfSmallVector to optimize for selections that map to a few prims, diff --git a/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.cpp b/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.cpp index eecac7aaf1..061d2ed49f 100644 --- a/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.cpp +++ b/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.cpp @@ -184,7 +184,7 @@ void SelectionSceneIndex::RemoveSelection(const Ufe::Path& appPath) HdSceneIndexObserver::DirtiedPrimEntries dirtiedPrims; for (const auto& primSelection : primSelections) { - if (_selection->Remove(primSelection.primPath)) { + if (_selection->Remove(primSelection)) { dirtiedPrims.emplace_back(primSelection.primPath, selectionsSchemaDefaultLocator); } } diff --git a/lib/flowViewport/selection/fvpPrefixPathMapper.cpp b/lib/flowViewport/selection/fvpPrefixPathMapper.cpp index 587d149eaa..771bf54cf1 100644 --- a/lib/flowViewport/selection/fvpPrefixPathMapper.cpp +++ b/lib/flowViewport/selection/fvpPrefixPathMapper.cpp @@ -54,9 +54,7 @@ PrimSelections PrefixPathMapper::UfePathToPrimSelections(const Ufe::Path& appPat primPath = primPath.AppendChild(TfToken(pathComponent.string())); } - auto selectionDataSource = createFullySelectedDataSource(); - - return PrimSelections{{primPath, selectionDataSource}}; + return PrimSelections{PrimSelection{primPath}}; } } diff --git a/lib/flowViewport/selection/fvpSelection.cpp b/lib/flowViewport/selection/fvpSelection.cpp index 42b47fc669..4cb21834a5 100644 --- a/lib/flowViewport/selection/fvpSelection.cpp +++ b/lib/flowViewport/selection/fvpSelection.cpp @@ -38,20 +38,14 @@ #include "flowViewport/selection/fvpSelection.h" +#include "flowViewport/fvpUtils.h" + #include "pxr/imaging/hd/selectionsSchema.h" PXR_NAMESPACE_USING_DIRECTIVE namespace FVP_NS_DEF { -HdDataSourceBaseHandle -Selection::_PrimSelectionState::GetVectorDataSource() const -{ - return HdSelectionsSchema::BuildRetained( - selectionSources.size(), selectionSources.data() - ); -}; - bool Selection::Add(const PrimSelection& primSelection) { @@ -59,20 +53,38 @@ Selection::Add(const PrimSelection& primSelection) return false; } - _pathToState[primSelection.primPath].selectionSources.push_back(primSelection.selectionDataSource); + _pathToSelections[primSelection.primPath].push_back(primSelection); return true; } -bool Selection::Remove(const PXR_NS::SdfPath& primPath) +bool Selection::Remove(const PrimSelection& primSelection) { - return (!primPath.IsEmpty() && (_pathToState.erase(primPath) == 1)); + if (primSelection.primPath.IsEmpty()) { + return false; + } + + // Remove the specific selection + auto itSelection = std::find( + _pathToSelections[primSelection.primPath].begin(), + _pathToSelections[primSelection.primPath].end(), + primSelection); + if (itSelection != _pathToSelections[primSelection.primPath].end()) { + _pathToSelections[primSelection.primPath].erase(itSelection); + } + + // If no selections remain, remove the entry entirely + if (_pathToSelections[primSelection.primPath].empty()) { + _pathToSelections.erase(primSelection.primPath); + } + + return true; } void Selection::Clear() { - _pathToState.clear(); + _pathToSelections.clear(); } void Selection::Replace(const PrimSelections& primSelections) @@ -83,26 +95,27 @@ void Selection::Replace(const PrimSelections& primSelections) if (primSelection.primPath.IsEmpty()) { continue; } - _pathToState[primSelection.primPath].selectionSources.push_back(primSelection.selectionDataSource); + _pathToSelections[primSelection.primPath].push_back(primSelection); } } void Selection::RemoveHierarchy(const PXR_NS::SdfPath& primPath) { - auto it = _pathToState.lower_bound(primPath); - while (it != _pathToState.end() && it->first.HasPrefix(primPath)) { - it = _pathToState.erase(it); + auto it = _pathToSelections.lower_bound(primPath); + while (it != _pathToSelections.end() && it->first.HasPrefix(primPath)) { + it = _pathToSelections.erase(it); } } bool Selection::IsEmpty() const { - return _pathToState.empty(); + return _pathToSelections.empty(); } bool Selection::IsFullySelected(const SdfPath& primPath) const { - return _pathToState.find(primPath) != _pathToState.end(); + return _pathToSelections.find(primPath) != _pathToSelections.end() + && !_pathToSelections.at(primPath).empty(); } bool Selection::HasFullySelectedAncestorInclusive(const SdfPath& primPath, const SdfPath& topmostAncestor/* = SdfPath::AbsoluteRootPath()*/) const @@ -110,7 +123,7 @@ bool Selection::HasFullySelectedAncestorInclusive(const SdfPath& primPath, const // FLOW_VIEWPORT_TODO Prefix tree would be much higher performance // than iterating over the whole selection, especially for a large // selection. PPT, 13-Sep-2023. - for(const auto& entry : _pathToState) { + for(const auto& entry : _pathToSelections) { if (primPath.HasPrefix(entry.first) && entry.first.HasPrefix(topmostAncestor)) { return true; } @@ -124,7 +137,7 @@ SdfPathVector Selection::FindFullySelectedAncestorsInclusive(const SdfPath& prim // than iterating over the whole selection, especially for a large // selection. PPT, 13-Sep-2023. SdfPathVector fullySelectedAncestors; - for(const auto& entry : _pathToState) { + for(const auto& entry : _pathToSelections) { if (primPath.HasPrefix(entry.first) && entry.first.HasPrefix(topmostAncestor)) { fullySelectedAncestors.push_back(entry.first); } @@ -135,8 +148,8 @@ SdfPathVector Selection::FindFullySelectedAncestorsInclusive(const SdfPath& prim SdfPathVector Selection::GetFullySelectedPaths() const { SdfPathVector fullySelectedPaths; - fullySelectedPaths.reserve(_pathToState.size()); - for(const auto& entry : _pathToState) { + fullySelectedPaths.reserve(_pathToSelections.size()); + for(const auto& entry : _pathToSelections) { fullySelectedPaths.emplace_back(entry.first); } return fullySelectedPaths; @@ -146,9 +159,22 @@ HdDataSourceBaseHandle Selection::GetVectorDataSource( const PXR_NS::SdfPath& primPath ) const { - auto it = _pathToState.find(primPath); - return (it != _pathToState.end()) ? - it->second.GetVectorDataSource() : nullptr; + auto it = _pathToSelections.find(primPath); + if (it == _pathToSelections.end()) { + return nullptr; + } + + std::vector selectionDataSources; + for (const auto& selection : it->second) { + if (selection.instanceIndex.has_value()) { + selectionDataSources.push_back(createInstanceSelectionDataSource(selection.primPath, selection.instanceIndex.value())); + } else { + selectionDataSources.push_back(createFullySelectedDataSource()); + } + } + return HdSelectionsSchema::BuildRetained( + selectionDataSources.size(), selectionDataSources.data() + ); } } diff --git a/lib/flowViewport/selection/fvpSelection.h b/lib/flowViewport/selection/fvpSelection.h index 55aa52c867..a6b3087444 100644 --- a/lib/flowViewport/selection/fvpSelection.h +++ b/lib/flowViewport/selection/fvpSelection.h @@ -49,7 +49,7 @@ class Selection // Returns true if the removal was successful, false otherwise. FVP_API - bool Remove(const PXR_NS::SdfPath& primPath); + bool Remove(const PrimSelection& primSelection); // Replace the selection with the contents of the argument primPath vector. // Any empty primPath in the argument will be skipped. @@ -93,17 +93,10 @@ class Selection GetVectorDataSource(const PXR_NS::SdfPath& primPath) const; private: - - struct _PrimSelectionState { - // Container data sources conforming to HdSelectionSchema - std::vector selectionSources; - - PXR_NS::HdDataSourceBaseHandle GetVectorDataSource() const; - }; - // Maps prim path to data sources to be returned by the vector data + // Maps prim path to selections to be returned by the vector data // source at locator selections. - std::map _pathToState; + std::map> _pathToSelections; }; } diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.cpp b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.cpp index 2cb0844623..ceca3f514a 100644 --- a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.cpp +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.cpp @@ -759,11 +759,7 @@ Fvp::PrimSelections MayaHydraSceneIndex::UfePathToPrimSelections(const Ufe::Path // the UFE path to a string, then does a Dag path lookup with the string. constexpr bool isSprim = false; // Can't handle sprims as of 15-Aug-2023. SdfPath primPath = GetPrimPath(UfeExtensions::ufeToDagPath(appPath), isSprim); - HdSelectionSchema::Builder selectionBuilder; - selectionBuilder.SetFullySelected(HdRetainedTypedSampledDataSource::New(true)); - auto selectionDataSource = HdDataSourceBase::Cast(selectionBuilder.Build()); - Fvp::PrimSelection primSelection {primPath, selectionDataSource}; - return Fvp::PrimSelections({primSelection}); + return Fvp::PrimSelections({Fvp::PrimSelection{primPath}}); } SdfPath MayaHydraSceneIndex::SetCameraViewport(const MDagPath& camPath, const GfVec4d& viewport) diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/registration.cpp b/lib/mayaHydra/hydraExtensions/sceneIndex/registration.cpp index 877ccc20c2..8c1adf879c 100644 --- a/lib/mayaHydra/hydraExtensions/sceneIndex/registration.cpp +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/registration.cpp @@ -87,21 +87,6 @@ class SceneObserver : public Observer } }; -HdDataSourceBaseHandle createInstanceSelectionDataSource(const SdfPath& instancerPrimPath, int instanceIndex) -{ - HdInstanceIndicesSchema::Builder instanceIndicesBuilder; - instanceIndicesBuilder.SetInstancer(HdRetainedTypedSampledDataSource::New(instancerPrimPath)); - instanceIndicesBuilder.SetInstanceIndices(HdRetainedTypedSampledDataSource>::New({instanceIndex})); - HdSelectionSchema::Builder selectionBuilder; - // Instancer is expected to be marked "fully selected" even if only certain instances are selected, - // based on USD's _AddToSelection function in selectionSceneIndexObserver.cpp : - // https://github.com/PixarAnimationStudios/OpenUSD/blob/f7b8a021ce3d13f91a0211acf8a64a8b780524df/pxr/imaging/hdx/selectionSceneIndexObserver.cpp#L212-L251 - selectionBuilder.SetFullySelected(HdRetainedTypedSampledDataSource::New(true)); - auto instanceIndicesDataSource = HdDataSourceBase::Cast(instanceIndicesBuilder.Build()); - selectionBuilder.SetNestedInstanceIndices(HdRetainedSmallVectorDataSource::New(1, &instanceIndicesDataSource)); - return HdDataSourceBase::Cast(selectionBuilder.Build()); -} - /// \class PathInterfaceSceneIndex /// /// Implement the path interface for plugin scene indices. @@ -159,10 +144,11 @@ class PathInterfaceSceneIndex : public Fvp::PathInterfaceSceneIndexBase } const auto lastComponentString = secondSegment.components().back().string(); - HdDataSourceBaseHandle selectionDataSource = lastComponentIsNumeric - ? createInstanceSelectionDataSource(primPath, std::stoi(lastComponentString)) - : Fvp::createFullySelectedDataSource(); - Fvp::PrimSelections primSelections({{primPath, selectionDataSource}}); + std::optional instanceIndex = std::nullopt; + if (lastComponentIsNumeric) { + instanceIndex = std::stoi(lastComponentString); + } + Fvp::PrimSelections primSelections({{primPath, instanceIndex}}); // Propagate selection to propagated prototypes auto ancestorsRange = primPath.GetAncestorsRange(); @@ -188,7 +174,7 @@ class PathInterfaceSceneIndex : public Fvp::PathInterfaceSceneIndexBase // for another instancer B will only mark the geometry-drawing instancer A as selected. This can be changed. // For now (2024/05/28), this only affects selection highlighting. if (propagatedPrim.primType != HdPrimTypeTokens->instancer) { - primSelections.push_back({propagatedPrimPath, selectionDataSource}); + primSelections.push_back({propagatedPrimPath, instanceIndex}); } } } diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testPointInstancingWireframeHighlight.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPointInstancingWireframeHighlight.cpp index 271ca5250c..943a8699bd 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/testPointInstancingWireframeHighlight.cpp +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPointInstancingWireframeHighlight.cpp @@ -321,3 +321,100 @@ TEST(PointInstancingWireframeHighlight, prototype) testPrototypeHighlightFn(prototypeItem, prototypePath); testPrototypeHighlightFn(prototypeParentItem, prototypeParentPath); } + +TEST(PointInstancingWireframeHighlight, multiInstances) +{ + const SceneIndicesVector& terminalSceneIndices = GetTerminalSceneIndices(); + ASSERT_FALSE(terminalSceneIndices.empty()); + SceneIndexInspector inspector(terminalSceneIndices.front()); + + auto isFvpMergingSceneIndexPredicate = SceneIndexDisplayNamePred("Flow Viewport Merging Scene Index"); + auto fvpMergingSceneIndex = TfDynamic_cast( + findSceneIndexInTree(terminalSceneIndices.front(), isFvpMergingSceneIndexPredicate)); + + auto isFvpWireframeSelectionHighlightSceneIndex = SceneIndexDisplayNamePred( + "Flow Viewport Wireframe Selection Highlight Scene Index"); + auto fvpWireframeSelectionHighlightSceneIndex = TfDynamic_cast( + findSceneIndexInTree(terminalSceneIndices.front(), isFvpWireframeSelectionHighlightSceneIndex)); + std::string selectionHighlightMirrorTag = fvpWireframeSelectionHighlightSceneIndex->GetSelectionHighlightMirrorTag(); + + auto ufeSelection = Ufe::GlobalSelection::get(); + + HdxSelectionSceneIndexObserver selectionObserver; + selectionObserver.SetSceneIndex(terminalSceneIndices.front()); + + // Create this test's selected scene items + auto topInstancerFirstInstancePath = Ufe::PathString::path(stagePathSegment + "," + "/Root/TopInstancerXform/TopInstancer/0"); + auto topInstancerSecondInstancePath = Ufe::PathString::path(stagePathSegment + "," + "/Root/TopInstancerXform/TopInstancer/1"); + + auto topInstancerFirstInstanceItem = Ufe::Hierarchy::createItem(topInstancerFirstInstancePath); + auto topInstancerSecondInstanceItem = Ufe::Hierarchy::createItem(topInstancerSecondInstancePath); + + // Initial state : ensure nothing is highlighted + ufeSelection->clear(); + + auto selectionHighlightMirrors = inspector.FindPrims([selectionHighlightMirrorTag](const HdSceneIndexBasePtr& sceneIndex, const SdfPath& primPath) -> bool { + return isSelectionHighlightMirror(primPath, selectionHighlightMirrorTag); + }); + EXPECT_TRUE(selectionHighlightMirrors.empty()); // No selection highlight mirrors + + auto meshPrims = inspector.FindPrims(findMeshPrimsPredicate); + for (const auto& meshPrim : meshPrims) { + HdLegacyDisplayStyleSchema displayStyle = HdLegacyDisplayStyleSchema::GetFromParent(meshPrim.prim.dataSource); + EXPECT_TRUE(displayStyle.IsDefined()); + EXPECT_FALSE(displayStyle.GetReprSelector()); // No specific repr is defined + } + + // Select instances + ufeSelection->append(topInstancerFirstInstanceItem); + ufeSelection->append(topInstancerSecondInstanceItem); + + auto firstInstancePrimSelections = fvpMergingSceneIndex->UfePathToPrimSelections(topInstancerFirstInstancePath); + ASSERT_EQ(firstInstancePrimSelections.size(), 1u); + auto instancerPrimPath = firstInstancePrimSelections.front().primPath; + + // Ensure selection is correct + ASSERT_EQ(selectionObserver.GetSelection()->GetAllSelectedPrimPaths().size(), 1u); + EXPECT_EQ(selectionObserver.GetSelection()->GetAllSelectedPrimPaths().front(), instancerPrimPath); + + // Validate scene structure + EXPECT_FALSE(inspector.FindPrims(findMeshPrimsPredicate).empty()); + auto selectionHighlightPath = getSelectionHighlightMirrorPathFromOriginal(instancerPrimPath, selectionHighlightMirrorTag); + assertSelectionHighlightCorrectness(inspector.GetSceneIndex(), selectionHighlightPath, selectionHighlightMirrorTag, HdReprTokens->refinedWire); + + // Get the selection highlight instancer's mask + HdSceneIndexPrim instancerHighlightPrim = inspector.GetSceneIndex()->GetPrim( + getSelectionHighlightMirrorPathFromOriginal(instancerPrimPath, selectionHighlightMirrorTag)); + HdInstancerTopologySchema instancerTopology = HdInstancerTopologySchema::GetFromParent(instancerHighlightPrim.dataSource); + ASSERT_TRUE(instancerTopology.IsDefined()); + ASSERT_NE(instancerTopology.GetMask(), nullptr); + auto mask = instancerTopology.GetMask()->GetTypedValue(0); + EXPECT_FALSE(mask.empty()); + + // Ensure only the selected instances are shown + std::set selectedInstanceIndices = { + std::stoul(topInstancerFirstInstancePath.getSegments().back().components().back().string()), + std::stoul(topInstancerSecondInstancePath.getSegments().back().components().back().string()) + }; + for (size_t iMask = 0; iMask < mask.size(); iMask++) { + EXPECT_EQ(mask[iMask], selectedInstanceIndices.find(iMask) != selectedInstanceIndices.end()); + } + + // Deselect the first instance; the second instance should still be selected + ufeSelection->remove(topInstancerFirstInstanceItem); + + // Get the selection highlight instancer's mask + instancerHighlightPrim = inspector.GetSceneIndex()->GetPrim( + getSelectionHighlightMirrorPathFromOriginal(instancerPrimPath, selectionHighlightMirrorTag)); + instancerTopology = HdInstancerTopologySchema::GetFromParent(instancerHighlightPrim.dataSource); + ASSERT_TRUE(instancerTopology.IsDefined()); + ASSERT_NE(instancerTopology.GetMask(), nullptr); + mask = instancerTopology.GetMask()->GetTypedValue(0); + EXPECT_FALSE(mask.empty()); + + // Ensure only the selected instance is shown + size_t selectedInstanceIndex = std::stoul(topInstancerSecondInstancePath.getSegments().back().components().back().string()); + for (size_t iMask = 0; iMask < mask.size(); iMask++) { + EXPECT_EQ(mask[iMask], iMask == selectedInstanceIndex); + } +} diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testPointInstancingWireframeHighlight.py b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPointInstancingWireframeHighlight.py index 71b10475d8..0ddbd0297e 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/testPointInstancingWireframeHighlight.py +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPointInstancingWireframeHighlight.py @@ -45,6 +45,11 @@ def test_PrototypeSelection(self): with PluginLoaded('mayaHydraCppTests'): cmds.mayaHydraCppTest( f="PointInstancingWireframeHighlight.prototype") + + def test_MultiInstancesSelection(self): + with PluginLoaded('mayaHydraCppTests'): + cmds.mayaHydraCppTest( + f="PointInstancingWireframeHighlight.multiInstances") if __name__ == '__main__': fixturesUtils.runTests(globals()) From d13ba28e5e368c4ee3835b26d7f874d94000f65b Mon Sep 17 00:00:00 2001 From: ppt-adsk Date: Tue, 3 Sep 2024 11:05:37 -0400 Subject: [PATCH 4/4] Isolate select scene index, with command-based implementation. (#164) * Isolate select scene index, with command-based implementation. * Update to latest dev. --- ...nAndSceneIndicesPerViewportDataManager.cpp | 86 +++++ ...ionAndSceneIndicesPerViewportDataManager.h | 52 ++- lib/flowViewport/debugCodes.cpp | 4 + lib/flowViewport/debugCodes.h | 1 + lib/flowViewport/sceneIndex/CMakeLists.txt | 2 + .../sceneIndex/fvpIsolateSelectSceneIndex.cpp | 280 +++++++++++++++ .../sceneIndex/fvpIsolateSelectSceneIndex.h | 181 ++++++++++ .../selection/fvpPathMapperRegistry.cpp | 23 +- .../selection/fvpPathMapperRegistry.h | 17 +- lib/flowViewport/selection/fvpSelection.cpp | 73 ++++ lib/flowViewport/selection/fvpSelection.h | 15 +- .../sceneIndex/mayaHydraSceneIndex.cpp | 49 +++ .../sceneIndex/mayaHydraSceneIndex.h | 4 + .../sceneIndex/registration.cpp | 40 ++- lib/mayaHydra/mayaPlugin/pluginDebugCodes.cpp | 4 + lib/mayaHydra/mayaPlugin/pluginDebugCodes.h | 3 +- lib/mayaHydra/mayaPlugin/renderOverride.cpp | 66 +++- .../mayaUsd/render/mayaToHydra/CMakeLists.txt | 1 + .../render/mayaToHydra/cpp/CMakeLists.txt | 1 + .../mayaToHydra/cpp/testIsolateSelect.cpp | 132 +++++++ .../mayaToHydra/cpp/testIsolateSelect.py | 334 ++++++++++++++++++ .../cpp/testPathMapperRegistry.cpp | 6 + .../render/mayaToHydra/cpp/testUtils.cpp | 34 ++ .../render/mayaToHydra/cpp/testUtils.h | 24 ++ 24 files changed, 1416 insertions(+), 16 deletions(-) create mode 100644 lib/flowViewport/sceneIndex/fvpIsolateSelectSceneIndex.cpp create mode 100644 lib/flowViewport/sceneIndex/fvpIsolateSelectSceneIndex.h create mode 100644 test/lib/mayaUsd/render/mayaToHydra/cpp/testIsolateSelect.cpp create mode 100644 test/lib/mayaUsd/render/mayaToHydra/cpp/testIsolateSelect.py diff --git a/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.cpp b/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.cpp index e88800eb90..cebd00d15d 100644 --- a/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.cpp +++ b/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.cpp @@ -19,6 +19,7 @@ #include "flowViewport/API/interfacesImp/fvpDataProducerSceneIndexInterfaceImp.h" #include "flowViewport/API/interfacesImp/fvpInformationInterfaceImp.h" #include "flowViewport/sceneIndex/fvpRenderIndexProxy.h" +#include "flowViewport/selection/fvpSelection.h" #include "flowViewport/API/perViewportSceneIndicesData/fvpFilteringSceneIndicesChainManager.h" //Hydra headers @@ -151,6 +152,91 @@ ViewportInformationAndSceneIndicesPerViewportData* ViewportInformationAndSceneIn return nullptr; } +SelectionPtr ViewportInformationAndSceneIndicesPerViewportDataManager::GetOrCreateIsolateSelection(const std::string& viewportId) +{ + auto found = _isolateSelection.find(viewportId); + if (found != _isolateSelection.end()) { + return found->second; + } + auto selection = std::make_shared(); + _isolateSelection[viewportId] = selection; + return selection; +} + +SelectionPtr ViewportInformationAndSceneIndicesPerViewportDataManager::GetIsolateSelection(const std::string& viewportId) const +{ + auto found = _isolateSelection.find(viewportId); + return (found != _isolateSelection.end()) ? found->second : nullptr; +} + +void ViewportInformationAndSceneIndicesPerViewportDataManager::AddIsolateSelection( + const std::string& viewportId, + const PrimSelections& primSelections +) +{ + if (!TF_VERIFY(_isolateSelectSceneIndex, "No isolate select scene index set.")) { + return; + } + _CheckAndSetViewport(viewportId); + _isolateSelectSceneIndex->AddIsolateSelection(primSelections); +} + +void ViewportInformationAndSceneIndicesPerViewportDataManager::RemoveIsolateSelection( + const std::string& viewportId, + const PrimSelections& primSelections +) +{ + if (!TF_VERIFY(_isolateSelectSceneIndex, "No isolate select scene index set.")) { + return; + } + _CheckAndSetViewport(viewportId); + _isolateSelectSceneIndex->RemoveIsolateSelection(primSelections); +} + +void ViewportInformationAndSceneIndicesPerViewportDataManager::ReplaceIsolateSelection( + const std::string& viewportId, + const SelectionConstPtr& selection +) +{ + if (!TF_VERIFY(_isolateSelectSceneIndex, "No isolate select scene index set.")) { + return; + } + _CheckAndSetViewport(viewportId); + _isolateSelectSceneIndex->ReplaceIsolateSelection(selection); +} + +void ViewportInformationAndSceneIndicesPerViewportDataManager::ClearIsolateSelection(const std::string& viewportId) +{ + if (!TF_VERIFY(_isolateSelectSceneIndex, "No isolate select scene index set.")) { + return; + } + _CheckAndSetViewport(viewportId); + _isolateSelectSceneIndex->ClearIsolateSelection(); +} + +void ViewportInformationAndSceneIndicesPerViewportDataManager::SetIsolateSelectSceneIndex( + const IsolateSelectSceneIndexRefPtr& sceneIndex +) +{ + _isolateSelectSceneIndex = sceneIndex; +} + +IsolateSelectSceneIndexRefPtr +ViewportInformationAndSceneIndicesPerViewportDataManager::GetIsolateSelectSceneIndex() const +{ + return _isolateSelectSceneIndex; +} + +void +ViewportInformationAndSceneIndicesPerViewportDataManager::_CheckAndSetViewport( + const std::string& viewportId +) +{ + if (_isolateSelectSceneIndex->GetViewportId() != viewportId) { + _isolateSelectSceneIndex->SetViewport(viewportId, _isolateSelection.at(viewportId)); + } +} + const std::set& ViewportInformationAndSceneIndicesPerViewportDataManager::GetDataProducerSceneIndicesDataFromViewportId(const std::string& viewportId)const { diff --git a/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.h b/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.h index c282968702..645b929109 100644 --- a/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.h +++ b/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.h @@ -20,6 +20,9 @@ //Local headers #include "fvpViewportInformationAndSceneIndicesPerViewportData.h" #include "flowViewport/sceneIndex/fvpRenderIndexProxyFwd.h" +#include "flowViewport/sceneIndex/fvpPathInterface.h" +#include "flowViewport/sceneIndex/fvpIsolateSelectSceneIndex.h" +#include "flowViewport/selection/fvpSelectionFwd.h" //Hydra headers #include @@ -31,11 +34,16 @@ namespace FVP_NS_DEF { * * To get an instance of this class, please use * ViewportInformationAndSceneIndicesPerViewportDataManager& manager = ViewportInformationAndSceneIndicesPerViewportDataManager:Get(); +* +* The PerViewportDataManager also manages the per-viewport isolate selection, +* as well as providing access to the single isolate select scene index. */ class FVP_API ViewportInformationAndSceneIndicesPerViewportDataManager { public: + using ViewportIds = std::vector; + /// Manager accessor static ViewportInformationAndSceneIndicesPerViewportDataManager& Get(); @@ -53,18 +61,58 @@ class FVP_API ViewportInformationAndSceneIndicesPerViewportDataManager const ViewportInformationAndSceneIndicesPerViewportData* GetViewportInfoAndDataFromViewportId(const std::string& viewportId)const; ViewportInformationAndSceneIndicesPerViewportData* GetViewportInfoAndDataFromViewportId(const std::string& viewportId); + SelectionPtr GetOrCreateIsolateSelection(const std::string& viewportId); + SelectionPtr GetIsolateSelection(const std::string& viewportId) const; + + void AddIsolateSelection( + const std::string& viewportId, + const PrimSelections& primSelections + ); + void RemoveIsolateSelection( + const std::string& viewportId, + const PrimSelections& primSelections + ); + void ReplaceIsolateSelection( + const std::string& viewportId, + const SelectionConstPtr& selection + ); + void ClearIsolateSelection(const std::string& viewportId); + + // Get and set the isolate select scene index. This scene index provides + // isolate select services for all viewports. + void SetIsolateSelectSceneIndex( + const IsolateSelectSceneIndexRefPtr& sceneIndex + ); + IsolateSelectSceneIndexRefPtr GetIsolateSelectSceneIndex() const; + const std::set& GetDataProducerSceneIndicesDataFromViewportId(const std::string& viewportId)const; bool ModelPanelIsAlreadyRegistered(const std::string& modelPanel)const; void RemoveAllViewportsInformation(); private: + + // Singleton, no public creation or copy. + ViewportInformationAndSceneIndicesPerViewportDataManager() = default; + ViewportInformationAndSceneIndicesPerViewportDataManager( + const ViewportInformationAndSceneIndicesPerViewportDataManager& + ) = delete; + + void _CheckAndSetViewport(const std::string& viewportId); + ///Hydra viewport information ViewportInformationAndSceneIndicesPerViewportDataVector _viewportsInformationAndSceneIndicesPerViewportData; - ViewportInformationAndSceneIndicesPerViewportDataManager() = default; + // Isolate selection, keyed by viewportId. + std::map _isolateSelection; + + // Isolate select scene index. + IsolateSelectSceneIndexRefPtr _isolateSelectSceneIndex; }; +// Convenience shorthand declaration. +using PerViewportDataManager = ViewportInformationAndSceneIndicesPerViewportDataManager; + } //End of namespace FVP_NS_DEF -#endif // FLOW_VIEWPORT_API_PERVIEWPORTSCENEINDICESDATA_VIEWPORT_INFORMATION_AND_SCENE_INDICES_DATA_PER_VIEWPORT_DATA_MANAGER \ No newline at end of file +#endif // FLOW_VIEWPORT_API_PERVIEWPORTSCENEINDICESDATA_VIEWPORT_INFORMATION_AND_SCENE_INDICES_DATA_PER_VIEWPORT_DATA_MANAGER diff --git a/lib/flowViewport/debugCodes.cpp b/lib/flowViewport/debugCodes.cpp index 4df57df760..688747379d 100644 --- a/lib/flowViewport/debugCodes.cpp +++ b/lib/flowViewport/debugCodes.cpp @@ -44,6 +44,10 @@ TF_REGISTRY_FUNCTION(TfDebug) TF_DEBUG_ENVIRONMENT_SYMBOL( PXR_NS::FVP_WIREFRAME_SELECTION_HIGHLIGHT_SCENE_INDEX, "Print information about the Flow Viewport wireframe selection highlight scene index."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + PXR_NS::FVP_ISOLATE_SELECT_SCENE_INDEX, + "Print information about the Flow Viewport isolate select scene index."); } PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/flowViewport/debugCodes.h b/lib/flowViewport/debugCodes.h index 29e5d6b04b..48fd50c3de 100644 --- a/lib/flowViewport/debugCodes.h +++ b/lib/flowViewport/debugCodes.h @@ -31,6 +31,7 @@ TF_DEBUG_CODES( , FVP_APP_SELECTION_CHANGE , FVP_MERGING_SCENE_INDEX , FVP_WIREFRAME_SELECTION_HIGHLIGHT_SCENE_INDEX + , FVP_ISOLATE_SELECT_SCENE_INDEX ); // clang-format on diff --git a/lib/flowViewport/sceneIndex/CMakeLists.txt b/lib/flowViewport/sceneIndex/CMakeLists.txt index 7ddb346eb9..0c379394f7 100644 --- a/lib/flowViewport/sceneIndex/CMakeLists.txt +++ b/lib/flowViewport/sceneIndex/CMakeLists.txt @@ -3,6 +3,7 @@ # ----------------------------------------------------------------------------- target_sources(${TARGET_NAME} PRIVATE + fvpIsolateSelectSceneIndex.cpp fvpMergingSceneIndex.cpp fvpPathInterface.cpp fvpPathInterfaceSceneIndex.cpp @@ -19,6 +20,7 @@ target_sources(${TARGET_NAME} ) set(HEADERS + fvpIsolateSelectSceneIndex.h fvpMergingSceneIndex.h fvpPathInterface.h fvpPathInterfaceSceneIndex.h diff --git a/lib/flowViewport/sceneIndex/fvpIsolateSelectSceneIndex.cpp b/lib/flowViewport/sceneIndex/fvpIsolateSelectSceneIndex.cpp new file mode 100644 index 0000000000..b09693936c --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpIsolateSelectSceneIndex.cpp @@ -0,0 +1,280 @@ +// +// Copyright 2024 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 "flowViewport/debugCodes.h" + +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace { + +const HdContainerDataSourceHandle visOff = + HdVisibilitySchema::BuildRetained( + HdRetainedTypedSampledDataSource::New(false)); +} + +namespace FVP_NS_DEF { + +IsolateSelectSceneIndexRefPtr +IsolateSelectSceneIndex::New( + const std::string& viewportId, + const SelectionPtr& isolateSelection, + const HdSceneIndexBaseRefPtr& inputSceneIndex +) +{ + TF_DEBUG(FVP_ISOLATE_SELECT_SCENE_INDEX) + .Msg("IsolateSelectSceneIndex::New() called.\n"); + + auto isSi = PXR_NS::TfCreateRefPtr(new IsolateSelectSceneIndex( + viewportId, isolateSelection, inputSceneIndex)); + + isSi->SetDisplayName(kDisplayName); + return isSi; +} + +IsolateSelectSceneIndex::IsolateSelectSceneIndex( + const std::string& viewportId, + const SelectionPtr& isolateSelection, + const HdSceneIndexBaseRefPtr& inputSceneIndex +) + : ParentClass(inputSceneIndex) + , InputSceneIndexUtils(inputSceneIndex) + , _viewportId(viewportId) + , _isolateSelection(isolateSelection) +{} + +std::string IsolateSelectSceneIndex::GetViewportId() const +{ + return _viewportId; +} + +HdSceneIndexPrim IsolateSelectSceneIndex::GetPrim(const SdfPath& primPath) const +{ + TF_DEBUG(FVP_ISOLATE_SELECT_SCENE_INDEX) + .Msg("IsolateSelectSceneIndex::GetPrim(%s) called for viewport %s.\n", primPath.GetText(), _viewportId.c_str()); + + auto inputPrim = GetInputSceneIndex()->GetPrim(primPath); + + // If isolate selection is empty, everything is included. + if (_isolateSelection->IsEmpty()) { + return inputPrim; + } + + const bool included = + _isolateSelection->HasAncestorOrDescendantInclusive(primPath); + + TF_DEBUG(FVP_ISOLATE_SELECT_SCENE_INDEX) + .Msg(" prim path %s is %s isolate select set", primPath.GetText(), (included ? "INCLUDED in" : "EXCLUDED from")); + + if (!included) { + inputPrim.dataSource = HdContainerDataSourceEditor(inputPrim.dataSource) + .Set(HdVisibilitySchema::GetDefaultLocator(), visOff) + .Finish(); + } + + return inputPrim; +} + +SdfPathVector IsolateSelectSceneIndex::GetChildPrimPaths(const SdfPath& primPath) const { + // Prims are hidden, not removed. + return GetInputSceneIndex()->GetChildPrimPaths(primPath); +} + +void IsolateSelectSceneIndex::_PrimsAdded( + const HdSceneIndexBase& , + const HdSceneIndexObserver::AddedPrimEntries& entries) +{ + // Prims outside the isolate select set will be hidden in GetPrim(). + _SendPrimsAdded(entries); +} + +void IsolateSelectSceneIndex::_PrimsRemoved( + const HdSceneIndexBase& , + const HdSceneIndexObserver::RemovedPrimEntries& entries) +{ + // We rely on the application to remove from the isolate select set those + // prims that have been removed. + _SendPrimsRemoved(entries); +} + +void IsolateSelectSceneIndex::_PrimsDirtied( + const HdSceneIndexBase& , + const HdSceneIndexObserver::DirtiedPrimEntries& entries) +{ + _SendPrimsDirtied(entries); +} + +void IsolateSelectSceneIndex::AddIsolateSelection(const PrimSelections& primSelections) +{ + TF_DEBUG(FVP_ISOLATE_SELECT_SCENE_INDEX) + .Msg("IsolateSelectSceneIndex::AddIsolateSelection() called for viewport %s.\n", _viewportId.c_str()); + + HdSceneIndexObserver::DirtiedPrimEntries dirtiedEntries; + for (const auto& primSelection : primSelections) { + TF_DEBUG(FVP_ISOLATE_SELECT_SCENE_INDEX) + .Msg(" Adding %s to the isolate select set.\n", primSelection.primPath.GetText()); + _isolateSelection->Add(primSelection); + _DirtyVisibility(primSelection.primPath, &dirtiedEntries); + } + + _SendPrimsDirtied(dirtiedEntries); +} + +void IsolateSelectSceneIndex::RemoveIsolateSelection(const PrimSelections& primSelections) +{ + TF_DEBUG(FVP_ISOLATE_SELECT_SCENE_INDEX) + .Msg("IsolateSelectSceneIndex::RemoveIsolateSelection() called for viewport %s.\n", _viewportId.c_str()); + + HdSceneIndexObserver::DirtiedPrimEntries dirtiedEntries; + for (const auto& primSelection : primSelections) { + TF_DEBUG(FVP_ISOLATE_SELECT_SCENE_INDEX) + .Msg(" Removing %s from the isolate select set.\n", primSelection.primPath.GetText()); + _isolateSelection->Remove(primSelection); + _DirtyVisibility(primSelection.primPath, &dirtiedEntries); + } + + _SendPrimsDirtied(dirtiedEntries); +} + +void IsolateSelectSceneIndex::ClearIsolateSelection() +{ + TF_DEBUG(FVP_ISOLATE_SELECT_SCENE_INDEX) + .Msg("IsolateSelectSceneIndex::ClearIsolateSelection() called for viewport %s.\n", _viewportId.c_str()); + + auto isolateSelectPaths = _isolateSelection->GetFullySelectedPaths(); + + HdSceneIndexObserver::DirtiedPrimEntries dirtiedEntries; + for (const auto& primPath : isolateSelectPaths) { + TF_DEBUG(FVP_ISOLATE_SELECT_SCENE_INDEX) + .Msg(" Removing %s from the isolate select set.\n", primPath.GetText()); + _DirtyVisibility(primPath, &dirtiedEntries); + } + + _isolateSelection->Clear(); + + _SendPrimsDirtied(dirtiedEntries); +} + +void IsolateSelectSceneIndex::ReplaceIsolateSelection(const SelectionConstPtr& isolateSelection) +{ + TF_DEBUG(FVP_ISOLATE_SELECT_SCENE_INDEX) + .Msg("IsolateSelectSceneIndex::ReplaceIsolateSelection() called for viewport %s.\n", _viewportId.c_str()); + + _ReplaceIsolateSelection(isolateSelection); + + _isolateSelection->Replace(*isolateSelection); +} + +void IsolateSelectSceneIndex::_ReplaceIsolateSelection(const SelectionConstPtr& isolateSelection) +{ + // Keep paths in a set to minimize dirtying. First clear old paths. + auto clearedPaths = _isolateSelection->GetFullySelectedPaths(); + std::set dirtyPaths(clearedPaths.begin(), clearedPaths.end()); + + // Then add new paths. + const auto& newPaths = isolateSelection->GetFullySelectedPaths(); + for (const auto& primPath : newPaths) { + dirtyPaths.insert(primPath); + } + + // Finally, dirty all cleared and added prim paths. + HdSceneIndexObserver::DirtiedPrimEntries dirtiedEntries; + for (const auto& primPath : dirtyPaths) { + _DirtyVisibility(primPath, &dirtiedEntries); + } + + _SendPrimsDirtied(dirtiedEntries); +} + +void IsolateSelectSceneIndex::SetViewport( + const std::string& viewportId, + const SelectionPtr& isolateSelection +) +{ + TF_DEBUG(FVP_ISOLATE_SELECT_SCENE_INDEX) + .Msg("IsolateSelectSceneIndex::SetViewport() called for new viewport %s.\n", viewportId.c_str()); + TF_DEBUG(FVP_ISOLATE_SELECT_SCENE_INDEX) + .Msg(" Old viewport was %s.\n", _viewportId.c_str()); + TF_DEBUG(FVP_ISOLATE_SELECT_SCENE_INDEX) + .Msg(" Old selection is %p, new selection is %p.\n", &*_isolateSelection, &*isolateSelection); + + if ((isolateSelection == _isolateSelection) || + (viewportId == _viewportId)) { + TF_WARN("IsolateSelectSceneIndex::SetViewport() called with identical information, no operation performed."); + return; + } + + _ReplaceIsolateSelection(isolateSelection); + + _isolateSelection = isolateSelection; + _viewportId = viewportId; +} + +SelectionPtr IsolateSelectSceneIndex::GetIsolateSelection() const +{ + return _isolateSelection; +} + +void IsolateSelectSceneIndex::_DirtyVisibility( + const SdfPath& primPath, + HdSceneIndexObserver::DirtiedPrimEntries* dirtiedEntries +) const +{ + // Dirty visibility by going up the prim path. GetAncestorsRange() + // includes the path itself, as desired. + for (const auto& p : primPath.GetAncestorsRange()) { + TF_DEBUG(FVP_ISOLATE_SELECT_SCENE_INDEX) + .Msg(" %s: examining %s for isolate select dirtying.\n", _viewportId.c_str(), p.GetText()); + if (p.GetPathElementCount() == 0) { + break; + } + auto parent = p.GetParentPath(); + auto siblings = GetChildPrimPaths(parent); + for (const auto& s : siblings) { + if (s == p) { + continue; + } + TF_DEBUG(FVP_ISOLATE_SELECT_SCENE_INDEX) + .Msg(" %s: dirtying sibling %s for isolate select.\n", _viewportId.c_str(), s.GetText()); + _DirtyVisibilityRecursive(s, dirtiedEntries); + } + } +} + +void IsolateSelectSceneIndex::_DirtyVisibilityRecursive( + const SdfPath& primPath, + HdSceneIndexObserver::DirtiedPrimEntries* dirtiedEntries +) const +{ + TF_DEBUG(FVP_ISOLATE_SELECT_SCENE_INDEX) + .Msg(" %s: marking %s visibility locator dirty.\n", _viewportId.c_str(), primPath.GetText()); + + dirtiedEntries->emplace_back( + primPath, HdVisibilitySchema::GetDefaultLocator()); + + for (const auto& childPath : GetChildPrimPaths(primPath)) { + _DirtyVisibilityRecursive(childPath, dirtiedEntries); + } +} + +} //end of namespace FVP_NS_DEF diff --git a/lib/flowViewport/sceneIndex/fvpIsolateSelectSceneIndex.h b/lib/flowViewport/sceneIndex/fvpIsolateSelectSceneIndex.h new file mode 100644 index 0000000000..f7a69d3c0a --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpIsolateSelectSceneIndex.h @@ -0,0 +1,181 @@ +// +// Copyright 2024 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 FLOW_VIEWPORT_SCENE_INDEX_ISOLATE_SELECT_SCENE_INDEX_H +#define FLOW_VIEWPORT_SCENE_INDEX_ISOLATE_SELECT_SCENE_INDEX_H + +//Local headers +#include "flowViewport/api.h" + +#include "flowViewport/selection/fvpSelectionFwd.h" +#include "flowViewport/sceneIndex/fvpSceneIndexUtils.h" +#include "flowViewport/sceneIndex/fvpPathInterface.h" // For PrimSelections + +//Hydra headers +#include + +namespace FVP_NS_DEF { + +// Pixar declarePtrs.h TF_DECLARE_REF_PTRS macro unusable, places resulting +// type in PXR_NS. +class IsolateSelectSceneIndex; +typedef PXR_NS::TfRefPtr IsolateSelectSceneIndexRefPtr; +typedef PXR_NS::TfRefPtr IsolateSelectSceneIndexConstRefPtr; + +/// \class IsolateSelectSceneIndex +/// +/// A filtering scene index that changes the visibility of prims that are not +/// in its set to false. +/// +/// The input isolate select data to the isolate select scene index is a set of +/// scene index prim selections tracked as an Fvp::Selection. External code is +/// responsible for converting the application isolate selection into prim +/// selections. +/// +/// Isolate select does not remove prims from the scene, it hides them. This +/// matches the Maya algorithm. A prim's previous visibility is restored +/// simply by taking out the isolate select scene index, thereby allowing +/// the original visibility to be sent to the renderer unchanged. +/// +/// At time of writing a single isolate select scene index is used to service +/// all viewports in the application, by switching the isolate selection on the +/// isolate scene index using IsolateSelectSceneIndex::SetViewport(). +/// +/// IsolateSelectSceneIndex::GetPrim() passes through prims that have an +/// ancestor or descendant (including themselves) in the isolate selection. +/// Other prims are hidden by setting visibility off. +/// +/// When the isolate selection is changed, prim visibility in the scene is +/// dirtied in the following way: starting at the changed prim path, +/// - Dirty all sibling visibilities +/// - Move up to the prim's parent +/// - Recurse and dirty all sibling visibilities. +/// - End recursion at the scene root. +/// +/// Dirtying any prim's visibility recurses to its children, to dirty the +/// visibility for the entire subtree. +/// +/// For example, consider the following hierarchy: +/// +/// a +/// |_b +/// |_c +/// |_d +/// |_e +/// |_f +/// |_g +/// |_h +/// |_i +/// |_j +/// |_k +/// +/// Given an initially empty isolate selection, adding f to the isolate +/// selection will: +/// +/// - Dirty g's visibility, and recursively that of all its descendants. +/// - Recursing up to e, dirty b and k's visibility, and all their descendants. +/// - Recursing up to a (the root), the algorithm ends. +/// +class IsolateSelectSceneIndex : + public PXR_NS::HdSingleInputFilteringSceneIndexBase + , public Fvp::InputSceneIndexUtils +{ +public: + using ParentClass = PXR_NS::HdSingleInputFilteringSceneIndexBase; + using PXR_NS::HdSingleInputFilteringSceneIndexBase::_GetInputSceneIndex; + + static constexpr char kDisplayName[] = "Flow Viewport Isolate Select Scene Index"; + + FVP_API + static IsolateSelectSceneIndexRefPtr New( + const std::string& viewportId, + const SelectionPtr& isolateSelection, + const PXR_NS::HdSceneIndexBaseRefPtr& inputSceneIndex + ); + + // From HdSceneIndexBase + PXR_NS::HdSceneIndexPrim GetPrim(const PXR_NS::SdfPath& primPath) const override; + + PXR_NS::SdfPathVector GetChildPrimPaths(const PXR_NS::SdfPath& primPath) const override; + + ~IsolateSelectSceneIndex() override = default; + + FVP_API + void AddIsolateSelection(const PrimSelections& primSelections); + FVP_API + void RemoveIsolateSelection(const PrimSelections& primSelections); + FVP_API + void ReplaceIsolateSelection(const SelectionConstPtr& selection); + FVP_API + void ClearIsolateSelection(); + + // Set viewport information (viewport ID and isolate selection) for this + // scene index. This occurs when switching the single scene index between + // viewports. If the same viewport ID or isolate selection is given as an + // argument, a warning will be issued. Otherwise, the previous and the new + // isolate selections will be dirtied. + FVP_API + void SetViewport( + const std::string& viewportId, + const SelectionPtr& isolateSelection + ); + + // Get viewport information for this scene index. + FVP_API + std::string GetViewportId() const; + FVP_API + SelectionPtr GetIsolateSelection() const; + +protected: + void _PrimsAdded( + const PXR_NS::HdSceneIndexBase& sender, + const PXR_NS::HdSceneIndexObserver::AddedPrimEntries& entries) override; + + void _PrimsRemoved( + const PXR_NS::HdSceneIndexBase& sender, + const PXR_NS::HdSceneIndexObserver::RemovedPrimEntries& entries) override; + + void _PrimsDirtied( + const PXR_NS::HdSceneIndexBase& sender, + const PXR_NS::HdSceneIndexObserver::DirtiedPrimEntries& entries) override; + +private: + + IsolateSelectSceneIndex( + const std::string& viewportId, + const SelectionPtr& isolateSelection, + const PXR_NS::HdSceneIndexBaseRefPtr& inputSceneIndex + ); + + void _DirtyVisibility( + const PXR_NS::SdfPath& primPath, + PXR_NS::HdSceneIndexObserver::DirtiedPrimEntries* dirtiedEntries + ) const; + + void _DirtyVisibilityRecursive( + const PXR_NS::SdfPath& primPath, + PXR_NS::HdSceneIndexObserver::DirtiedPrimEntries* dirtiedEntries + ) const; + + void _ReplaceIsolateSelection(const SelectionConstPtr& selection); + + std::string _viewportId; + + SelectionPtr _isolateSelection{}; +}; + +}//end of namespace FVP_NS_DEF + +#endif //FLOW_VIEWPORT_SCENE_INDEX_ISOLATE_SELECT_SCENE_INDEX_H diff --git a/lib/flowViewport/selection/fvpPathMapperRegistry.cpp b/lib/flowViewport/selection/fvpPathMapperRegistry.cpp index 0ecf953c3a..3f69296766 100644 --- a/lib/flowViewport/selection/fvpPathMapperRegistry.cpp +++ b/lib/flowViewport/selection/fvpPathMapperRegistry.cpp @@ -25,6 +25,7 @@ namespace { Ufe::Trie mappers; +Fvp::PathMapperConstPtr fallbackMapper{}; } @@ -56,12 +57,28 @@ bool PathMapperRegistry::Unregister(const Ufe::Path& prefix) return mappers.remove(prefix) != nullptr; } +void PathMapperRegistry::SetFallbackMapper( + const PathMapperConstPtr& pathMapper +) +{ + fallbackMapper = pathMapper; +} + +PathMapperConstPtr PathMapperRegistry::GetFallbackMapper() const +{ + return fallbackMapper; +} + PathMapperConstPtr PathMapperRegistry::GetMapper(const Ufe::Path& path) const { - if (mappers.empty() || path.empty()) { + if (path.empty()) { return nullptr; } + if (mappers.empty()) { + return fallbackMapper; + } + // We are looking for the closest ancestor of the argument. Internal trie // nodes have no data, and exist only as parents for trie nodes with data. // In our case the trie node data is the path mapper, so we walk down the @@ -72,7 +89,7 @@ PathMapperConstPtr PathMapperRegistry::GetMapper(const Ufe::Path& path) const // If we've reached a trie leaf node before the end of our path, there // is no trie node with data as ancestor of the path. if (!child) { - return nullptr; + return fallbackMapper; } trieNode = child; @@ -83,7 +100,7 @@ PathMapperConstPtr PathMapperRegistry::GetMapper(const Ufe::Path& path) const } // We reached the end of the parent path without returning true, therefore // there are no ancestors. - return nullptr; + return fallbackMapper; } } diff --git a/lib/flowViewport/selection/fvpPathMapperRegistry.h b/lib/flowViewport/selection/fvpPathMapperRegistry.h index 171b3d4776..54e032e0b5 100644 --- a/lib/flowViewport/selection/fvpPathMapperRegistry.h +++ b/lib/flowViewport/selection/fvpPathMapperRegistry.h @@ -39,8 +39,13 @@ namespace FVP_NS_DEF { /// - All entries are unique. /// - No entry is a prefix (ancestor) of another entry. /// -/// Therefore, a fallback path mapping must be implemented outside the -/// application path to scene index path mapper. +/// A fallback path mapper can be provided to implement a path mapping chain of +/// responsibility, for an application's native data model paths. This is +/// useful as the path mapper uses plugin prim path prefixes to convert between +/// a data model path to one (or more) scene index prim path(s). The +/// application data model has no plugin data model Hydra scene index prim path +/// prefix, so the application data model should be made the fallback, if no +/// other path mapper prefix matches. class PathMapperRegistry { public: @@ -62,6 +67,14 @@ class PathMapperRegistry { FVP_API bool Unregister(const Ufe::Path& prefix); + //! Set a fallback path mapper. If set, it will be returned by + //! GetMapper() if no mapper is registered for a given argument path. + //! A null pointer argument removes the fallback path mapper. + FVP_API + void SetFallbackMapper(const PathMapperConstPtr& pathMapper); + FVP_API + PathMapperConstPtr GetFallbackMapper() const; + //! Get a path mapper for the argument application path. This //! mapper has a prefix that is an ancestor of the argument path. If no //! path mapper is found, returns a null pointer. diff --git a/lib/flowViewport/selection/fvpSelection.cpp b/lib/flowViewport/selection/fvpSelection.cpp index 4cb21834a5..42a564e8fe 100644 --- a/lib/flowViewport/selection/fvpSelection.cpp +++ b/lib/flowViewport/selection/fvpSelection.cpp @@ -99,6 +99,11 @@ void Selection::Replace(const PrimSelections& primSelections) } } +void Selection::Replace(const Selection& rhs) +{ + _pathToSelections = rhs._pathToSelections; +} + void Selection::RemoveHierarchy(const PXR_NS::SdfPath& primPath) { auto it = _pathToSelections.lower_bound(primPath); @@ -131,6 +136,74 @@ bool Selection::HasFullySelectedAncestorInclusive(const SdfPath& primPath, const return false; } +bool Selection::HasDescendantInclusive(const PXR_NS::SdfPath& primPath) const +{ + // No entries? No descendant + if (_pathToSelections.empty()) { + return false; + } + + // At least one entry. Skip all entries before argument. The iterator + // points to an entry with matching or greater key. + auto it = _pathToSelections.lower_bound(primPath); + + // Reached the end? Last entry is strictly smaller than, so no descendants. + if (it == _pathToSelections.end()) { + return false; + } + + // Not at the end. Query is exactly in the map, or is prefix to what is in + // the map (i.e. map contents is a descendant)? Return true. + return (it->first == primPath || it->first.HasPrefix(primPath)); +} + +bool +Selection::HasAncestorOrDescendantInclusive(const PXR_NS::SdfPath& primPath) const +{ + // Use std::map::lower_bound to accelerate prim path lookup. The map is + // lexically ordered on SdfPath, with shorter paths less than longer + // paths. Makes determining ancestors and descendants somewhat tricky, but + // efficient. A prefix tree would be an easier data structure to implement + // this functionality. + + // No entries? No ancestors or descendants. + if (_pathToSelections.empty()) { + return false; + } + + // At least one entry. Skip all entries before argument. The iterator + // points to an entry with matching or greater key. + auto it = _pathToSelections.lower_bound(primPath); + + // Reached the end? Last entry is strictly smaller than, so no descendants + // in map. Check if it's an ancestor. + if (it == _pathToSelections.end()) { + auto rit = _pathToSelections.rbegin(); + return primPath.HasPrefix(rit->first); + } + + // Not at the end. Map entry has matching or greater key, so check + // match, or if the map entry with greater key is a descendant (i.e. query + // is an ancestor). + if (it->first == primPath || // Query is in map + it->first.HasPrefix(primPath)) { // Query descendant in map + return true; + } + + // Map entry is strictly greater and not a descendant. For the map entry + // to be an ancestor of the query, it would have to be less than the query. + // Thus, if we're at the beginning, the map entry is unrelated to the query. + if (it == _pathToSelections.begin()) { + return false; + } + + // Map entry still strictly greater and not a descendant. Is the previous + // map entry (lower key) a prefix (ancestor) to the query (i.e. query is a + // descendant)? + it = std::prev(it); + return primPath.HasPrefix(it->first); // Ancestor in map +} + SdfPathVector Selection::FindFullySelectedAncestorsInclusive(const SdfPath& primPath, const SdfPath& topmostAncestor/* = SdfPath::AbsoluteRootPath()*/) const { // FLOW_VIEWPORT_TODO Prefix tree would be much higher performance diff --git a/lib/flowViewport/selection/fvpSelection.h b/lib/flowViewport/selection/fvpSelection.h index a6b3087444..a9362321fb 100644 --- a/lib/flowViewport/selection/fvpSelection.h +++ b/lib/flowViewport/selection/fvpSelection.h @@ -51,11 +51,14 @@ class Selection FVP_API bool Remove(const PrimSelection& primSelection); - // Replace the selection with the contents of the argument primPath vector. + // Replace the selection with the contents of the argument vector. // Any empty primPath in the argument will be skipped. FVP_API void Replace(const PrimSelections& primSelections); + FVP_API + void Replace(const Selection& selection); + // Remove all entries from the selection. FVP_API void Clear(); @@ -76,6 +79,16 @@ class Selection FVP_API bool HasFullySelectedAncestorInclusive(const PXR_NS::SdfPath& primPath, const PXR_NS::SdfPath& topmostAncestor = PXR_NS::SdfPath::AbsoluteRootPath()) const; + // Returns true if the argument itself is selected, or a descendant of the + // argument. + FVP_API + bool HasDescendantInclusive(const PXR_NS::SdfPath& primPath) const; + + // Returns true if the argument itself is selected, or an ancestor or + // descendant of the argument is selected. + FVP_API + bool HasAncestorOrDescendantInclusive(const PXR_NS::SdfPath& primPath) const; + // Returns the paths to all fully selected ancestors of the prim up to the specified // topmost ancestor. If the prim is itself selected, its path will also be returned. // By default, the topmost ancestor is set to the absolute root path, so that all diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.cpp b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.cpp index ceca3f514a..952c3420b8 100644 --- a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.cpp +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.cpp @@ -17,6 +17,8 @@ #include "mayaHydraSceneIndex.h" #include +#include +#include #include #include @@ -435,6 +437,21 @@ namespace { sActiveFacesName(L"PolyActiveFaces"); // When we have a render item which is a selection of // faces, it always has this name in maya. +class MayaPathMapper : public Fvp::PathMapper +{ +public: + MayaPathMapper(const MayaHydraSceneIndex& piSi) : _piSi(piSi) {} + + Fvp::PrimSelections + UfePathToPrimSelections(const Ufe::Path& appPath) const override { + return _piSi.UfePathToPrimSelectionsLit(appPath); + } + +private: + // Non-owning reference to prevent ownership cycle. + const MayaHydraSceneIndex& _piSi; +}; + } MayaHydraSceneIndex::MayaHydraSceneIndex( @@ -447,6 +464,7 @@ MayaHydraSceneIndex::MayaHydraSceneIndex( , _rprimPath(initData.delegateID.AppendPath(SdfPath(std::string("rprims")))) , _sprimPath(initData.delegateID.AppendPath(SdfPath(std::string("sprims")))) , _materialPath(initData.delegateID.AppendPath(SdfPath(std::string("materials")))) + , _mayaPathMapper(std::make_shared(*this)) { static std::once_flag once; std::call_once(once, []() { @@ -473,10 +491,18 @@ MayaHydraSceneIndex::MayaHydraSceneIndex( AddPrims({ { _mayaFacesSelectionMaterialPath, HdPrimTypeTokens->material, mayaHydraFacesSelectionMaterialDataSource } }); + + // Register a fallback path mapper in the path mapper registry. Non-Maya + // data models will have a Maya path segment prefix in their UFE path. + // Maya data will not, and will be picked up by the fallback mapper. + Fvp::PathMapperRegistry::Instance().SetFallbackMapper(_mayaPathMapper); } MayaHydraSceneIndex::~MayaHydraSceneIndex() { + // Unregister the fallback path mapper. + Fvp::PathMapperRegistry::Instance().SetFallbackMapper(nullptr); + //If you get a crash in a callback with a nullptr for _sceneIndex, // it may be due to the fact that the _sceneIndex pointer has been nulled as its ref count reached 0 but the destructor is still being called. //You should call RemoveCallbacksAndDeleteAdapters(); before the destructor is called. @@ -759,6 +785,29 @@ Fvp::PrimSelections MayaHydraSceneIndex::UfePathToPrimSelections(const Ufe::Path // the UFE path to a string, then does a Dag path lookup with the string. constexpr bool isSprim = false; // Can't handle sprims as of 15-Aug-2023. SdfPath primPath = GetPrimPath(UfeExtensions::ufeToDagPath(appPath), isSprim); + TF_DEBUG(MAYAHYDRALIB_SCENE_INDEX) + .Msg(" mapped to scene index path %s.\n", primPath.GetText()); + return Fvp::PrimSelections({Fvp::PrimSelection{primPath}}); +} + +Fvp::PrimSelections MayaHydraSceneIndex::UfePathToPrimSelectionsLit( + const Ufe::Path& appPath +) const +{ + TF_DEBUG(MAYAHYDRALIB_SCENE_INDEX) + .Msg("MayaHydraSceneIndex::UfePathToPrimSelectionsLit(const Ufe::Path& %s) called.\n", Ufe::PathString::string(appPath).c_str()); + + // Same as UfePathToPrimSelections(), except returns the "Lighted" + // hierarchy. Should not be required. Having the path mapper call + // UfePathToPrimSelections() would allow factoring out into a single path + // mapper for Usd and Maya (see registration.cpp). + if (appPath.runTimeId() != UfeExtensions::getMayaRunTimeId()) { + return {}; + } + + SdfPath primPath = GetLightedPrimsRootPath().AppendPath(toSdfPath(UfeExtensions::ufeToDagPath(appPath)).MakeRelativePath(SdfPath::AbsoluteRootPath())); + TF_DEBUG(MAYAHYDRALIB_SCENE_INDEX) + .Msg(" mapped to scene index path %s.\n", primPath.GetText()); return Fvp::PrimSelections({Fvp::PrimSelection{primPath}}); } diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.h b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.h index 7fcb54815d..74e2c4a2ce 100644 --- a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.h +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.h @@ -35,6 +35,7 @@ #include #include "flowViewport/sceneIndex/fvpPathInterface.h" +#include #include #include @@ -204,6 +205,7 @@ class MAYAHYDRALIB_API MayaHydraSceneIndex : public HdRetainedSceneIndex, public bool GetPlaybackRunning() const; Fvp::PrimSelections UfePathToPrimSelections(const Ufe::Path& appPath) const override; + Fvp::PrimSelections UfePathToPrimSelectionsLit(const Ufe::Path& appPath) const; //Sdfpath of the maya default material SdfPath GetDefaultMaterialPath() const{return _mayaDefaultMaterialPath;} @@ -354,6 +356,8 @@ class MAYAHYDRALIB_API MayaHydraSceneIndex : public HdRetainedSceneIndex, public SdfPath _rprimPath; SdfPath _sprimPath; SdfPath _materialPath; + + const Fvp::PathMapperConstPtr _mayaPathMapper{}; }; PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/registration.cpp b/lib/mayaHydra/hydraExtensions/sceneIndex/registration.cpp index 8c1adf879c..55e77217bd 100644 --- a/lib/mayaHydra/hydraExtensions/sceneIndex/registration.cpp +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/registration.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include @@ -206,7 +208,18 @@ class PathInterfaceSceneIndex : public Fvp::PathInterfaceSceneIndexBase (op.subOpType == ObjectPathChange::ObjectRename))) { const auto& siPath = _pi.GetSceneIndexAppPath(); if (siPath.startsWith(op.path)) { - _pi.SetSceneIndexAppPath(siPath.reparent(op.path, op.item->path())); + const auto oldPath = siPath; + auto newPath = oldPath.reparent(op.path, op.item->path()); + _pi.SetSceneIndexAppPath(newPath); + + // Update our entry in the path mapper registry. + auto mapper = Fvp::PathMapperRegistry::Instance().GetMapper( + oldPath); + TF_AXIOM(mapper); + TF_AXIOM(Fvp::PathMapperRegistry::Instance().Unregister( + oldPath)); + TF_AXIOM(Fvp::PathMapperRegistry::Instance().Register( + newPath, mapper)); } } } @@ -214,6 +227,21 @@ class PathInterfaceSceneIndex : public Fvp::PathInterfaceSceneIndexBase PathInterfaceSceneIndex& _pi; }; + class UsdPathMapper : public Fvp::PathMapper + { + public: + UsdPathMapper(const PathInterfaceSceneIndex& piSi) : _piSi(piSi) {} + + Fvp::PrimSelections + UfePathToPrimSelections(const Ufe::Path& appPath) const override { + return _piSi.UfePathToPrimSelections(appPath); + } + + private: + // Non-owning reference to prevent ownership cycle. + const PathInterfaceSceneIndex& _piSi; + }; + PathInterfaceSceneIndex( const HdSceneIndexBaseRefPtr& inputSceneIndex, const SdfPath& sceneIndexPathPrefix, @@ -223,6 +251,7 @@ class PathInterfaceSceneIndex : public Fvp::PathInterfaceSceneIndexBase , _sceneIndexPathPrefix(sceneIndexPathPrefix) , _appSceneObserver(std::make_shared(*this)) , _sceneIndexAppPath(sceneIndexAppPath) + , _usdPathMapper(std::make_shared(*this)) { // The gateway node (proxy shape) is a Maya node, so the scene index // path must be a single segment. @@ -231,9 +260,17 @@ class PathInterfaceSceneIndex : public Fvp::PathInterfaceSceneIndexBase // Observe the scene to be informed of path changes to the gateway node // (proxy shape) that corresponds to our scene index data producer. Scene::instance().addObserver(_appSceneObserver); + + // Register a mapper in the path mapper registry. + TF_AXIOM(Fvp::PathMapperRegistry::Instance().Register( + _sceneIndexAppPath, _usdPathMapper)); } ~PathInterfaceSceneIndex() { + // Unregister our path mapper. + TF_AXIOM(Fvp::PathMapperRegistry::Instance().Unregister( + _sceneIndexAppPath)); + // Ufe::Subject has automatic cleanup of stale observers, but this can // be problematic on application exit if the library of the observer is // cleaned up before that of the subject, so simply stop observing. @@ -243,6 +280,7 @@ class PathInterfaceSceneIndex : public Fvp::PathInterfaceSceneIndexBase const SdfPath _sceneIndexPathPrefix; const Observer::Ptr _appSceneObserver; Ufe::Path _sceneIndexAppPath; + const Fvp::PathMapperConstPtr _usdPathMapper; }; constexpr char kMayaUsdProxyShapeNode[] = { "mayaUsdProxyShape" }; diff --git a/lib/mayaHydra/mayaPlugin/pluginDebugCodes.cpp b/lib/mayaHydra/mayaPlugin/pluginDebugCodes.cpp index 71a2f98670..8473c11be6 100644 --- a/lib/mayaHydra/mayaPlugin/pluginDebugCodes.cpp +++ b/lib/mayaHydra/mayaPlugin/pluginDebugCodes.cpp @@ -40,6 +40,10 @@ TF_REGISTRY_FUNCTION(TfDebug) TF_DEBUG_ENVIRONMENT_SYMBOL( MAYAHYDRALIB_RENDEROVERRIDE_SELECTION, "Print information about selection for the Maya VP2 RenderOverride."); + + TF_DEBUG_ENVIRONMENT_SYMBOL( + MAYAHYDRALIB_RENDEROVERRIDE_SCENE_INDEX_CHAIN_MGMT, + "Print information about scene index tree management for the Maya VP2 RenderOverride."); } PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/mayaPlugin/pluginDebugCodes.h b/lib/mayaHydra/mayaPlugin/pluginDebugCodes.h index 403ae5062e..d5f92cce76 100644 --- a/lib/mayaHydra/mayaPlugin/pluginDebugCodes.h +++ b/lib/mayaHydra/mayaPlugin/pluginDebugCodes.h @@ -28,7 +28,8 @@ TF_DEBUG_CODES( MAYAHYDRALIB_RENDEROVERRIDE_DEFAULT_LIGHTING, MAYAHYDRALIB_RENDEROVERRIDE_RENDER, MAYAHYDRALIB_RENDEROVERRIDE_RESOURCES, - MAYAHYDRALIB_RENDEROVERRIDE_SELECTION + MAYAHYDRALIB_RENDEROVERRIDE_SELECTION, + MAYAHYDRALIB_RENDEROVERRIDE_SCENE_INDEX_CHAIN_MGMT ); // clang-format on diff --git a/lib/mayaHydra/mayaPlugin/renderOverride.cpp b/lib/mayaHydra/mayaPlugin/renderOverride.cpp index c6cacc7892..03aa5f398b 100644 --- a/lib/mayaHydra/mayaPlugin/renderOverride.cpp +++ b/lib/mayaHydra/mayaPlugin/renderOverride.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -170,6 +171,16 @@ void replaceSelectionTask(PXR_NS::HdTaskSharedPtrVector* tasks) *found = HdTaskSharedPtr(new Fvp::SelectionTask); } +std::string getRenderingDestination( + const MHWRender::MFrameContext* frameContext +) +{ + TF_AXIOM(frameContext); + MString viewportId; + frameContext->renderingDestination(viewportId); + return std::string(viewportId.asChar()); +} + } PXR_NAMESPACE_OPEN_SCOPE @@ -649,11 +660,17 @@ MStatus MtohRenderOverride::Render( //This code with strings comparison will go away when doing multi viewports MString panelName; + std::string panelNameStr; auto framecontext = getFrameContext(); if (framecontext){ framecontext->renderingDestination(panelName); + panelNameStr = std::string(panelName.asChar()); + + TF_DEBUG(MAYAHYDRALIB_RENDEROVERRIDE_SCENE_INDEX_CHAIN_MGMT) + .Msg("Rendering destination is %s\n", panelName.asChar()); + auto& manager = Fvp::ViewportInformationAndSceneIndicesPerViewportDataManager::Get(); - if (false == manager.ModelPanelIsAlreadyRegistered(panelName.asChar())){ + if (false == manager.ModelPanelIsAlreadyRegistered(panelNameStr)){ //Get information from viewport std::string cameraName; @@ -666,7 +683,7 @@ MStatus MtohRenderOverride::Render( } //Create a HydraViewportInformation - const Fvp::InformationInterface::ViewportInformation hydraViewportInformation(std::string(panelName.asChar()), cameraName); + const Fvp::InformationInterface::ViewportInformation hydraViewportInformation(panelNameStr, cameraName); const bool dataProducerSceneIndicesAdded = manager.AddViewportInformation(hydraViewportInformation, _renderIndexProxy, _lastFilteringSceneIndexBeforeCustomFiltering); //Update the selection since we have added data producer scene indices through manager.AddViewportInformation to the merging scene index if (dataProducerSceneIndicesAdded && _selectionSceneIndex){ @@ -691,13 +708,16 @@ MStatus MtohRenderOverride::Render( _mayaHydraSceneIndex->SetParams(delegateParams); _mayaHydraSceneIndex->PreFrame(drawContext); + auto& manager = Fvp::ViewportInformationAndSceneIndicesPerViewportDataManager::Get(); if (_NeedToRecreateTheSceneIndicesChain(currentDisplayStyle)){ _blockPrimRemovalPropagationSceneIndex->setPrimRemovalBlocked(true);//Prevent prim removal propagation to keep the current selection. //We need to recreate the filtering scene index chain after the merging scene index as there was a change such as in the BBox display style which has been turned on or off. _lastFilteringSceneIndexBeforeCustomFiltering = nullptr;//Release + + TF_DEBUG(MAYAHYDRALIB_RENDEROVERRIDE_SCENE_INDEX_CHAIN_MGMT) + .Msg("Re-creating scene index chain to render %s\n", panelNameStr.c_str()); _CreateSceneIndicesChainAfterMergingSceneIndex(drawContext); - auto& manager = Fvp::ViewportInformationAndSceneIndicesPerViewportDataManager::Get(); - manager.RemoveViewportInformation(std::string(panelName.asChar())); + manager.RemoveViewportInformation(panelNameStr); //Get information from viewport std::string cameraName; M3dView view; @@ -707,10 +727,32 @@ MStatus MtohRenderOverride::Render( MFnCamera viewCamera(dpath); cameraName = viewCamera.name().asChar(); } - const Fvp::InformationInterface::ViewportInformation hydraViewportInformation(std::string(panelName.asChar()), cameraName); + const Fvp::InformationInterface::ViewportInformation hydraViewportInformation(panelNameStr, cameraName); manager.AddViewportInformation(hydraViewportInformation, _renderIndexProxy, _lastFilteringSceneIndexBeforeCustomFiltering); _blockPrimRemovalPropagationSceneIndex->setPrimRemovalBlocked(false);//Allow prim removal propagation again. } + else { + TF_DEBUG(MAYAHYDRALIB_RENDEROVERRIDE_SCENE_INDEX_CHAIN_MGMT) + .Msg("Re-using existing scene index chain to render %s\n", panelNameStr.c_str()); + + // Make sure the isolate selection scene index set to the proper + // isolate selection. We currently have a single scene index tree, + // thus a single isolate select scene index is common to and + // provides prims to render all viewports. + auto isSi = manager.GetIsolateSelectSceneIndex(); + auto isolateSelection = manager.GetOrCreateIsolateSelection(panelNameStr); + if (isSi->GetIsolateSelection() != isolateSelection) { + TF_DEBUG(MAYAHYDRALIB_RENDEROVERRIDE_SCENE_INDEX_CHAIN_MGMT) + .Msg("Switching scene index to isolate selection %p\n", &*isolateSelection); + // Isolate select scene index is being switched to a different + // viewport, set its isolate selection. + isSi->SetViewport(panelNameStr, isolateSelection); + } + else { + TF_DEBUG(MAYAHYDRALIB_RENDEROVERRIDE_SCENE_INDEX_CHAIN_MGMT) + .Msg("Re-using isolate selection %p\n", &*isolateSelection); + } + } } if (_displayStyleSceneIndex) { @@ -1119,9 +1161,21 @@ void MtohRenderOverride::_CreateSceneIndicesChainAfterMergingSceneIndex(const MH { //This function is where happens the ordering of filtering scene indices that are after the merging scene index //We use as its input scene index : _inputSceneIndexOfFilteringSceneIndicesChain + auto viewportId = getRenderingDestination(getFrameContext()); + + // Add isolate select scene index. + auto& perVpDataMgr = Fvp::PerViewportDataManager::Get(); + auto selection = perVpDataMgr.GetOrCreateIsolateSelection(viewportId); + auto isSi = Fvp::IsolateSelectSceneIndex::New( + viewportId, selection, _inputSceneIndexOfFilteringSceneIndicesChain); + // At time of writing we have a single selection scene index serving + // all viewports. + perVpDataMgr.SetIsolateSelectSceneIndex(isSi); + _lastFilteringSceneIndexBeforeCustomFiltering = isSi; + // Add display style scene index _lastFilteringSceneIndexBeforeCustomFiltering = _displayStyleSceneIndex = - Fvp::DisplayStyleOverrideSceneIndex::New(_inputSceneIndexOfFilteringSceneIndicesChain); + Fvp::DisplayStyleOverrideSceneIndex::New(_lastFilteringSceneIndexBeforeCustomFiltering); _displayStyleSceneIndex->addExcludedSceneRoot(MAYA_NATIVE_ROOT); // Maya native prims don't use global refinement // Add texture disabling Scene Index diff --git a/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt b/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt index db3a5e7b7d..7a0f7b65af 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt +++ b/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt @@ -53,6 +53,7 @@ set(INTERACTIVE_TEST_SCRIPT_FILES cpp/testColorPreferences.py cpp/testCppFramework.py cpp/testDataProducerExample.py + cpp/testIsolateSelect.py cpp/testMayaSceneFlattening.py cpp/testMayaUsdUfeItems.py cpp/testMergingSceneIndex.py diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt b/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt index 430cfbc9a6..3d1c9f236f 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt @@ -20,6 +20,7 @@ target_sources(${TARGET_NAME} testColorPreferences.cpp testCppFramework.cpp testHydraPrim.cpp + testIsolateSelect.cpp testMayaSceneFlattening.cpp testMayaUsdUfeItems.cpp testMergingSceneIndex.cpp diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testIsolateSelect.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testIsolateSelect.cpp new file mode 100644 index 0000000000..383bf221fb --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testIsolateSelect.cpp @@ -0,0 +1,132 @@ +// Copyright 2024 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 "testUtils.h" + +#include + +#include +#include +#include +#include + +#include +#include + +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +using namespace MayaHydra; + +TEST(TestHydraPrim, isVisible) +{ + const auto& sceneIndices = GetTerminalSceneIndices(); + auto siRoot = sceneIndices.front(); + + auto [argc, argv] = getTestingArgs(); + ASSERT_EQ(argc, 1); + const Ufe::Path appPath(Ufe::PathString::path(argv[0])); + + auto primSelections = ufePathToPrimSelections(appPath); + + // If an application path maps to multiple prim selections, all prim + // selections must be visible, else we fail. + unsigned int primVis = 0; + for (const auto& primSelection : primSelections) { + if (visibility(siRoot, primSelection.primPath)) { + ++primVis; + } + } + + ASSERT_EQ(primVis, primSelections.size()); +} + +TEST(TestHydraPrim, notVisible) +{ + const auto& sceneIndices = GetTerminalSceneIndices(); + auto siRoot = sceneIndices.front(); + + auto [argc, argv] = getTestingArgs(); + ASSERT_EQ(argc, 1); + const Ufe::Path appPath(Ufe::PathString::path(argv[0])); + + auto primSelections = ufePathToPrimSelections(appPath); + + // If an application path maps to multiple prim selections, all prim + // selections must be invisible, else we fail. + int primVis = 0; + for (const auto& primSelection : primSelections) { + if (visibility(siRoot, primSelection.primPath)) { + ++primVis; + } + } + + ASSERT_EQ(primVis, 0); +} + +TEST(TestIsolateSelection, add) +{ + auto [argc, argv] = getTestingArgs(); + ASSERT_EQ(argc, 2); + const std::string viewportId(argv[0]); + const Ufe::Path appPath(Ufe::PathString::path(argv[1])); + + auto& perVpDataMgr = Fvp::PerViewportDataManager::Get(); + auto primSelections = ufePathToPrimSelections(appPath); + perVpDataMgr.AddIsolateSelection(viewportId, primSelections); +} + +TEST(TestIsolateSelection, remove) +{ + auto [argc, argv] = getTestingArgs(); + ASSERT_EQ(argc, 2); + const std::string viewportId(argv[0]); + const Ufe::Path appPath(Ufe::PathString::path(argv[1])); + + auto& perVpDataMgr = Fvp::PerViewportDataManager::Get(); + auto primSelections = ufePathToPrimSelections(appPath); + perVpDataMgr.RemoveIsolateSelection(viewportId, primSelections); +} + +TEST(TestIsolateSelection, clear) +{ + auto [argc, argv] = getTestingArgs(); + ASSERT_EQ(argc, 1); + const std::string viewportId(argv[0]); + + auto& perVpDataMgr = Fvp::PerViewportDataManager::Get(); + perVpDataMgr.ClearIsolateSelection(viewportId); +} + +TEST(TestIsolateSelection, replace) +{ + auto [argc, argv] = getTestingArgs(); + ASSERT_GE(argc, 2); + const std::string viewportId(argv[0]); + + auto isolateSelect = std::make_shared(); + + for (int i=1; i < argc; ++i) { + const Ufe::Path appPath(Ufe::PathString::path(argv[i])); + auto primSelections = ufePathToPrimSelections(appPath); + for (const auto& primSelection : primSelections) { + isolateSelect->Add(primSelection); + } + } + + auto& perVpDataMgr = Fvp::PerViewportDataManager::Get(); + perVpDataMgr.ReplaceIsolateSelection(viewportId, isolateSelect); +} diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testIsolateSelect.py b/test/lib/mayaUsd/render/mayaToHydra/cpp/testIsolateSelect.py new file mode 100644 index 0000000000..873eb7a170 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testIsolateSelect.py @@ -0,0 +1,334 @@ +# Copyright 2024 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 fixturesUtils +import mtohUtils +from testUtils import PluginLoaded +import mayaUsd +import mayaUsd_createStageWithNewLayer +import maya.cmds as cmds +from pxr import UsdGeom + +class TestIsolateSelect(mtohUtils.MayaHydraBaseTestCase): + # MayaHydraBaseTestCase.setUpClass requirement. + _file = __file__ + + # Base class setUp() defines HdStorm as the renderer. + + def setupScene(self): + proxyShapePathStr = mayaUsd_createStageWithNewLayer.createStageWithNewLayer() + stage = mayaUsd.lib.GetPrim(proxyShapePathStr).GetStage() + + stage.DefinePrim('/parent1', 'Xform') + stage.DefinePrim('/parent2', 'Xform') + + UsdGeom.Cylinder.Define(stage, "/parent1/cylinder1") + UsdGeom.Cone.Define(stage, "/parent2/cone1") + UsdGeom.Sphere.Define(stage, "/parent2/sphere1") + + cmds.polyTorus() + cmds.polySphere() + cmds.polyCube() + cmds.polyCone() + cmds.group('pSphere1', 'pCube1') + cmds.group('pCone1') + + cmds.refresh() + + return ['|pTorus1', + '|pTorus1|pTorusShape1', + '|stage1|stageShape1,/parent1', + '|stage1|stageShape1,/parent1/cylinder1', + '|stage1|stageShape1,/parent2', + '|stage1|stageShape1,/parent2/cone1', + '|stage1|stageShape1,/parent2/sphere1', + '|group1', + '|group1|pSphere1', + '|group1|pSphere1|pSphereShape1', + '|group1|pCube1', + '|group1|pCube1|pCubeShape1', + '|group2', + '|group2|pCone1', + '|group2|pCone1|pConeShape1'] + + def setupMultiStageScene(self): + scene = self.setupScene() + + proxyShapePathStr = mayaUsd_createStageWithNewLayer.createStageWithNewLayer() + stage = mayaUsd.lib.GetPrim(proxyShapePathStr).GetStage() + + stage.DefinePrim('/parent1', 'Xform') + stage.DefinePrim('/parent2', 'Xform') + + UsdGeom.Cylinder.Define(stage, "/parent1/cylinder1") + UsdGeom.Cone.Define(stage, "/parent2/cone1") + UsdGeom.Sphere.Define(stage, "/parent2/sphere1") + + scene.extend([ + '|stage2|stageShape2,/parent1', + '|stage2|stageShape2,/parent1/cylinder1', + '|stage2|stageShape2,/parent2', + '|stage2|stageShape2,/parent2/cone1', + '|stage2|stageShape2,/parent2/sphere1']) + return scene + + def assertVisible(self, visible): + for v in visible: + self.trace("Testing %s for visibility\n" % v) + cmds.mayaHydraCppTest(v, f="TestHydraPrim.isVisible") + + def assertNotVisible(self, notVisible): + for nv in notVisible: + self.trace("Testing %s for invisibility\n" % nv) + cmds.mayaHydraCppTest(nv, f="TestHydraPrim.notVisible") + + def assertVisibility(self, visible, notVisible): + self.assertVisible(visible) + self.assertNotVisible(notVisible) + + def test_isolateSelectSingleViewport(self): + scene = self.setupScene() + with PluginLoaded('mayaHydraCppTests'): + # The default viewport is in the following panel. + vpPanel = 'modelPanel4' + + #============================================================ + # Add + #============================================================ + + # Add a single object to the isolate selection. Only that object, + # its ancestors, and its descendants are visible. + cmds.mayaHydraCppTest(vpPanel, "|pTorus1", f="TestIsolateSelection.add") + + visible = ['|pTorus1', '|pTorus1|pTorusShape1'] + notVisible = scene.copy() + for v in visible: + notVisible.remove(v) + + self.assertVisibility(visible, notVisible) + + # Add a USD object to the isolate selection. + cmds.mayaHydraCppTest(vpPanel, '|stage1|stageShape1,/parent2', f="TestIsolateSelection.add") + + for p in ['|stage1|stageShape1,/parent2', + '|stage1|stageShape1,/parent2/cone1', + '|stage1|stageShape1,/parent2/sphere1']: + visible.append(p) + notVisible.remove(p) + + self.assertVisibility(visible, notVisible) + + #============================================================ + # Remove + #============================================================ + + # Remove the Maya object from the isolate selection. Only the USD + # isolate selected objects are visible. + cmds.mayaHydraCppTest(vpPanel, "|pTorus1", f="TestIsolateSelection.remove") + + visible.clear() + notVisible = scene.copy() + + for p in ['|stage1|stageShape1,/parent2', + '|stage1|stageShape1,/parent2/cone1', + '|stage1|stageShape1,/parent2/sphere1']: + visible.append(p) + notVisible.remove(p) + + self.assertVisibility(visible, notVisible) + + # Remove the USD isolate selected object. Everything is now visible. + cmds.mayaHydraCppTest(vpPanel, '|stage1|stageShape1,/parent2', f="TestIsolateSelection.remove") + + visible = scene.copy() + notVisible.clear() + + self.assertVisibility(visible, notVisible) + + #============================================================ + # Clear + #============================================================ + + # Add an object back to the isolate selection. + cmds.mayaHydraCppTest(vpPanel, '|stage1|stageShape1,/parent1/cylinder1', f="TestIsolateSelection.add") + + notVisible = scene.copy() + visible.clear() + + for p in ['|stage1|stageShape1,/parent1', + '|stage1|stageShape1,/parent1/cylinder1']: + visible.append(p) + notVisible.remove(p) + + self.assertVisibility(visible, notVisible) + + # Clear the isolate selection. + cmds.mayaHydraCppTest(vpPanel, f="TestIsolateSelection.clear") + + visible = scene.copy() + notVisible.clear() + + self.assertVisibility(visible, notVisible) + + #============================================================ + # Replace + #============================================================ + + # Add an object back to the isolate selection. + cmds.mayaHydraCppTest(vpPanel, '|group2|pCone1', f="TestIsolateSelection.add") + + notVisible = scene.copy() + visible.clear() + + for p in ['|group2', '|group2|pCone1', '|group2|pCone1|pConeShape1']: + visible.append(p) + notVisible.remove(p) + + self.assertVisibility(visible, notVisible) + + # Replace this isolate selection with a different one. + cmds.mayaHydraCppTest(vpPanel, '|group1|pCube1', '|stage1|stageShape1,/parent2/cone1', f="TestIsolateSelection.replace") + + visible.clear() + notVisible = scene.copy() + + for p in ['|group1', '|group1|pCube1', '|group1|pCube1|pCubeShape1', + '|stage1|stageShape1,/parent2', + '|stage1|stageShape1,/parent2/cone1']: + visible.append(p) + notVisible.remove(p) + + self.assertVisibility(visible, notVisible) + + # Clear the isolate selection to avoid affecting other tests. + cmds.mayaHydraCppTest(vpPanel, f="TestIsolateSelection.clear") + + # To test multi-viewport behavior we would have wanted to test scene index + # prim visibility for each viewport, and demonstrate per-viewport + # visibility. Unfortunately, tracing demonstrates that we can't obtain + # scene index prim visibility in one viewport before a draw is performed in + # another viewport. + # + # Since visibility is according to the last viewport drawn, and don't know + # of way to control order of viewport draw, this testing strategy fails. + # For example, consider drawing modelPanel4, then modelPanel1: + # + #====================================================================== + # Re-using existing scene index chain to render modelPanel4 + # found isolate selection 0000022DD5557B30 for viewport ID modelPanel4 + # Re-using isolate selection 0000022DD5557B30 + # Rendering destination is modelPanel1 + # Re-using existing scene index chain to render modelPanel1 + # found isolate selection 0000022E05EC5740 for viewport ID modelPanel1 + # Switching scene index to isolate selection 0000022E05EC5740 + # IsolateSelectSceneIndex::SetViewport() called for new viewport modelPanel1. + # Old viewport was modelPanel4. + # Old selection is 0000022DD5557B30, new selection is 0000022E05EC5740. + # modelPanel4: examining /MayaHydraViewportRenderer/rprims/Lighted/pSphere1 for isolate select dirtying. + # [...] + # [Dirtying to bring objects invisible in modelPanel4 into modelPanel1] + # [...] + # [Multiple GetPrim() calls for modelPanel1 which all succeed because the + # isolate selection for modelPanel1 is empty] + # IsolateSelectSceneIndex::GetPrim(/MayaHydraViewportRenderer/rprims/pCone1/pConeShape1/DormantPolyWire_58) called for viewport modelPanel1. + # [...] + # Rendering destination is modelPanel1 + # Re-using existing scene index chain to render modelPanel1 + # found isolate selection 0000022E05EC5740 for viewport ID modelPanel1 + # Re-using isolate selection 0000022E05EC5740 + # + # [For an unknown reason we switch back to rendering modelPanel4.] + # + # Rendering destination is modelPanel4 + # Re-using existing scene index chain to render modelPanel4 + # found isolate selection 0000022DD5557B30 for viewport ID modelPanel4 + # Switching scene index to isolate selection 0000022DD5557B30 + # IsolateSelectSceneIndex::SetViewport() called for new viewport modelPanel4. + # Old viewport was modelPanel1. + # Old selection is 0000022E05EC5740, new selection is 0000022DD5557B30. + #====================================================================== + # + # And at this point if we ask for visibility we'll get the modelPanel4 + # visibility, rather than the desired modelPanel1 visibility. + # + # We may have to resort to image comparison to test this. + + def test_isolateSelectMultiViewport(self): + scene = self.setupScene() + with PluginLoaded('mayaHydraCppTests'): + + # We start in single viewport mode. Set an isolate selection there. + cmds.mayaHydraCppTest('modelPanel4', '|group1', '|stage1|stageShape1,/parent1/cylinder1', f="TestIsolateSelection.replace") + + notVisible = scene.copy() + visible = ['|group1', + '|group1|pSphere1', + '|group1|pSphere1|pSphereShape1', + '|group1|pCube1', + '|group1|pCube1|pCubeShape1', + '|stage1|stageShape1,/parent1', + '|stage1|stageShape1,/parent1/cylinder1'] + for p in visible: + notVisible.remove(p) + + self.assertVisibility(visible, notVisible) + + # Switch to four-up viewport mode. Set the renderer in each new + # viewport to be Hydra Storm. Viewport 4 is already set. + # Everything should be initially visible in viewports 1-3. + cmds.FourViewLayout() + visible = scene.copy() + notVisible.clear() + for i in range(1, 4): + cmds.setFocus('modelPanel'+str(i)) + self.setHdStormRenderer() + # self.assertVisibility(visible, notVisible) + + # Here we would set different isolate selections in each viewport. + + # As a final step clear the isolate selections to avoid affecting + # other tests. + for i in range(1, 5): + modelPanel = 'modelPanel'+str(i) + cmds.setFocus(modelPanel) + cmds.mayaHydraCppTest(modelPanel, f="TestIsolateSelection.clear") + + def test_isolateSelectMultipleStages(self): + scene = self.setupMultiStageScene() + with PluginLoaded('mayaHydraCppTests'): + vpPanel = 'modelPanel4' + cmds.mayaHydraCppTest( + vpPanel, '|group1|pCube1', '|stage1|stageShape1,/parent2/cone1', + '|stage2|stageShape2,/parent1/cylinder1', + f="TestIsolateSelection.replace") + + visible = ['|group1', '|group1|pCube1', '|group1|pCube1|pCubeShape1', + '|stage1|stageShape1,/parent2', + '|stage1|stageShape1,/parent2/cone1', + '|stage2|stageShape2,/parent1', + '|stage2|stageShape2,/parent1/cylinder1'] + notVisible = scene.copy() + + for p in visible: + notVisible.remove(p) + + self.assertVisibility(visible, notVisible) + + # As a final step clear the isolate selection to avoid affecting + # other tests. + cmds.mayaHydraCppTest(vpPanel, f="TestIsolateSelection.clear") + +if __name__ == '__main__': + fixturesUtils.runTests(globals()) diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testPathMapperRegistry.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPathMapperRegistry.cpp index b9c5959003..babda835a7 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/testPathMapperRegistry.cpp +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPathMapperRegistry.cpp @@ -48,6 +48,10 @@ TEST(TestPathMapperRegistry, testRegistry) // Exercise the path mapper registry. auto& r = Fvp::PathMapperRegistry::Instance(); + // For the duration of this test set a null fallback mapper. + auto fbm = r.GetFallbackMapper(); + r.SetFallbackMapper(nullptr); + auto dummy = TestPathMapper::create(); // Can't register for an empty path. @@ -106,4 +110,6 @@ TEST(TestPathMapperRegistry, testRegistry) for (const auto& h : registered) { ASSERT_TRUE(r.Unregister(h)); } + r.SetFallbackMapper(fbm); + ASSERT_EQ(r.GetFallbackMapper(), fbm); } diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.cpp index 10ce9b43f0..c0294c9f41 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.cpp +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.cpp @@ -20,6 +20,10 @@ #include #include +#include +#include +#include + #include #include #include @@ -34,6 +38,9 @@ #include #include +#include +#include + #include #include @@ -513,4 +520,31 @@ void assertSelectionHighlightCorrectness( } } +Fvp::PrimSelections ufePathToPrimSelections(const Ufe::Path& appPath) +{ + Fvp::PrimSelections primSelections; + + auto mapper = Fvp::PathMapperRegistry::Instance().GetMapper(appPath); + + if (!mapper) { + TF_WARN("No registered mapping for path %s, no prim path returned.", Ufe::PathString::string(appPath).c_str()); + } + else { + primSelections = mapper->UfePathToPrimSelections(appPath); + if (primSelections.empty()) { + TF_WARN("Mapping for path %s returned no prim path.", Ufe::PathString::string(appPath).c_str()); + } + } + + return primSelections; +} + +bool visibility(const HdSceneIndexBasePtr& sceneIndex, const SdfPath& primPath) +{ + auto prim = sceneIndex->GetPrim(primPath); + auto handle = HdVisibilitySchema::GetFromParent(prim.dataSource).GetVisibility(); + // If there is no handle the prim is visible. + return (handle ? handle->GetTypedValue(0.0f) : true); +} + } // namespace MAYAHYDRA_NS_DEF diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.h b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.h index e3af995d55..22fe3c2cb4 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.h +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.h @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -29,6 +30,8 @@ #include #include +#include + #include #include @@ -37,6 +40,10 @@ #include #include +UFE_NS_DEF { +class Path; +} + PXR_NAMESPACE_OPEN_SCOPE constexpr double DEFAULT_TOLERANCE = std::numeric_limits::epsilon(); @@ -483,6 +490,23 @@ void assertSelectionHighlightCorrectness( const std::string& selectionHighlightMirrorTag, const PXR_NS::TfToken& leafDisplayStyle); +/** + * @brief Get the prim selections for a given application path. + * + * If an application path corresponds to a scene index prim, this function will + * return one or more prim selections for it. If no such scene index prim + * exists, the return prim selections will be empty. + * + * @param[in] appPath The application path for which prim selections should be returned. + * @return Zero or more prim selections. + */ +Fvp::PrimSelections ufePathToPrimSelections(const Ufe::Path& appPath); + +/** + * @brief Return whether the prim is visible or not. + */ +bool visibility(const PXR_NS::HdSceneIndexBasePtr& sceneIndex, const PXR_NS::SdfPath& primPath); + } // namespace MAYAHYDRA_NS_DEF #endif // MAYAHYDRA_TEST_UTILS_H