From b0db8f0413a7c088171237a2d0356ed0d0ae103b Mon Sep 17 00:00:00 2001 From: ppt-adsk Date: Wed, 11 Dec 2024 15:19:23 -0500 Subject: [PATCH] Selection highlighting, picking of MhFootPrint node, with test. (#228) --- .../footPrintNode/mhFootPrintNode.cpp | 63 ++++++++++++++++++- .../render/mayaToHydra/cpp/testHydraPrim.cpp | 30 +++++++++ .../render/mayaToHydra/testFootPrintNode.py | 35 +++++++++++ 3 files changed, 125 insertions(+), 3 deletions(-) diff --git a/lib/mayaHydra/flowViewportAPIExamples/footPrintNode/mhFootPrintNode.cpp b/lib/mayaHydra/flowViewportAPIExamples/footPrintNode/mhFootPrintNode.cpp index 52e91c4921..bd71856b96 100644 --- a/lib/mayaHydra/flowViewportAPIExamples/footPrintNode/mhFootPrintNode.cpp +++ b/lib/mayaHydra/flowViewportAPIExamples/footPrintNode/mhFootPrintNode.cpp @@ -42,6 +42,14 @@ #include #include #include +#include +#include +#include + +// MayaHydra headers. +#include +#include +#include //Flow viewport headers #include @@ -50,6 +58,7 @@ //Hydra headers #include #include +#include #include #include #include @@ -63,6 +72,40 @@ PXR_NAMESPACE_USING_DIRECTIVE namespace { void nodeAddedToModel(MObject& node, void* clientData); void nodeRemovedFromModel(MObject& node, void* clientData); + +// Pick handler for the footprint node. + +class FootPrintPickHandler : public MayaHydra::PickHandler { +public: + + FootPrintPickHandler(MObject& footPrintObj) : _footPrintObj(footPrintObj) {} + + bool handlePickHit( + const Input& pickInput, Output& pickOutput + ) const override + { + // Foot print parts are not selectable individually: only the complete + // Maya shape object is selectable. + // + // Conceptually we could append a picked Maya object either to the + // classic Maya MSelectionList selection or to the "MayaSelectTool" + // named UFE selection (which provides input for the global selection). + // However, the Maya select context filters out Maya items added to the + // "MayaSelectTool" named UFE selection, so add to the MSelectionList. + MDagPath dagPath; + TF_AXIOM(MDagPath::getAPathTo(_footPrintObj, dagPath) == MS::kSuccess); + pickOutput.mayaSelection.add(dagPath); + const auto& wsPt = pickInput.pickHit.worldSpaceHitPoint; + pickOutput.mayaWorldSpaceHitPts.append(wsPt[0], wsPt[1], wsPt[2]); + + return true; + } + +private: + + MObject _footPrintObj; +}; + } //--------------------------------------------------------------------------- @@ -131,6 +174,8 @@ class MhFootPrint : public MPxLocatorNode MCallbackId _nodeAddedToModelCbId{0}; MCallbackId _nodeRemovedFromModelCbId{0}; + + SdfPath _pathPrefix; }; namespace @@ -409,7 +454,8 @@ namespace { //Trigger a call to compute so that everything is initialized MhFootPrint* footPrintInstance = reinterpret_cast(clientData); footPrintInstance->updateFootPrintPrims(); - footPrintInstance->addedToModelCb(); + // No need to call footPrintInstance->addedToModelCb(), as reading the + // file will add the node to the model. } } @@ -552,7 +598,7 @@ void* MhFootPrint::creator() void MhFootPrint::addedToModelCb() { - static const SdfPath noPrefix = SdfPath::AbsoluteRootPath(); + _pathPrefix = SdfPath(TfStringPrintf("/MhFootPrint_%p", this)); //Add the callback when an attribute of this node changes MObject obj = thisMObject(); @@ -560,11 +606,22 @@ void MhFootPrint::addedToModelCb() //Data producer scene index interface is used to add the retained scene index to all viewports with all render delegates auto& dataProducerSceneIndexInterface = Fvp::DataProducerSceneIndexInterface::get(); - dataProducerSceneIndexInterface.addDataProducerSceneIndex(_retainedSceneIndex, noPrefix, (void*)&obj, FvpViewportAPITokens->allViewports,FvpViewportAPITokens->allRenderers); + dataProducerSceneIndexInterface.addDataProducerSceneIndex(_retainedSceneIndex, _pathPrefix, (void*)&obj); + + // Register a pick handler for our prefix with the pick handler registry. + auto pickHandler = std::make_shared(obj); + TF_AXIOM(MayaHydra::PickHandlerRegistry::Instance().Register(_pathPrefix, pickHandler)); + + // No need for a path mapper: the parts of the footprint are not selectable + // individually, only the Maya shape, so the built-in Maya path mapper does + // the job of path mapping for the footprint node. } void MhFootPrint::removedFromModelCb() { + // Unregister our pick handler. + TF_AXIOM(MayaHydra::PickHandlerRegistry::Instance().Unregister(_pathPrefix)); + //Remove the callback if (_cbAttributeChangedId){ CHECK_MSTATUS(MMessage::removeCallback(_cbAttributeChangedId)); diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testHydraPrim.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testHydraPrim.cpp index 533d344f3d..ab95400283 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/testHydraPrim.cpp +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testHydraPrim.cpp @@ -20,8 +20,12 @@ #include #include +#include + #include +#include + PXR_NAMESPACE_USING_DIRECTIVE using namespace MayaHydra; @@ -108,3 +112,29 @@ TEST(TestHydraPrim, translation) constexpr double epsilon{1e-7}; ASSERT_TRUE(GfIsClose(primTranslation, expectedTranslation, epsilon)); } + +TEST(TestHydraPrim, countPrims) +{ + const auto& sceneIndices = GetTerminalSceneIndices(); + auto siRoot = sceneIndices.front(); + + auto [argc, argv] = getTestingArgs(); + ASSERT_EQ(argc, 2); + + const std::regex r{argv[0]}; + std::smatch m; + const int expectedNbMatches{std::stoi(argv[1])}; + int nbMatches{0}; + + for (const auto& primPath : + HdSceneIndexPrimView(siRoot, SdfPath::AbsoluteRootPath())) { + // Can't match temporary string, see + // https://stackoverflow.com/questions/27391016 + const std::string element = primPath.GetElementString(); + if (std::regex_search(element, m, r)) { + ++nbMatches; + } + } + + ASSERT_EQ(nbMatches, expectedNbMatches); +} diff --git a/test/lib/mayaUsd/render/mayaToHydra/testFootPrintNode.py b/test/lib/mayaUsd/render/mayaToHydra/testFootPrintNode.py index 7f49a30eb0..e935b75e0c 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/testFootPrintNode.py +++ b/test/lib/mayaUsd/render/mayaToHydra/testFootPrintNode.py @@ -34,6 +34,8 @@ class TestFootPrintNode(mtohUtils.MayaHydraBaseTestCase): #Subclassing mtohUtils IMAGE_DIFF_FAIL_PERCENT = 0.1 imageVersion = None + _requiredPlugins = ['mayaHydraCppTests'] + @classmethod def setUpClass(cls): super(TestFootPrintNode, cls).setUpClass() @@ -257,5 +259,38 @@ def test_Load(self): #using imageVersion as the color is different for this image under usd 24.11+ self.assertSnapshotClose("loadingFootPrintScene.png", self.IMAGE_DIFF_FAIL_THRESHOLD, self.IMAGE_DIFF_FAIL_PERCENT, self.imageVersion) + # Test selection highlighting. When the footprint node is selected, only + # its two Hydra scene index prims (heel and sole) should have a + # _SelectionHighlight mirror hierarchy, and no other prim. + def test_selectionHighlight(self): + with PluginLoaded('mayaHydraFootPrintNode'): + # Create a cube. + cmds.polyCube() + + cmds.refresh() + + # Create a footprint node. It will be selected. + cmds.createNode('MhFootPrint') + + cmds.refresh() + + # Traverse the scene starting at the root, and count the number + # of _SelectionHighlight mirror hierarchies. There should be 2, + # one for the heel and one for the sole of the footprint node. + cmds.mayaHydraCppTest('.*_SelectionHighlight', 2, f="TestHydraPrim.countPrims") + + # Test picking. Once picked, the footprint node must appear in the global + # selection. + def test_picking(self): + with PluginLoaded('mayaHydraFootPrintNode'): + + cmds.createNode('MhFootPrint') + + cmds.refresh() + + cmds.mayaHydraCppTest('|transform1|MhFootPrint1', f="TestUsdPicking.pickPrim") + + self.assertEqual(['MhFootPrint1'], cmds.ls(sl=True)) + if __name__ == '__main__': fixturesUtils.runTests(globals())