Skip to content

Commit

Permalink
Selection highlighting, picking of MhFootPrint node, with test. (#228)
Browse files Browse the repository at this point in the history
  • Loading branch information
ppt-adsk authored Dec 11, 2024
1 parent 1687eaf commit b0db8f0
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@
#include <maya/MGlobal.h>
#include <maya/MFnDagNode.h>
#include <maya/MModelMessage.h>
#include <maya/MDagPath.h>
#include <maya/MSelectionList.h>
#include <maya/MPointArray.h>

// MayaHydra headers.
#include <mayaHydraLib/pick/mhPickHandler.h>
#include <mayaHydraLib/pick/mhPickHandlerRegistry.h>
#include <ufeExtensions/Global.h>

//Flow viewport headers
#include <flowViewport/API/fvpVersionInterface.h>
Expand All @@ -50,6 +58,7 @@
//Hydra headers
#include <pxr/base/vt/array.h>
#include <pxr/base/gf/vec3f.h>
#include <pxr/imaging/hdx/pickTask.h>
#include <pxr/imaging/hd/tokens.h>
#include <pxr/imaging/hd/retainedSceneIndex.h>
#include <pxr/imaging/hd/retainedDataSource.h>
Expand All @@ -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;
};

}

//---------------------------------------------------------------------------
Expand Down Expand Up @@ -131,6 +174,8 @@ class MhFootPrint : public MPxLocatorNode

MCallbackId _nodeAddedToModelCbId{0};
MCallbackId _nodeRemovedFromModelCbId{0};

SdfPath _pathPrefix;
};

namespace
Expand Down Expand Up @@ -409,7 +454,8 @@ namespace {
//Trigger a call to compute so that everything is initialized
MhFootPrint* footPrintInstance = reinterpret_cast<MhFootPrint*>(clientData);
footPrintInstance->updateFootPrintPrims();
footPrintInstance->addedToModelCb();
// No need to call footPrintInstance->addedToModelCb(), as reading the
// file will add the node to the model.
}
}

Expand Down Expand Up @@ -552,19 +598,30 @@ 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();
_cbAttributeChangedId = MNodeMessage::addAttributeChangedCallback(obj, attributeChangedCallback, ((void*)this));

//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<FootPrintPickHandler>(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));
Expand Down
30 changes: 30 additions & 0 deletions test/lib/mayaUsd/render/mayaToHydra/cpp/testHydraPrim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@
#include <ufe/path.h>
#include <ufe/pathString.h>

#include <pxr/imaging/hd/sceneIndexPrimView.h>

#include <gtest/gtest.h>

#include <regex>

PXR_NAMESPACE_USING_DIRECTIVE

using namespace MayaHydra;
Expand Down Expand Up @@ -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);
}
35 changes: 35 additions & 0 deletions test/lib/mayaUsd/render/mayaToHydra/testFootPrintNode.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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())

0 comments on commit b0db8f0

Please sign in to comment.