diff --git a/lib/flowViewport/API/interfacesImp/fvpDataProducerSceneIndexInterfaceImp.cpp b/lib/flowViewport/API/interfacesImp/fvpDataProducerSceneIndexInterfaceImp.cpp index 12eb5aaf16..30a461d070 100644 --- a/lib/flowViewport/API/interfacesImp/fvpDataProducerSceneIndexInterfaceImp.cpp +++ b/lib/flowViewport/API/interfacesImp/fvpDataProducerSceneIndexInterfaceImp.cpp @@ -250,7 +250,7 @@ void DataProducerSceneIndexInterfaceImp::_AddDataProducerSceneIndexToThisViewpor //Add it to the merging scene index if the render inex proxy is present, it may happen that it will be set later auto renderIndexProxy = viewportInformationAndSceneIndicesPerViewportData->GetRenderIndexProxy(); if (renderIndexProxy && dataProducerSceneIndexData && dataProducerSceneIndexData->GetDataProducerLastSceneIndexChain()){ - renderIndexProxy->InsertSceneIndex(dataProducerSceneIndexData->GetDataProducerLastSceneIndexChain(), SdfPath::AbsoluteRootPath()); + renderIndexProxy->InsertSceneIndex(dataProducerSceneIndexData->GetDataProducerLastSceneIndexChain(), dataProducerSceneIndexData->GetPrefix()); } } diff --git a/lib/flowViewport/API/interfacesImp/fvpFilteringSceneIndexInterfaceImp.cpp b/lib/flowViewport/API/interfacesImp/fvpFilteringSceneIndexInterfaceImp.cpp index 6631138a2a..7029f3db9b 100644 --- a/lib/flowViewport/API/interfacesImp/fvpFilteringSceneIndexInterfaceImp.cpp +++ b/lib/flowViewport/API/interfacesImp/fvpFilteringSceneIndexInterfaceImp.cpp @@ -97,7 +97,8 @@ bool FilteringSceneIndexInterfaceImp::_CreateSceneFilteringSceneIndicesData(cons auto findResult = std::find_if(sceneFilteringSceneIndicesData().cbegin(), sceneFilteringSceneIndicesData().cend(), [&client](const PXR_NS::FVP_NS_DEF::FilteringSceneIndexDataBaseRefPtr& filteringSIData) { return filteringSIData->getClient() == client;}); - if (findResult != sceneFilteringSceneIndicesData().cend()){ + if (!TF_VERIFY(findResult == sceneFilteringSceneIndicesData().cend(), + "Filtering scene index client already found in FilteringSceneIndexInterfaceImp::_CreateSceneFilteringSceneIndicesData()")){ return false; } diff --git a/lib/flowViewport/API/samples/fvpDataProducerSceneIndexExample.cpp b/lib/flowViewport/API/samples/fvpDataProducerSceneIndexExample.cpp index 56a0e22eec..e562c6cecc 100644 --- a/lib/flowViewport/API/samples/fvpDataProducerSceneIndexExample.cpp +++ b/lib/flowViewport/API/samples/fvpDataProducerSceneIndexExample.cpp @@ -125,11 +125,13 @@ namespace PrototypeInstancing namespace FVP_NS_DEF { DataProducerSceneIndexExample::DataProducerSceneIndexExample() : - _cubeRootPath(SdfPath(TfStringPrintf("/cube_%p", this))),//Is the root path for the cubes - _instancerPath(SdfPath(TfStringPrintf("/instancer_%p", this)))//Is the instancer path when using instancing + _cubeRootPath(SdfPath("/cube_")),//Is the root path for the cubes + _instancerPath(SdfPath("/instancer_"))//Is the instancer path when using instancing { //Create the HdRetainedSceneIndex to be able to easily add primitives _retainedSceneIndex = HdRetainedSceneIndex::New(); + + _retainedSceneIndex->SetDisplayName("Flow Viewport Data Producer Example Scene Index"); //Add all primitives _AddAllPrims(); @@ -165,10 +167,10 @@ void DataProducerSceneIndexExample::getPrimsBoundingBox(float& corner1X, float& {-_currentCubeGridParams._halfSize, -_currentCubeGridParams._halfSize, -_currentCubeGridParams._halfSize}, { _currentCubeGridParams._halfSize, _currentCubeGridParams._halfSize, _currentCubeGridParams._halfSize} ); - const GfBBox3d cubeInitialBBox (cubeRange, _currentCubeGridParams._initalTransform); + const GfBBox3d cubeInitialBBox (cubeRange, _currentCubeGridParams._initialTransform); //Init some variables before looping - const GfVec3d initTrans = _currentCubeGridParams._initalTransform.ExtractTranslation(); + const GfVec3d initTrans = _currentCubeGridParams._initialTransform.ExtractTranslation(); const int numLevelsX = _currentCubeGridParams._numLevelsX; const int numLevelsY = _currentCubeGridParams._numLevelsY; const int numLevelsZ = _currentCubeGridParams._numLevelsZ; @@ -178,18 +180,18 @@ void DataProducerSceneIndexExample::getPrimsBoundingBox(float& corner1X, float& //Combine the AABB of each cube prim of the 3D grid of Hydra cubes primitives for (int z = 0; z < numLevelsZ; ++z) { - for (int y = 0; y < numLevelsY; ++y) { - for (int x = 0; x < numLevelsX; ++x) { + for (int y = 0; y < numLevelsY; ++y) { + for (int x = 0; x < numLevelsX; ++x) { //Update translation, by getting the initial transform - GfMatrix4d currentXForm = _currentCubeGridParams._initalTransform; + GfMatrix4d currentXForm = _currentCubeGridParams._initialTransform; //And updating the translation only in the matrix, _currentCubeGridParams._deltaTrans holds the number of units to separate the cubes in the grid in X, Y, and Z. currentXForm.SetTranslateOnly(initTrans + (GfCompMult(_currentCubeGridParams._deltaTrans, GfVec3f(x,y,z)))); const GfBBox3d currentCubeAABB (cubeRange, currentXForm); combinedAABBox = GfBBox3d::Combine(currentCubeAABB, combinedAABBox); - } - } - } + } + } + } //Get resulting AABB const GfRange3d resultedAABB = combinedAABBox.ComputeAlignedRange(); @@ -227,14 +229,14 @@ void DataProducerSceneIndexExample::_AddAllPrimsWithInstancing() //Copy the main cube primitive in the array, we will update only the SdfPath and the transform, all others attributes are identical HdRetainedSceneIndex::AddedPrimEntry cubePrimEntry = _CreateCubePrim(_cubeRootPath, _currentCubeGridParams._halfSize, _currentCubeGridParams._color, _currentCubeGridParams._opacity, - _currentCubeGridParams._initalTransform, instancing); + _currentCubeGridParams._initialTransform, instancing); //Add the cube to the retained scene index _retainedSceneIndex->AddPrims({cubePrimEntry}); const size_t totalSize = _currentCubeGridParams._numLevelsX * _currentCubeGridParams._numLevelsY * _currentCubeGridParams._numLevelsZ; - const GfVec3d initTrans{0,0,0};// = _currentCubeGridParams._initalTransform.ExtractTranslation(); + const GfVec3d initTrans{0,0,0};// = _currentCubeGridParams._initialTransform.ExtractTranslation(); const int numLevelsX = _currentCubeGridParams._numLevelsX; const int numLevelsY = _currentCubeGridParams._numLevelsY; const int numLevelsZ = _currentCubeGridParams._numLevelsZ; @@ -246,11 +248,11 @@ void DataProducerSceneIndexExample::_AddAllPrimsWithInstancing() //Create matrices array tbb::parallel_for(tbb::blocked_range3d(0, numLevelsZ, 0, numLevelsY, 0, numLevelsX), - [&](const tbb::blocked_range3d& r) - { - for (int z = r.pages().begin(), z_end = r.pages().end(); z < z_end; ++z) { - for (int y = r.rows().begin(), y_end = r.rows().end(); y < y_end; ++y) { - for (int x = r.cols().begin(), x_end = r.cols().end(); x < x_end; ++x) { + [&](const tbb::blocked_range3d& r) + { + for (int z = r.pages().begin(), z_end = r.pages().end(); z < z_end; ++z) { + for (int y = r.rows().begin(), y_end = r.rows().end(); y < y_end; ++y) { + for (int x = r.cols().begin(), x_end = r.cols().end(); x < x_end; ++x) { const size_t index = x + (numLevelsX * y) + (numLevelsX * numLevelsY * z); @@ -259,11 +261,11 @@ void DataProducerSceneIndexExample::_AddAllPrimsWithInstancing() //Update translation matrices[index].SetTranslate(initTrans + (GfCompMult(_currentCubeGridParams._deltaTrans, GfVec3f(x,y,z)))); - } - } - } - } - ); + } + } + } + } + ); //Add new prims to the scene index PrototypeInstancing::createInstancer(_instancerPath, _cubeRootPath, prototypeIndices, matrices, _retainedSceneIndex); @@ -271,61 +273,82 @@ void DataProducerSceneIndexExample::_AddAllPrimsWithInstancing() void DataProducerSceneIndexExample::_AddAllPrimsNoInstancing() { - static const bool instancing = false; + constexpr bool instancing = false; - //Arrays of added prims - HdRetainedSceneIndex::AddedPrimEntries addedPrims; - - //Copy the main cube primitive in the array, we will update only the SdfPath and the transform of the other cubes created, all others attributes are identical. - HdRetainedSceneIndex::AddedPrimEntry cubePrimEntry = _CreateCubePrim(_cubeRootPath, _currentCubeGridParams._halfSize, - _currentCubeGridParams._color, _currentCubeGridParams._opacity, - _currentCubeGridParams._initalTransform, instancing); - - //We build a 3D grid of cubes - const size_t totalSize = _currentCubeGridParams._numLevelsX * _currentCubeGridParams._numLevelsY * _currentCubeGridParams._numLevelsZ; + //Readability shorthand. + const auto& cgp = _currentCubeGridParams; - //Resize the addedPrims array to the exact size of the number of cube primitives we want in the grid. - addedPrims.resize(totalSize, cubePrimEntry); + //Array of added prims. We want to fill in our addedPrims vector in + //parallel. Pre-allocate the addedPrims array for the maximum number of + //cube primitives in the grid. Hidden cubes reduce the size of the array. + const size_t maxSize = cgp._numLevelsX * cgp._numLevelsY * cgp._numLevelsZ; + HdRetainedSceneIndex::AddedPrimEntries addedPrims{maxSize}; + std::atomic nbEntries; //Init some variables before looping - const std::string cubeRootString = _cubeRootPath.GetString(); - const GfVec3d initTrans = _currentCubeGridParams._initalTransform.ExtractTranslation(); - const int numLevelsX = _currentCubeGridParams._numLevelsX; - const int numLevelsY = _currentCubeGridParams._numLevelsY; - const int numLevelsZ = _currentCubeGridParams._numLevelsZ; + const std::string cubeRootString = _cubeRootPath.GetName(); + const GfVec3d initTrans = cgp._initialTransform.ExtractTranslation(); + const int numLevelsX = cgp._numLevelsX; + const int numLevelsY = cgp._numLevelsY; + const int numLevelsZ = cgp._numLevelsZ; //Create the 3D grid of cubes primitives in Hydra tbb::parallel_for(tbb::blocked_range3d(0, numLevelsZ, 0, numLevelsY, 0, numLevelsX), - [&](const tbb::blocked_range3d& r) - { - for (int z = r.pages().begin(), z_end = r.pages().end(); z < z_end; ++z) { - for (int y = r.rows().begin(), y_end = r.rows().end(); y < y_end; ++y) { - for (int x = r.cols().begin(), x_end = r.cols().end(); x < x_end; ++x) { + [&](const tbb::blocked_range3d& r) + { + for (int z = r.pages().begin(), z_end = r.pages().end(); z < z_end; ++z) { + for (int y = r.rows().begin(), y_end = r.rows().end(); y < y_end; ++y) { + for (int x = r.cols().begin(), x_end = r.cols().end(); x < x_end; ++x) { + + //Compute the prim path so that all cube prim path are unique + std::string cubePathStr( + cubeRootString + std::to_string(x) + + "_" + std::to_string(y) + "_" + std::to_string(z)); + + //If cube is hidden, skip to the next one. + if (cgp._hidden.count(cubePathStr) > 0) { + continue; + } - //Update translation, by getting the initial transform - GfMatrix4d currentXForm = _currentCubeGridParams._initalTransform; - //And updating the translation only in the matrix, _currentCubeGridParams._deltaTrans holds the number of units to separate the cubes in the grid in X, Y, and Z. - currentXForm.SetTranslateOnly(initTrans + (GfCompMult(_currentCubeGridParams._deltaTrans, GfVec3f(x,y,z)))); + int ndx = nbEntries++; + HdRetainedSceneIndex::AddedPrimEntry& cubePrimEntry = + addedPrims[ndx]; + // Prims added to retained scene index must have + // absolute path, otherwise infinite recursion. + auto cubePath = SdfPath::AbsoluteRootPath().AppendChild(TfToken(cubePathStr)); + cubePrimEntry = _CreateCubePrim( + cubePath, cgp._halfSize, cgp._color, + cgp._opacity, cgp._initialTransform, instancing); - //Update information at the right place in the array - HdRetainedSceneIndex::AddedPrimEntry& currentCubePrimEntry = addedPrims[x + (numLevelsX * y) + (numLevelsX * numLevelsY * z)]; + //Update translation, by getting the initial transform + GfMatrix4d currentXForm = cgp._initialTransform; + + //Is the cube in set of transformed cubes? Currently + //only supporting translation, so add the translation + //to the transform if appropriate. + auto cubeTrans = initTrans; + auto found = cgp._transformed.find(cubePathStr); + if (found != cgp._transformed.end()) { + cubeTrans += found->second; + } - //Update the prim path so that all cube prim path are unique - currentCubePrimEntry.primPath = SdfPath( cubeRootString + std::to_string(x) + - std::string("_") + std::to_string(y)+ - std::string("_") + std::to_string(z)); + //And updating the translation only in the matrix, _currentCubeGridParams._deltaTrans holds the number of units to separate the cubes in the grid in X, Y, and Z. + currentXForm.SetTranslateOnly(cubeTrans + (GfCompMult(cgp._deltaTrans, GfVec3f(x,y,z)))); //Update the matrix in the data source for this cube prim - currentCubePrimEntry.dataSource = HdContainerDataSourceEditor(currentCubePrimEntry.dataSource) + cubePrimEntry.dataSource = HdContainerDataSourceEditor(cubePrimEntry.dataSource) .Set(HdXformSchema::GetDefaultLocator(), HdXformSchema::Builder().SetMatrix(HdRetainedTypedSampledDataSource::New(currentXForm)) .Build()) .Finish(); - } - } - } - } - ); + } + } + } + } + ); + + //All done, bring the added entries vector down to size. + addedPrims.resize(nbEntries); //Add all the cube prims to the retained scene index _retainedSceneIndex->AddPrims(addedPrims); @@ -363,11 +386,11 @@ void DataProducerSceneIndexExample::_RemoveAllPrimsNoInstancing() //Remove cube primitives in Hydra tbb::parallel_for(tbb::blocked_range3d(0, numLevelsZ, 0, numLevelsY, 0, numLevelsX), - [&](const tbb::blocked_range3d& r) - { - for (int z = r.pages().begin(), z_end = r.pages().end(); z < z_end; ++z) { - for (int y = r.rows().begin(), y_end = r.rows().end(); y < y_end; ++y) { - for (int x = r.cols().begin(), x_end = r.cols().end(); x < x_end; ++x) { + [&](const tbb::blocked_range3d& r) + { + for (int z = r.pages().begin(), z_end = r.pages().end(); z < z_end; ++z) { + for (int y = r.rows().begin(), y_end = r.rows().end(); y < y_end; ++y) { + for (int x = r.cols().begin(), x_end = r.cols().end(); x < x_end; ++x) { //Get the SdfPath from the cube prim at that place in the 3D grid. It's the same way as when we create the cube prim, see DataProducerSceneIndexExample::AddAllPrimsNoInstancing() const SdfPath currentCubePath(cubeRootString + std::to_string(x) + std::string("_") + std::to_string(y)+ std::string("_") + std::to_string(z)); @@ -471,14 +494,7 @@ HdRetainedSceneIndex::AddedPrimEntry DataProducerSceneIndexExample::_CreateCubeP HdPrimvarSchema::Builder() .SetPrimvarValue( HdRetainedTypedSampledDataSource::New( - VtFloatArray{ - opacity, opacity, opacity, opacity,//Is a value per face vertex (quads) - opacity, opacity, opacity, opacity, - opacity, opacity, opacity, opacity, - opacity, opacity, opacity, opacity, - opacity, opacity, opacity, opacity, - opacity, opacity, opacity, opacity - })) + VtFloatArray(24, opacity))) //Is a value per face vertex (quads) .SetInterpolation( HdPrimvarSchema::BuildInterpolationDataSource( HdPrimvarSchemaTokens->faceVarying)) @@ -554,11 +570,10 @@ HdRetainedSceneIndex::AddedPrimEntry DataProducerSceneIndexExample::_CreateCubeP return addedPrim; } -void DataProducerSceneIndexExample::addDataProducerSceneIndex() +void DataProducerSceneIndexExample::addDataProducerSceneIndex(const PXR_NS::SdfPath& prefix) { if (!_dataProducerSceneIndexAdded && _hydraInterface){ - SdfPath inoutPrefix = PXR_NS::SdfPath::AbsoluteRootPath(); - const bool res = _hydraInterface->addDataProducerSceneIndex(_retainedSceneIndex, inoutPrefix, _containerNode, PXR_NS::FvpViewportAPITokens->allViewports, PXR_NS::FvpViewportAPITokens->allRenderers); + const bool res = _hydraInterface->addDataProducerSceneIndex(_retainedSceneIndex, prefix, _containerNode, PXR_NS::FvpViewportAPITokens->allViewports, PXR_NS::FvpViewportAPITokens->allRenderers); if (false == res){ TF_CODING_ERROR("_hydraInterface->addDataProducerSceneIndex returned false !"); } diff --git a/lib/flowViewport/API/samples/fvpDataProducerSceneIndexExample.h b/lib/flowViewport/API/samples/fvpDataProducerSceneIndexExample.h index e30c45feb1..ced23558ad 100644 --- a/lib/flowViewport/API/samples/fvpDataProducerSceneIndexExample.h +++ b/lib/flowViewport/API/samples/fvpDataProducerSceneIndexExample.h @@ -24,10 +24,14 @@ #include #include #include +#include #include #include #include +#include +#include + namespace FVP_NS_DEF { /** This class is an example on how to add Hydra primitives into a Hydra viewport. @@ -67,17 +71,7 @@ class FVP_API DataProducerSceneIndexExample struct CubeGridCreationParams { /// Constructor - CubeGridCreationParams(){ - _numLevelsX = 10;//Default values - _numLevelsY = 10; - _numLevelsZ = 1; - _halfSize = 2.0; - _color = {0.f, 1.0f, 0.0f}; - _opacity = 0.8; - _deltaTrans = {5.0f, 5.0f, 5.0f}; - _initalTransform.SetIdentity(); - _useInstancing = false; - } + CubeGridCreationParams() = default; //Comparison operator bool operator == (const CubeGridCreationParams& other)const{ @@ -87,9 +81,11 @@ class FVP_API DataProducerSceneIndexExample (_halfSize == other._halfSize) && (_color == other._color) && (_opacity == other._opacity) && - (_initalTransform == other._initalTransform)&& + (_initialTransform == other._initialTransform)&& (_deltaTrans == other._deltaTrans) && - (_useInstancing == other._useInstancing); + (_useInstancing == other._useInstancing) && + (_hidden == other._hidden) && + (_transformed == other._transformed); } /// Number of X levels for the 3D grid of cube primitives @@ -105,7 +101,7 @@ class FVP_API DataProducerSceneIndexExample /// Opacity of each cube in the 3D grid. double _opacity{0.8}; /// Initial transform of each cube in the 3D grid. - PXR_NS::GfMatrix4d _initalTransform; + PXR_NS::GfMatrix4d _initialTransform{1.0}; /** _deltaTrans.x is the space between 2 cubes on the X axis of the 3D grid. * _deltaTrans.y is the space between 2 cubes on the Y axis of the 3D grid. * _deltaTrans.z is the space between 2 cubes on the Z axis of the 3D grid. @@ -113,13 +109,20 @@ class FVP_API DataProducerSceneIndexExample PXR_NS::GfVec3f _deltaTrans{5.0f, 5.0f, 5.0f}; /// if _useInstancing is true, then we are using Hydra instancing to create the cube primitives, if it is false then we are not using Hydra instancing. bool _useInstancing{false}; + + // Hidden cubes, indexed by name, when not using instancing. + std::set _hidden{}; + + // Transformed cubes, indexed by name, when not using instancing. + // At time of writing (31-May-2024) only translation is supported. + std::map _transformed{}; }; ///Set the CubeGridCreationParams void setCubeGridParams(const CubeGridCreationParams& params); ///Call FlowViewport::DataProducerSceneIndexInterface::_AddDataProducerSceneIndex to add our data producer scene index to create the 3D grid of cubes - void addDataProducerSceneIndex(); + void addDataProducerSceneIndex(const PXR_NS::SdfPath& prefix = PXR_NS::SdfPath::AbsoluteRootPath()); ///Call the FlowViewport::DataProducerSceneIndexInterface::RemoveViewportDataProducerSceneIndex to remove our data producer scene index from the Hydra viewport void removeDataProducerSceneIndex(); @@ -190,4 +193,4 @@ class FVP_API DataProducerSceneIndexExample } //end of namespace FVP_NS_DEF { -#endif //FLOW_VIEWPORT_EXAMPLES_DATA_PRODUCER_SCENE_INDEX_EXAMPLE_H \ No newline at end of file +#endif //FLOW_VIEWPORT_EXAMPLES_DATA_PRODUCER_SCENE_INDEX_EXAMPLE_H diff --git a/lib/flowViewport/fvpUtils.cpp b/lib/flowViewport/fvpUtils.cpp index b82b1466a6..1ddc1ec73b 100644 --- a/lib/flowViewport/fvpUtils.cpp +++ b/lib/flowViewport/fvpUtils.cpp @@ -15,6 +15,8 @@ #include "fvpUtils.h" +#include + namespace FVP_NS_DEF { #ifdef CODE_COVERAGE_WORKAROUND @@ -30,4 +32,11 @@ void leakSceneIndex(const PXR_NS::HdSceneIndexBaseRefPtr& si) { } #endif +PXR_NS::HdDataSourceBaseHandle createFullySelectedDataSource() +{ + PXR_NS::HdSelectionSchema::Builder selectionBuilder; + selectionBuilder.SetFullySelected(PXR_NS::HdRetainedTypedSampledDataSource::New(true)); + return PXR_NS::HdDataSourceBase::Cast(selectionBuilder.Build()); +} + } // namespace FVP_NS_DEF diff --git a/lib/flowViewport/fvpUtils.h b/lib/flowViewport/fvpUtils.h index a2073c245a..2e349f303f 100644 --- a/lib/flowViewport/fvpUtils.h +++ b/lib/flowViewport/fvpUtils.h @@ -89,6 +89,8 @@ class PrimvarDataSource final : public PXR_NS::HdContainerDataSource PXR_NS::TfToken _role; }; +PXR_NS::HdDataSourceBaseHandle FVP_API createFullySelectedDataSource(); + } // namespace FVP_NS_DEF #endif // FVP_UTILS_H diff --git a/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.cpp b/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.cpp index 195246054a..eecac7aaf1 100644 --- a/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.cpp +++ b/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.cpp @@ -39,6 +39,8 @@ #include "flowViewport/sceneIndex/fvpSelectionSceneIndex.h" #include "flowViewport/sceneIndex/fvpPathInterface.h" #include "flowViewport/selection/fvpSelection.h" +#include +#include #include "flowViewport/debugCodes.h" @@ -265,7 +267,23 @@ PrimSelections SelectionSceneIndex::UfePathToPrimSelections(const Ufe::Path& app auto primSelections = _inputSceneIndexPathInterface->UfePathToPrimSelections(appPath); if (primSelections.empty()) { - TF_WARN("SelectionSceneIndex::UfePathToPrimSelections(%s) returned no path, Hydra selection will be incorrect", Ufe::PathString::string(appPath).c_str()); + // Path interface of input scene index didn't provide information. + // Try path mapper registry. + auto mapper = Fvp::PathMapperRegistry::Instance().GetMapper(appPath); + + auto warnEmptyPath = [](const Ufe::Path& appPath) { + TF_WARN("SelectionSceneIndex::UfePathToPrimSelections(%s) returned no path, Hydra selection will be incorrect", Ufe::PathString::string(appPath).c_str()); + }; + + if (!mapper) { + warnEmptyPath(appPath); + } + else { + primSelections = mapper->UfePathToPrimSelections(appPath); + if (primSelections.empty()) { + warnEmptyPath(appPath); + } + } } return primSelections; diff --git a/lib/flowViewport/selection/CMakeLists.txt b/lib/flowViewport/selection/CMakeLists.txt index f7ce7d4887..2d986d14c3 100644 --- a/lib/flowViewport/selection/CMakeLists.txt +++ b/lib/flowViewport/selection/CMakeLists.txt @@ -7,6 +7,10 @@ target_sources(${TARGET_NAME} fvpSelectionFwd.cpp fvpSelectionTask.cpp fvpSelectionTracker.cpp + fvpPathMapper.cpp + fvpPathMapperFwd.cpp + fvpPathMapperRegistry.cpp + fvpPrefixPathMapper.cpp ) set(HEADERS @@ -14,6 +18,10 @@ set(HEADERS fvpSelectionFwd.h fvpSelectionTask.h fvpSelectionTracker.h + fvpPathMapper.h + fvpPathMapperFwd.h + fvpPathMapperRegistry.h + fvpPrefixPathMapper.h ) # ----------------------------------------------------------------------------- diff --git a/lib/flowViewport/selection/fvpPathMapper.cpp b/lib/flowViewport/selection/fvpPathMapper.cpp new file mode 100644 index 0000000000..862b91106b --- /dev/null +++ b/lib/flowViewport/selection/fvpPathMapper.cpp @@ -0,0 +1,17 @@ +// +// 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 diff --git a/lib/flowViewport/selection/fvpPathMapper.h b/lib/flowViewport/selection/fvpPathMapper.h new file mode 100644 index 0000000000..647923fcab --- /dev/null +++ b/lib/flowViewport/selection/fvpPathMapper.h @@ -0,0 +1,51 @@ +// +// 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 FVP_PATH_MAPPER_H +#define FVP_PATH_MAPPER_H + +#include +#include +#include + +#include + +#include + +UFE_NS_DEF { +class Path; +} + +namespace FVP_NS_DEF { + +/// \class PathMapper +/// +/// The path handler performs application path to scene index path mapping. +/// +/// This is useful for selection highlighting, where an application selection +/// path is converted to a path to a Hydra scene index prim that must be +/// highlighted. + +class PathMapper : public PathInterface +{ +protected: + + FVP_API + PathMapper() = default; +}; + +} + +#endif diff --git a/lib/flowViewport/selection/fvpPathMapperFwd.cpp b/lib/flowViewport/selection/fvpPathMapperFwd.cpp new file mode 100644 index 0000000000..08e1205976 --- /dev/null +++ b/lib/flowViewport/selection/fvpPathMapperFwd.cpp @@ -0,0 +1,17 @@ +// +// 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 diff --git a/lib/flowViewport/selection/fvpPathMapperFwd.h b/lib/flowViewport/selection/fvpPathMapperFwd.h new file mode 100644 index 0000000000..d32161c51c --- /dev/null +++ b/lib/flowViewport/selection/fvpPathMapperFwd.h @@ -0,0 +1,32 @@ +// +// 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 FVP_SELECTION_PATH_MAPPER_FWD_H +#define FVP_SELECTION_PATH_MAPPER_FWD_H + +#include + +#include + +namespace FVP_NS_DEF { + +class PathMapper; + +using PathMapperPtr = std::shared_ptr; +using PathMapperConstPtr = std::shared_ptr; + +} + +#endif diff --git a/lib/flowViewport/selection/fvpPathMapperRegistry.cpp b/lib/flowViewport/selection/fvpPathMapperRegistry.cpp new file mode 100644 index 0000000000..0ecf953c3a --- /dev/null +++ b/lib/flowViewport/selection/fvpPathMapperRegistry.cpp @@ -0,0 +1,89 @@ +// +// 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 + +namespace { + +Ufe::Trie mappers; + +} + +PXR_NAMESPACE_OPEN_SCOPE +TF_INSTANTIATE_SINGLETON(Fvp::PathMapperRegistry); +PXR_NAMESPACE_CLOSE_SCOPE + +namespace FVP_NS_DEF { + +/* static */ +PathMapperRegistry& PathMapperRegistry::Instance() +{ + return PXR_NS::TfSingleton::GetInstance(); +} + +bool PathMapperRegistry::Register(const Ufe::Path& prefix, const PathMapperConstPtr& pathMapper) +{ + if (prefix.empty() || mappers.containsDescendantInclusive(prefix) || + mappers.containsAncestor(prefix)) { + return false; + } + + mappers.add(prefix, pathMapper); + return true; +} + +bool PathMapperRegistry::Unregister(const Ufe::Path& prefix) +{ + return mappers.remove(prefix) != nullptr; +} + +PathMapperConstPtr PathMapperRegistry::GetMapper(const Ufe::Path& path) const +{ + if (mappers.empty() || path.empty()) { + return nullptr; + } + + // 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 + // path trying to find a trie node with data. + auto trieNode = mappers.root(); + for (const auto& c : path) { + auto child = (*trieNode)[c]; + // 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; + } + trieNode = child; + + // Found a trieNode with data. + if (trieNode->hasData()) { + return trieNode->data(); + } + } + // We reached the end of the parent path without returning true, therefore + // there are no ancestors. + return nullptr; +} + +} diff --git a/lib/flowViewport/selection/fvpPathMapperRegistry.h b/lib/flowViewport/selection/fvpPathMapperRegistry.h new file mode 100644 index 0000000000..171b3d4776 --- /dev/null +++ b/lib/flowViewport/selection/fvpPathMapperRegistry.h @@ -0,0 +1,83 @@ +// +// 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 FVP_PATH_MAPPER_REGISTRY_H +#define FVP_PATH_MAPPER_REGISTRY_H + +#include +#include + +#include +#include + +#include + +UFE_NS_DEF { +class Path; +} + +namespace FVP_NS_DEF { + +/// \class PathMapperRegistry +/// +/// A registry of path mappers that map from an application path to scene index +/// path, indexed by application path. +/// +/// The path mapper registry has the following properties: +/// - 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. + +class PathMapperRegistry { +public: + + FVP_API + static PathMapperRegistry& Instance(); + + //! Register a path mapper to deal with all application paths at or + //! under prefix. + /*! + \return False if an ancestor, descendant, or prefix itself is found in the registry, true otherwise. + */ + FVP_API + bool Register(const Ufe::Path& prefix, const PathMapperConstPtr& pathMapper); + //! Unregister path mapper for prefix. + /*! + \return False if prefix itself was not found in the registry, true otherwise. + */ + FVP_API + bool Unregister(const Ufe::Path& prefix); + + //! 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. + FVP_API + PathMapperConstPtr GetMapper(const Ufe::Path& path) const; + +private: + + PathMapperRegistry() = default; + ~PathMapperRegistry() = default; + PathMapperRegistry(const PathMapperRegistry&) = delete; + PathMapperRegistry& operator=(const PathMapperRegistry&) = delete; + + friend class PXR_NS::TfSingleton; +}; + +} + +#endif diff --git a/lib/flowViewport/selection/fvpPrefixPathMapper.cpp b/lib/flowViewport/selection/fvpPrefixPathMapper.cpp new file mode 100644 index 0000000000..587d149eaa --- /dev/null +++ b/lib/flowViewport/selection/fvpPrefixPathMapper.cpp @@ -0,0 +1,62 @@ +// +// 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 + +// Need Pixar namespace for TF_ diagnostics macros. +PXR_NAMESPACE_USING_DIRECTIVE + +namespace FVP_NS_DEF { + +PrefixPathMapper::PrefixPathMapper( + Ufe::Rtid rtid, + const Ufe::Path& appPathPrefix, + const PXR_NS::SdfPath& sceneIndexPathPrefix +) : _rtid(rtid), _appPathPrefix(appPathPrefix), + _sceneIndexPathPrefix(sceneIndexPathPrefix) +{} + +PrimSelections PrefixPathMapper::UfePathToPrimSelections(const Ufe::Path& appPath) const +{ + // We only handle scene items from our assigned run time ID. + if (appPath.runTimeId() != _rtid) { + return {}; + } + + // If the data model object application path does not match the path we + // translate, return an empty path. + if (!appPath.startsWith(_appPathPrefix)) { + return {}; + } + + // The scene index path is composed of 2 parts, in order: + // 1) The scene index path prefix, which is fixed on construction. + // 2) The second segment of the UFE path, with each UFE path component + // becoming an SdfPath component. + PXR_NS::SdfPath primPath = _sceneIndexPathPrefix; + TF_AXIOM(appPath.nbSegments() == 2); + const auto& secondSegment = appPath.getSegments()[1]; + for (const auto& pathComponent : secondSegment) { + primPath = primPath.AppendChild(TfToken(pathComponent.string())); + } + + auto selectionDataSource = createFullySelectedDataSource(); + + return PrimSelections{{primPath, selectionDataSource}}; +} + +} diff --git a/lib/flowViewport/selection/fvpPrefixPathMapper.h b/lib/flowViewport/selection/fvpPrefixPathMapper.h new file mode 100644 index 0000000000..48c75d16de --- /dev/null +++ b/lib/flowViewport/selection/fvpPrefixPathMapper.h @@ -0,0 +1,58 @@ +// +// 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 FVP_PREFIX_PATH_MAPPER_H +#define FVP_PREFIX_PATH_MAPPER_H + +#include +#include + +#include +#include + +namespace FVP_NS_DEF { + +/// \class PrefixPathMapper +/// +/// This simple path handler performs application path to scene index path +/// mapping by substituting a scene index prefix for an application path prefix. +/// The prefix mapper applies only to application paths of a specific UFE run +/// time. +/// + +class PrefixPathMapper : public PathMapper +{ +public: + + FVP_API + PrefixPathMapper( + Ufe::Rtid rtid, + const Ufe::Path& appPathPrefix, + const PXR_NS::SdfPath& sceneIndexPathPrefix + ); + + FVP_API + PrimSelections UfePathToPrimSelections(const Ufe::Path& appPath) const override; + +private: + + const Ufe::Rtid _rtid{0}; // 0 is invalid + const Ufe::Path _appPathPrefix; + const PXR_NS::SdfPath _sceneIndexPathPrefix; +}; + +} + +#endif diff --git a/lib/mayaHydra/flowViewportAPIExamples/flowViewportAPILocator/mhFlowViewportAPILocator.cpp b/lib/mayaHydra/flowViewportAPIExamples/flowViewportAPILocator/mhFlowViewportAPILocator.cpp index 2c451b3407..f63897942d 100644 --- a/lib/mayaHydra/flowViewportAPIExamples/flowViewportAPILocator/mhFlowViewportAPILocator.cpp +++ b/lib/mayaHydra/flowViewportAPIExamples/flowViewportAPILocator/mhFlowViewportAPILocator.cpp @@ -22,9 +22,17 @@ #include #include #include +#include +#include -//Maya hydra headers +//Maya Hydra headers +#include #include +#include +#include +#include +#include +#include //Maya headers #include @@ -32,9 +40,10 @@ #include #include #include -#include +#include #include #include +#include #include #include #include @@ -43,6 +52,16 @@ #include #include +#include + +#include +#include +#include +#include + +#include +#include + //We use a locator node to deal with creating and filtering hydra primitives as an example. //But you could use another kind of maya plugin. @@ -50,17 +69,15 @@ createNode("MhFlowViewportAPILocator") */ -namespace { -void nodeAddedToModel(MObject& node, void* clientData); -void nodeRemovedFromModel(MObject& node, void* clientData); -} - PXR_NAMESPACE_USING_DIRECTIVE ///Maya Locator node subclass to create filtering and data producer scene indices example, to be used with the flow viewport API. class MhFlowViewportAPILocator : public MPxLocatorNode { public: + using TransformedCubes = std::map; + using HiddenCubes = std::set; + ~MhFlowViewportAPILocator() override; void postConstructor() override; @@ -86,11 +103,18 @@ class MhFlowViewportAPILocator : public MPxLocatorNode static MObject mNumCubeLevelsY; static MObject mNumCubeLevelsZ; static MObject mCubeHalfSize; - static MObject mCubeInitalTransform; + static MObject mCubeInitialTransform; static MObject mCubeColor; static MObject mCubeOpacity; static MObject mCubesUseInstancing; static MObject mCubesDeltaTrans; + static MObject mHiddenCubes; + static MObject mCubeTranslateX; + static MObject mCubeTranslateY; + static MObject mCubeTranslateZ; + static MObject mCubeTranslate; + static MObject mTransformedCubeName; + static MObject mTransformedCubes; ///3D Grid of cube mesh primitives creation parameters for the data producer scene index Fvp::DataProducerSceneIndexExample::CubeGridCreationParams _cubeGridParams; @@ -103,6 +127,31 @@ class MhFlowViewportAPILocator : public MPxLocatorNode // Callback when the footprint node is removed from model (delete) void removedFromModelCb(); + // Get sparse list of hidden cubes. + HiddenCubes hiddenCubes() const; + + // Set sparse list of hidden cubes. + void hideCubes(const HiddenCubes& hidden); + + // Get sparse list of transformed cubes. + TransformedCubes transformedCubes() const; + + // Set cube translation. + void translate(const std::string& cubeName, double x, double y, double z); + + // Get cube translation. + GfVec3d translation(const std::string& cubeName) const; + + GfVec3d deltaTrans() const; + + Ufe::Path getUfePath() const; + + Ufe::Path getCubeUfePath(const std::string& cubeName) const; + static Ufe::Path getCubeUfePath( + const MObject& locatorObj, + const std::string& cubeName + ); + protected: /// _hydraViewportFilteringSceneIndexClientExample is the filtering scene index example for a Hydra viewport scene index. std::shared_ptr _hydraViewportFilteringSceneIndexClientExample; @@ -120,12 +169,77 @@ class MhFlowViewportAPILocator : public MPxLocatorNode MCallbackId _nodeRemovedFromModelCbId{0}; private: + /// Private Constructor MhFlowViewportAPILocator() {} + + // Set the translation for a given cube. + void setTranslatePlug(const MPlug& cubePlug, double x, double y, double z); + + SdfPath _pathPrefix; + Ufe::Path _appPath{}; +}; + +namespace { + +using namespace Ufe; + +constexpr char ufeRunTimeName[] = "FlowViewportAPILocatorRunTime"; +Ufe::Rtid ufeRunTimeId{0}; // 0 is invalid initial UFE run time ID. + +std::set split(const std::string& str) +{ + std::set tokens; + std::stringstream ss(str); + std::string token; + + while( ss >> token) { + tokens.insert(token); + } + return tokens; +} + +// Pick handler for the locator node. + +class LocatorPickHandler : public MayaHydra::PickHandler { +public: + + LocatorPickHandler(MObject& locatorObj) : _locatorObj(locatorObj) {} + + bool handlePickHit( + const Input& pickInput, Output& pickOutput + ) const override + { + auto cubeUfePath = MhFlowViewportAPILocator::getCubeUfePath( + _locatorObj, pickInput.pickHit.objectId.GetName()); + + // Append the picked object to the UFE selection. + auto si = Ufe::Hierarchy::createItem(cubeUfePath); + if (!TF_VERIFY(si)) { + return false; + } + + pickOutput.ufeSelection->append(si); + return true; + } + +private: + + MObject _locatorObj; }; -namespace +MhFlowViewportAPILocator* getLocator(const Ufe::Path& cubePath) { + if (cubePath.size() <= 1) { + return nullptr; + } + auto locatorDagPath = UfeExtensions::ufeToDagPath(cubePath.pop()); + MFnDependencyNode fn(locatorDagPath.node()); + return dynamic_cast(fn.userNode()); +} + + std::string GetStringAttributeValue(const MPlug& plug); + //Callback when an attribute of this Maya node changes void attributeChangedCallback(MNodeMessage::AttributeMessage msg, MPlug& plug, MPlug & otherPlug, void* dataProducerSceneIndexData) { @@ -148,10 +262,10 @@ namespace if (plug == flowViewportAPIMayaLocator->mCubeHalfSize){ flowViewportAPIMayaLocator->_cubeGridParams._halfSize = plug.asDouble(); }else - if (plug == flowViewportAPIMayaLocator->mCubeInitalTransform){ + if (plug == flowViewportAPIMayaLocator->mCubeInitialTransform){ auto dataHandle = plug.asMDataHandle(); const MMatrix mat = dataHandle.asMatrix(); - memcpy(flowViewportAPIMayaLocator->_cubeGridParams._initalTransform.GetArray(), mat[0], sizeof(double) * 16);//convert from MMatrix to GfMatrix4d + memcpy(flowViewportAPIMayaLocator->_cubeGridParams._initialTransform.GetArray(), mat[0], sizeof(double) * 16);//convert from MMatrix to GfMatrix4d }else if (parentPlug == flowViewportAPIMayaLocator->mCubeColor){ auto dataHandle = parentPlug.asMDataHandle();//Using parent plug as this is composed of 3 doubles @@ -186,6 +300,42 @@ namespace flowViewportAPIMayaLocator->_cubeGridParams._deltaTrans.data()[0] = deltaTrans[0];//Implicit conversion from double to float flowViewportAPIMayaLocator->_cubeGridParams._deltaTrans.data()[1] = deltaTrans[1]; flowViewportAPIMayaLocator->_cubeGridParams._deltaTrans.data()[2] = deltaTrans[2]; + }else + if (plug == flowViewportAPIMayaLocator->mHiddenCubes){ + flowViewportAPIMayaLocator->_cubeGridParams._hidden = split(GetStringAttributeValue(plug)); + }else + // Cube transform plugs: the translate plug itself never changes, + // only its x, y, z children. + if (plug == flowViewportAPIMayaLocator->mTransformedCubes || + plug == flowViewportAPIMayaLocator->mTransformedCubeName || + plug == flowViewportAPIMayaLocator->mCubeTranslateX || + plug == flowViewportAPIMayaLocator->mCubeTranslateY || + plug == flowViewportAPIMayaLocator->mCubeTranslateZ) { + + flowViewportAPIMayaLocator->_cubeGridParams._transformed = + flowViewportAPIMayaLocator->transformedCubes(); + + // Notify UFE Transform3d observers that a cube transform has + // changed. We do so centrally on attribute change so that any + // modifier of cube translate data (API, scripting, undo / redo, + // manipulator) will cause a UFE notification to be emitted. + if (plug == flowViewportAPIMayaLocator->mCubeTranslateX || + plug == flowViewportAPIMayaLocator->mCubeTranslateY || + plug == flowViewportAPIMayaLocator->mCubeTranslateZ) { + + // Walk up to the translate plug, then up to the transformed + // cubes plug, then down to the cube name plug. + MPlug transformedCubesPlug(plug.parent().parent()); + TF_AXIOM(transformedCubesPlug.isElement()); + auto cubeNamePlug = transformedCubesPlug.child(MhFlowViewportAPILocator::mTransformedCubeName); + std::string cubeName = cubeNamePlug.asString().asChar(); + + // During translate manipulation the x, y, and z plugs are + // modified in turn, which causes 3x notification, unclear how + // to optimize this as of 6-Jun-2024. + Ufe::Transform3d::notify(flowViewportAPIMayaLocator->getCubeUfePath(cubeName)); + } + }else{ return; //Not a grid cube attribute } @@ -220,6 +370,11 @@ namespace fnData.getData( outVal[0], outVal[1], outVal[2] ); } + std::string GetStringAttributeValue(const MPlug& plug) + { + return std::string(plug.asString().asChar()); + } + //Callback after a File Open void afterOpenCallback (void *clientData) { @@ -229,7 +384,8 @@ namespace MhFlowViewportAPILocator* flowViewportAPIMayaLocator = reinterpret_cast(clientData); flowViewportAPIMayaLocator->setCubeGridParametersFromAttributes(); - flowViewportAPIMayaLocator->addedToModelCb(); + // No need to call flowViewportAPIMayaLocator->addedToModelCb(), + // as reading the file will add the node to the model. } void nodeAddedToModel(MObject& node, void* /* clientData */) @@ -252,6 +408,306 @@ namespace fpNode->removedFromModelCb(); } +// Minimal UFE scene item implementation, to be included in UFE selection. +class CubeSceneItem : public Ufe::SceneItem +{ +public: + typedef std::shared_ptr Ptr; + + CubeSceneItem(const Ufe::Path& path) + : SceneItem(path), _locator(getLocator(path)) { TF_AXIOM(_locator); } + + std::string nodeType() const override { return "FlowViewportAPILocatorCube"; } + + // Returns the locator parent of the cube. + MhFlowViewportAPILocator* locator() const { return _locator; } + + // Unimplemented defaults. These should preferably be in UFE. + // PPT, 6-Jun-2024. + Ufe::Value getMetadata(const std::string& key) const override { return {}; } + UndoableCommandPtr setMetadataCmd(const std::string& key, const Ufe::Value& value) override { return nullptr; } + UndoableCommandPtr clearMetadataCmd(const std::string& key) override { return nullptr; } + Ufe::Value getGroupMetadata(const std::string& group, const std::string& key) const override { return {}; } + UndoableCommandPtr setGroupMetadataCmd(const std::string& group, const std::string& key, const Ufe::Value& value) override { return nullptr; } + UndoableCommandPtr clearGroupMetadataCmd(const std::string& group, const std::string& key) override { return nullptr; } + +private: + + MhFlowViewportAPILocator* const _locator; +}; + +// Minimal Hierarchy interface handler for locator cubes. Its only +// responsibility is to create a scene item for a locator cube. +class CubeHierarchyHandler : public Ufe::HierarchyHandler +{ +public: + + // No hierarchy interface for locator cubes. + Hierarchy::Ptr hierarchy(const SceneItem::Ptr& item) const override { + return nullptr; + } + + SceneItem::Ptr createItem(const Path& path) const override { + // Is the argument path an MhFlowViewportAPILocator node? + return getLocator(path) ? std::make_shared(path) : nullptr; + } + + // No children for locator cubes, so no child filter. + Hierarchy::ChildFilter childFilter() const override { return {}; } +}; + +// UFE command for visibility change undo / redo. +class CubeUndoVisibleCommand : public Ufe::UndoableCommand +{ +public: + CubeUndoVisibleCommand(const Path& cubePath, bool newVis, bool oldVis) + : _cubePath(cubePath), _newVis(newVis), _oldVis(oldVis) + {} + +private: + + void setVisibility(bool vis) { + auto item = Hierarchy::createItem(_cubePath); + if (!TF_VERIFY(item)) { return; } + auto o3d = Object3d::object3d(item); + if (!TF_VERIFY(o3d)) { return; } + o3d->setVisibility(vis); + } + + void execute() override { redo(); } + void undo() override { + setVisibility(_oldVis); + } + void redo() override { + setVisibility(_newVis); + } + + const Path _cubePath; + const bool _newVis; + const bool _oldVis; +}; + +// Minimal Object3d interface for locator cubes. It only implements show / +// hide. If framing is desired, the bounding box method could be implemented. +// +// Only visibility support is implemented as of 28-May-2024. A sparse list of +// hidden cubes is stored in the Maya locator node. If our name isn't in the +// hidden list, we're visible. + +class CubeObject3d : public Ufe::Object3d +{ +public: + + CubeObject3d(const CubeSceneItem::Ptr& item) : _item(item) {} + ~CubeObject3d() override = default; + + SceneItem::Ptr sceneItem() const override { return _item; } + CubeSceneItem::Ptr cubeSceneItem() const { return _item; } + bool visibility() const override { + auto hidden = cubeSceneItem()->locator()->hiddenCubes(); + + // If we're not on the list, we're visible. + return hidden.count(sceneItem()->nodeName()) == 0; + } + + // Set visibility for this cube. No-op changes do not write to the Maya + // locator node. + void setVisibility(bool vis) override { + auto hidden = cubeSceneItem()->locator()->hiddenCubes(); + + auto cubeName = sceneItem()->nodeName(); + // If making visible, try removing from the hidden set, else (making + // invisible) try adding to the hidden set. + bool update = (vis ? (hidden.erase(cubeName) > 0) : + hidden.insert(cubeName).second); + + if (update) { + cubeSceneItem()->locator()->hideCubes(hidden); + } + } + + Ufe::UndoableCommand::Ptr setVisibleCmd(bool vis) override { + // In Maya calling hide on an already-hidden object is legal, and + // logs a no-op undoable command. + return std::make_shared( + sceneItem()->path(), vis, visibility()); + } + + // Unimplemented. + BBox3d boundingBox() const override { return {}; } + +private: + + const CubeSceneItem::Ptr _item; +}; + +class CubeObject3dHandler : public Ufe::Object3dHandler +{ +public: + Object3d::Ptr object3d(const SceneItem::Ptr& item) const override + { + return std::make_shared( + std::dynamic_pointer_cast(item)); + } +}; + +class CubeTranslateCommand : public Ufe::TranslateUndoableCommand +{ +public: + CubeTranslateCommand( + const Path& cubePath, const Vector3d& newT, const Vector3d& oldT + ) : TranslateUndoableCommand(cubePath), _newT(newT), _oldT(oldT) + {} + +private: + + bool set(double x, double y, double z) override { + auto item = sceneItem(); + if (!TF_VERIFY(item)) { return false; } + auto t3d = Transform3d::transform3d(item); + if (!TF_VERIFY(t3d)) { return false; } + t3d->translate(x, y, z); + return true; + } + + void execute() override { redo(); } + void undo() override { + set(_oldT.x(), _oldT.y(), _oldT.z()); + } + void redo() override { + set(_newT.x(), _newT.y(), _newT.z()); + } + + const Path _cubePath; + const Vector3d _newT; + const Vector3d _oldT; +}; + +// Minimal Transform3d interface for locator cubes. It only implements +// translation. A sparse list of transformed cubes is stored in the Maya +// locator node. +// +// The cube local transformation is composed of two parts: +// - The cube's position in the grid, as determined by its (x, y, z) indices +// and the delta translation between cubes. This acts as a fixed rotate and +// scale pivot (if rotation and scaling were to be added). +// - The optional per-cube translation. + +class CubeTransform3d : public Ufe::Transform3d +{ +public: + + CubeTransform3d(const CubeSceneItem::Ptr& item) + : _item(item), _gridOffset(gridOffset()) {} + + // Overrides. + const Path& path() const override { + static Path emptyPath; + return _item ? _item->path() : emptyPath; + } + + SceneItem::Ptr sceneItem() const override { return _item; } + CubeSceneItem::Ptr cubeSceneItem() const { return _item; } + + Matrix4d matrix() const override { + GfMatrix4d m(1.0); + + // The local transform matrix is the pivot plus the translation. + m.SetTranslateOnly( + UfeExtensions::toUsd(rotatePivot()) + + UfeExtensions::toUsd(translation())); + + return UfeExtensions::toUfe(m); + } + + Matrix4d segmentInclusiveMatrix() const override { + // Since the cube path segment has only one component (the cube + // itself), this is simply equal to matrix(). + return matrix(); + } + + Matrix4d segmentExclusiveMatrix() const override { + // Since the cube path segment has only one component (the cube + // itself), this is simply the identity matrix. + return UfeExtensions::toUfe(GfMatrix4d(1.0)); + } + + TranslateUndoableCommand::Ptr translateCmd( + double x, double y, double z) override { + return std::make_shared( + path(), Vector3d(x, y, z), translation()); + } + + void translate(double x, double y, double z) override { + cubeSceneItem()->locator()->translate( + sceneItem()->nodeName(), x, y, z); + } + + Vector3d translation() const override { + return UfeExtensions::toUfe( + cubeSceneItem()->locator()->translation(sceneItem()->nodeName())); + } + + Vector3d rotatePivot() const override { + return UfeExtensions::toUfe(gridOffset()); + } + Vector3d scalePivot() const override { + return rotatePivot(); + } + + // Unimplemented. + RotateUndoableCommand::Ptr rotateCmd( + double x, double y, double z) override { return nullptr; } + Vector3d rotation() const override { return {}; } + ScaleUndoableCommand::Ptr scaleCmd( + double x, double y, double z) override { return nullptr; } + Vector3d scale() const override { return {1, 1, 1}; } + TranslateUndoableCommand::Ptr rotatePivotCmd( + double x, double y, double z) override { return nullptr; } + TranslateUndoableCommand::Ptr scalePivotCmd( + double x, double y, double z) override { return nullptr; } + SetMatrix4dUndoableCommand::Ptr setMatrixCmd(const Matrix4d& m) override + { return nullptr; } + +private: + + Ufe::Vector3i indices() const { + // Extract (x, y, z) indices from cube name, which is + // cube_x_y_z. + const static std::regex re("cube_([0-9]+)_([0-9]+)_([0-9]+)$"); + std::smatch match; + // Can't match temporary string, see + // https://stackoverflow.com/questions/27391016 + std::string cn = sceneItem()->nodeName(); + if (!TF_VERIFY(std::regex_match(cn, match, re), + "Illegal cube names without positional indices.")) { + return {}; + } + return {std::stoi(match[1]), std::stoi(match[2]), std::stoi(match[3])}; + } + + GfVec3d gridOffset() const { + // Get the delta translation from the locator node. + auto dt = cubeSceneItem()->locator()->deltaTrans(); + auto i = indices(); + return GfCompMult(dt, GfVec3d(i.x(), i.y(), i.z())); + } + + const CubeSceneItem::Ptr _item; + const GfVec3d _gridOffset; +}; + +class CubeTransform3dHandler : public Ufe::Transform3dHandler +{ +public: + + Transform3d::Ptr transform3d(const SceneItem::Ptr& item) const override + { + return std::make_shared( + std::dynamic_pointer_cast(item)); + } +}; + }//end of anonymous namespace //Initialization of static members @@ -261,11 +717,18 @@ MObject MhFlowViewportAPILocator::mNumCubeLevelsX; MObject MhFlowViewportAPILocator::mNumCubeLevelsY; MObject MhFlowViewportAPILocator::mNumCubeLevelsZ; MObject MhFlowViewportAPILocator::mCubeHalfSize; -MObject MhFlowViewportAPILocator::mCubeInitalTransform; +MObject MhFlowViewportAPILocator::mCubeInitialTransform; MObject MhFlowViewportAPILocator::mCubeColor; MObject MhFlowViewportAPILocator::mCubeOpacity; MObject MhFlowViewportAPILocator::mCubesUseInstancing; MObject MhFlowViewportAPILocator::mCubesDeltaTrans; +MObject MhFlowViewportAPILocator::mHiddenCubes; +MObject MhFlowViewportAPILocator::mCubeTranslateX; +MObject MhFlowViewportAPILocator::mCubeTranslateY; +MObject MhFlowViewportAPILocator::mCubeTranslateZ; +MObject MhFlowViewportAPILocator::mCubeTranslate; +MObject MhFlowViewportAPILocator::mTransformedCubeName; +MObject MhFlowViewportAPILocator::mTransformedCubes; void MhFlowViewportAPILocator::postConstructor() { @@ -343,8 +806,8 @@ void MhFlowViewportAPILocator::setCubeGridParametersFromAttributes() GetAttributeValue(_cubeGridParams._halfSize, mObj, MhFlowViewportAPILocator::mCubeHalfSize); MMatrix mat; - GetMatrixAttributeValue(mat, mObj, MhFlowViewportAPILocator::mCubeInitalTransform); - memcpy(_cubeGridParams._initalTransform.GetArray(), mat[0], sizeof(double) * 16);//convert from MMatrix to GfMatrix4d + GetMatrixAttributeValue(mat, mObj, MhFlowViewportAPILocator::mCubeInitialTransform); + memcpy(_cubeGridParams._initialTransform.GetArray(), mat[0], sizeof(double) * 16);//convert from MMatrix to GfMatrix4d double3 color; GetDouble3AttributeValue(color, mObj, MhFlowViewportAPILocator::mCubeColor); @@ -361,6 +824,8 @@ void MhFlowViewportAPILocator::setCubeGridParametersFromAttributes() _cubeGridParams._deltaTrans.data()[1] = deltaTrans[1]; _cubeGridParams._deltaTrans.data()[2] = deltaTrans[2]; + _cubeGridParams._hidden = split(GetStringAttributeValue(MPlug(mObj, mHiddenCubes))); + _hydraViewportDataProducerSceneIndexExample.setCubeGridParams(_cubeGridParams); } @@ -412,26 +877,78 @@ void* MhFlowViewportAPILocator::creator() return new MhFlowViewportAPILocator; } -void MhFlowViewportAPILocator::addedToModelCb() +Ufe::Path MhFlowViewportAPILocator::getUfePath() const { - static const SdfPath noPrefix = SdfPath::AbsoluteRootPath(); + MDagPath dagPath; + TF_AXIOM(MDagPath::getAPathTo(thisMObject(), dagPath) == MS::kSuccess); + return Ufe::Path(UfeExtensions::dagPathToUfePathSegment(dagPath)); +} + +/* static */ +Ufe::Path MhFlowViewportAPILocator::getCubeUfePath( + const MObject& locatorObj, + const std::string& cubeName +) +{ + Ufe::Path::Segments segments; + segments.reserve(2); + + // First path segment: Dag path to the locator node. + MDagPath dagPath; + TF_AXIOM(MDagPath::getAPathTo(locatorObj, dagPath) == MS::kSuccess); + segments.emplace_back(UfeExtensions::dagPathToUfePathSegment(dagPath)); + + // Second path segment: a single component, the cube identifier. + segments.emplace_back(Ufe::PathComponent(cubeName), ufeRunTimeId, '/'); + + return Ufe::Path(std::move(segments)); +} +Ufe::Path +MhFlowViewportAPILocator::getCubeUfePath(const std::string& cubeName) const +{ + return getCubeUfePath(thisMObject(), cubeName); +} + +void MhFlowViewportAPILocator::addedToModelCb() +{ //Add the callback when an attribute of this node changes MObject obj = thisMObject(); _cbAttributeChangedId = MNodeMessage::addAttributeChangedCallback(obj, attributeChangedCallback, ((void*)this)); _hydraViewportDataProducerSceneIndexExample.setContainerNode(&obj); - _hydraViewportDataProducerSceneIndexExample.addDataProducerSceneIndex(); + + // Construct our scene below a prefix in the Hydra scene. Would have liked + // to call + // GetMayaHydraLibInterface().GetTerminalSceneIndices(); + // to compute a unique, descriptive scene index prefix while accounting for + // existing prefixes, using + // MayaHydra::sceneIndexPathPrefix() + // but during file read this is not possible, as scene indices are built + // later, and GetTerminalSceneIndices() returns an empty vector. Use a + // pointer value to make the prefix unique, even if this is not very + // readable. + _pathPrefix = SdfPath(TfStringPrintf("/cube_%p", this)); + _hydraViewportDataProducerSceneIndexExample.addDataProducerSceneIndex( + _pathPrefix); //Store the MObject* of the maya node in various classes _hydraViewportFilteringSceneIndexClientExample->setDccNode(&obj); //Register this filtering scene index client, so it can append custom filtering scene indices to Hydra viewport scene indices Fvp::FilteringSceneIndexInterface& filteringSceneIndexInterface = Fvp::FilteringSceneIndexInterface::get(); - const bool bResult = filteringSceneIndexInterface.registerFilteringSceneIndexClient(_hydraViewportFilteringSceneIndexClientExample); - if(! bResult){ - perror("ERROR : filteringSceneIndexInterface.registerFilteringSceneIndexClient returned false"); - } + TF_VERIFY(filteringSceneIndexInterface.registerFilteringSceneIndexClient(_hydraViewportFilteringSceneIndexClientExample)); + + // 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)); + + // Register a path mapper to map application UFE paths to scene index paths, + // for selection highlighting. + _appPath = getUfePath(); + auto pathMapper = std::make_shared( + ufeRunTimeId, _appPath, _pathPrefix); + TF_AXIOM(Fvp::PathMapperRegistry::Instance().Register(_appPath, pathMapper)); } void MhFlowViewportAPILocator::removedFromModelCb() @@ -447,6 +964,113 @@ void MhFlowViewportAPILocator::removedFromModelCb() Fvp::FilteringSceneIndexInterface& filteringSceneIndexInterface = Fvp::FilteringSceneIndexInterface::get(); filteringSceneIndexInterface.unregisterFilteringSceneIndexClient(_hydraViewportFilteringSceneIndexClientExample); + + // Unregister our pick handler. + TF_AXIOM(MayaHydra::PickHandlerRegistry::Instance().Unregister(_pathPrefix)); + + // Unregister our path mapper. Use stored UFE path, as at this point + // our locator node is no longer in the Maya scene, so we cannot obtain + // an MDagPath for it. + TF_AXIOM(Fvp::PathMapperRegistry::Instance().Unregister(_appPath)); +} + +MhFlowViewportAPILocator::TransformedCubes +MhFlowViewportAPILocator::transformedCubes() const +{ + // On the assumption that the array of transformed cubes is small and few + // cubes are transformed, read the whole array. + MPlug transformedCubesPlug(thisMObject(), mTransformedCubes); + TransformedCubes transformedCubes; + TF_AXIOM(transformedCubesPlug.isArray()); + + for (unsigned int i=0; i < transformedCubesPlug.numElements(); ++i) { + auto cubePlug = transformedCubesPlug[i]; + auto cubeNamePlug = cubePlug.child(mTransformedCubeName); + auto cubeTranslatePlug = cubePlug.child(mCubeTranslate); + auto cubeTxPlug = cubeTranslatePlug.child(mCubeTranslateX); + auto cubeTyPlug = cubeTranslatePlug.child(mCubeTranslateY); + auto cubeTzPlug = cubeTranslatePlug.child(mCubeTranslateZ); + + std::string cubeName = cubeNamePlug.asString().asChar(); + GfVec3f cubeTranslate( + cubeTxPlug.asFloat(), cubeTyPlug.asFloat(), cubeTzPlug.asFloat()); + transformedCubes[cubeName] = cubeTranslate; + } + return transformedCubes; +} + +void MhFlowViewportAPILocator::setTranslatePlug( + const MPlug& cubePlug, double x, double y, double z +) +{ + auto cubeTranslatePlug = cubePlug.child(mCubeTranslate); + cubeTranslatePlug.child(mCubeTranslateX).setValue(x); + cubeTranslatePlug.child(mCubeTranslateY).setValue(y); + cubeTranslatePlug.child(mCubeTranslateZ).setValue(z); +} + +void MhFlowViewportAPILocator::translate( + const std::string& cubeName, double x, double y, double z +) +{ + // Check if this cube already has an entry; if so, update it. + MPlug transformedCubesPlug(thisMObject(), mTransformedCubes); + TF_AXIOM(transformedCubesPlug.isArray()); + + bool found = false; + MPlug cubePlug; + for (unsigned int i=0; i < transformedCubesPlug.numElements() && !found; + ++i) { + cubePlug = transformedCubesPlug[i]; + auto cubeNamePlug = cubePlug.child(mTransformedCubeName); + std::string cn = cubeNamePlug.asString().asChar(); + if (cn == cubeName) { + found = true; + } + } + + if (!found) { + // Add an entry to the array. + cubePlug = transformedCubesPlug.elementByLogicalIndex( + transformedCubesPlug.numElements()); + + auto cubeNamePlug = cubePlug.child(mTransformedCubeName); + cubeNamePlug.setValue(cubeName.c_str()); + } + + setTranslatePlug(cubePlug, x, y, z); +} + +GfVec3d +MhFlowViewportAPILocator::translation(const std::string& cubeName) const +{ + auto tc = transformedCubes(); + auto found = tc.find(cubeName); + return (found == tc.end() ? GfVec3d() : found->second); +} + +MhFlowViewportAPILocator::HiddenCubes +MhFlowViewportAPILocator::hiddenCubes() const +{ + return split(GetStringAttributeValue( + MPlug(thisMObject(), MhFlowViewportAPILocator::mHiddenCubes))); +} + +void MhFlowViewportAPILocator::hideCubes(const HiddenCubes& hidden) +{ + // Concatenate the set into a space-separated string, and write to the + // plug. + constexpr const char* space = " "; + std::ostringstream newHidden; + std::copy(hidden.cbegin(), hidden.cend(), + std::ostream_iterator(newHidden, space)); + MPlug(thisMObject(), mHiddenCubes).setString(MString(newHidden.str().c_str())); +} + +GfVec3d MhFlowViewportAPILocator::deltaTrans() const +{ + const auto& dt = MPlug(thisMObject(), mCubesDeltaTrans).asMDataHandle().asDouble3(); + return GfVec3d(dt[0], dt[1], dt[2]); } //--------------------------------------------------------------------------- @@ -460,7 +1084,7 @@ void MhFlowViewportAPILocator::removedFromModelCb() CHECK_MSTATUS(attr.setStorable(true) ); \ CHECK_MSTATUS(attr.setReadable(true) ); \ CHECK_MSTATUS(attr.setWritable(true) ); \ - CHECK_MSTATUS(attr.setAffectsAppearance(true) ); + CHECK_MSTATUS(attr.setAffectsAppearance(true) ); //Macro to create output attribute for the maya node #define MAKE_OUTPUT(attr) \ @@ -493,7 +1117,7 @@ MStatus MhFlowViewportAPILocator::initialize() MAKE_INPUT(nAttr); CHECK_MSTATUS ( nAttr.setDefault(2.0) ); - mCubeInitalTransform = mAttr.create("cubeInitalTransform", "cIT", MFnMatrixAttribute::kDouble, &status); + mCubeInitialTransform = mAttr.create("cubeInitalTransform", "cIT", MFnMatrixAttribute::kDouble, &status); MAKE_INPUT(mAttr); mCubeColor = nAttr.create("cubeColor", "cC", MFnNumericData::k3Double, 1.0, &status); @@ -512,15 +1136,44 @@ MStatus MhFlowViewportAPILocator::initialize() MAKE_INPUT(nAttr); CHECK_MSTATUS ( nAttr.setDefault(5.0, 5.0, 5.0) ); + MFnTypedAttribute strAttr; + mHiddenCubes = strAttr.create("hiddenCubes", "hc", MFnData::kString); + MAKE_INPUT(strAttr); + + mCubeTranslateX = nAttr.create("translateX", "tx", MFnNumericData::kDouble); + MAKE_INPUT(nAttr); + mCubeTranslateY = nAttr.create("translateY", "ty", MFnNumericData::kDouble); + MAKE_INPUT(nAttr); + mCubeTranslateZ = nAttr.create("translateZ", "tz", MFnNumericData::kDouble); + MAKE_INPUT(nAttr); + + MFnCompoundAttribute cAttr; + mCubeTranslate = cAttr.create("translate", "t"); + cAttr.addChild(mCubeTranslateX); + cAttr.addChild(mCubeTranslateY); + cAttr.addChild(mCubeTranslateZ); + MAKE_INPUT(cAttr); + + mTransformedCubeName = strAttr.create("transformedCubeName", "tcn", MFnData::kString); + MAKE_INPUT(strAttr); + + mTransformedCubes = cAttr.create("transformedCubes", "tc"); + cAttr.addChild(mTransformedCubeName); + cAttr.addChild(mCubeTranslate); + cAttr.setArray(true); + MAKE_INPUT(cAttr); + CHECK_MSTATUS ( addAttribute(mNumCubeLevelsX)); CHECK_MSTATUS ( addAttribute(mNumCubeLevelsY)); CHECK_MSTATUS ( addAttribute(mNumCubeLevelsZ)); CHECK_MSTATUS ( addAttribute(mCubeHalfSize)); - CHECK_MSTATUS ( addAttribute(mCubeInitalTransform)); + CHECK_MSTATUS ( addAttribute(mCubeInitialTransform)); CHECK_MSTATUS ( addAttribute(mCubeColor)); CHECK_MSTATUS ( addAttribute(mCubeOpacity)); CHECK_MSTATUS ( addAttribute(mCubesUseInstancing)); CHECK_MSTATUS ( addAttribute(mCubesDeltaTrans)); + CHECK_MSTATUS ( addAttribute(mHiddenCubes)); + CHECK_MSTATUS ( addAttribute(mTransformedCubes)); return status; } @@ -543,6 +1196,23 @@ MStatus initializePlugin( MObject obj ) return status; } + // Register a UFE run-time for the locator node type. The Hierarchy + // handler is supported for scene item creation only. + // + // Supported UFE interfaces: + // - Object3d: only visibility supported as of 30-May-2024; bounding box + // unsupported. + // - Transform3d: only translation supported as of 3-Jun-2024. + // + Ufe::RunTimeMgr::Handlers ufeHandlers; + ufeHandlers.hierarchyHandler = std::make_shared(); + ufeHandlers.object3dHandler = std::make_shared(); + ufeHandlers.transform3dHandler = std::make_shared(); + ufeRunTimeId = Ufe::RunTimeMgr::instance().register_(ufeRunTimeName, ufeHandlers); + // Arbitrarily use '/' as a path string component separator, will never be + // more than one component. + Ufe::PathString::registerPathComponentSeparator(ufeRunTimeId, '/'); + return status; } @@ -551,6 +1221,11 @@ MStatus uninitializePlugin( MObject obj) MStatus status; MFnPlugin plugin( obj ); + Ufe::PathString::unregisterPathComponentSeparator(ufeRunTimeId, '/'); + + // Unregister UFE run-time for the locator node type. + Ufe::RunTimeMgr::instance().unregister(ufeRunTimeId); + status = plugin.deregisterNode( MhFlowViewportAPILocator::id ); if (!status) { status.perror("deregisterNode"); diff --git a/lib/mayaHydra/hydraExtensions/CMakeLists.txt b/lib/mayaHydra/hydraExtensions/CMakeLists.txt index 032a7b50d2..976243f946 100644 --- a/lib/mayaHydra/hydraExtensions/CMakeLists.txt +++ b/lib/mayaHydra/hydraExtensions/CMakeLists.txt @@ -203,4 +203,5 @@ endif() # subdirectories # ----------------------------------------------------------------------------- add_subdirectory(adapters) +add_subdirectory(pick) add_subdirectory(sceneIndex) diff --git a/lib/mayaHydra/hydraExtensions/hydraUtils.cpp b/lib/mayaHydra/hydraExtensions/hydraUtils.cpp index 1587e4a222..6b1cf3ab83 100644 --- a/lib/mayaHydra/hydraExtensions/hydraUtils.cpp +++ b/lib/mayaHydra/hydraExtensions/hydraUtils.cpp @@ -261,7 +261,7 @@ void GetDirectionalLightPositionFromDirectionVector(GfVec3f& outPosition, const { //To simulate a directional light which has no actual position, but doesn't seem to be supported in hydra, we set a position very very far //so it looks like a directional light. - static const float farfarAway {1.0e15f};//we use a point on the Z axis far far away + constexpr float farfarAway {1.0e15f};//we use a point on the Z axis far far away outPosition = {-farfarAway*direction.data()[0], -farfarAway*direction.data()[1], -farfarAway*direction.data()[2]}; } diff --git a/lib/mayaHydra/hydraExtensions/hydraUtils.h b/lib/mayaHydra/hydraExtensions/hydraUtils.h index 28f2acdcc8..72ed4d5d62 100644 --- a/lib/mayaHydra/hydraExtensions/hydraUtils.h +++ b/lib/mayaHydra/hydraExtensions/hydraUtils.h @@ -100,16 +100,15 @@ MAYAHYDRALIB_API bool GetXformMatrixFromPrim(const PXR_NS::HdSceneIndexPrim& prim, PXR_NS::GfMatrix4d& outMatrix); /** - * @brief Get the Hydra Xform matrix from a given prim. + * @brief Get a directional light position from a direction vector. * - * This method makes no guarantee on whether the matrix is flattened or not. + * A directional light without a position does not seem to be supported by + * Hydra at time of writing (6-May-2024). Simulate a directional light by + * positioning a light far away. * - * @param[in] prim is the Hydra prim in the SceneIndex of which to get the transform matrix. - * @param[out] outMatrix is the transform matrix of the prim. - * - * @return True if the operation succeeded, false otherwise. + * @param[in] direction + * @param[out] outPosition computed distant light position. */ - MAYAHYDRALIB_API void GetDirectionalLightPositionFromDirectionVector(PXR_NS::GfVec3f& outPosition, const PXR_NS::GfVec3f& direction); diff --git a/lib/mayaHydra/hydraExtensions/mixedUtils.cpp b/lib/mayaHydra/hydraExtensions/mixedUtils.cpp index b796aaa46a..549790ca2b 100644 --- a/lib/mayaHydra/hydraExtensions/mixedUtils.cpp +++ b/lib/mayaHydra/hydraExtensions/mixedUtils.cpp @@ -110,7 +110,7 @@ SdfPath RenderItemToSdfPath(const MRenderItem& ri, const bool stripNamespaces) return sdfPath; } -bool getRGBAColorPreferenceValue(const std::string& colorName, PXR_NS::GfVec4f& outColor) +bool getRGBAColorPreferenceValue(const std::string& colorName, GfVec4f& outColor) { MDoubleArray rgbaColorValues; bool wasCommandSuccessful = MGlobal::executeCommand( @@ -144,7 +144,7 @@ bool getIndexedColorPreferenceIndex( bool getColorPreferencesPaletteColor( const std::string& tableName, size_t index, - PXR_NS::GfVec4f& outColor) + GfVec4f& outColor) { MDoubleArray rgbColorValues; std::string getColorCommand = "colorIndex -q -" + tableName + " " + std::to_string(index); @@ -163,7 +163,7 @@ bool getColorPreferencesPaletteColor( bool getIndexedColorPreferenceValue( const std::string& colorName, const std::string& tableName, - PXR_NS::GfVec4f& outColor) + GfVec4f& outColor) { size_t colorIndex = 0; if (getIndexedColorPreferenceIndex(colorName, tableName, colorIndex)) { @@ -172,4 +172,32 @@ bool getIndexedColorPreferenceValue( return false; } +SdfPath sceneIndexPathPrefix( + const HdSceneIndexBaseRefPtr& sceneIndex, + MObject& mayaNode +) +{ + constexpr char kSceneIndexPluginSuffix[] = {"_PluginNode"}; + MFnDependencyNode dependNodeFn(mayaNode); + // To match plugin TfType registration, name must begin with upper case. + const auto sceneIndexPluginName = [&](){ + std::string name = dependNodeFn.typeName().asChar(); + name[0] = toupper(name[0]); + name += kSceneIndexPluginSuffix; + return TfToken(name);}(); + + // Create a unique scene index path prefix by starting with the + // Dag node name, and checking for uniqueness under the scene + // index plugin parent rprim. If not unique, add an + // incrementing numerical suffix until it is. + const auto sceneIndexPluginPath = SdfPath::AbsoluteRootPath().AppendChild(sceneIndexPluginName); + const auto newName = uniqueChildName( + sceneIndex, + sceneIndexPluginPath, + SanitizeNameForSdfPath(dependNodeFn.name().asChar()) + ); + + return sceneIndexPluginPath.AppendChild(newName); +} + } // namespace MAYAHYDRA_NS_DEF diff --git a/lib/mayaHydra/hydraExtensions/mixedUtils.h b/lib/mayaHydra/hydraExtensions/mixedUtils.h index a35e1d3c08..bba2af7177 100644 --- a/lib/mayaHydra/hydraExtensions/mixedUtils.h +++ b/lib/mayaHydra/hydraExtensions/mixedUtils.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -225,6 +226,16 @@ bool getIndexedColorPreferenceValue( const std::string& tableName, PXR_NS::GfVec4f& outColor); +//! Using a standard suffix and the depend node type, call uniqueChildName() to +//! create a unique scene index path prefix based at the root of the scene +//! index scene. The mayaNode MObject is passed by non-const reference to +//! satisfy MFnDependencyNode API requirements. +MAYAHYDRALIB_API +PXR_NS::SdfPath sceneIndexPathPrefix( + const PXR_NS::HdSceneIndexBaseRefPtr& sceneIndex, + MObject& mayaNode +); + } // namespace MAYAHYDRA_NS_DEF #endif // MAYAHYDRALIB_MIXED_UTILS_H diff --git a/lib/mayaHydra/hydraExtensions/pick/CMakeLists.txt b/lib/mayaHydra/hydraExtensions/pick/CMakeLists.txt new file mode 100644 index 0000000000..10777b0e52 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/pick/CMakeLists.txt @@ -0,0 +1,39 @@ +# ----------------------------------------------------------------------------- +# sources +# ----------------------------------------------------------------------------- +target_sources(${TARGET_NAME} + PRIVATE + mhPickContext.cpp + mhPickContextFwd.cpp + mhPickHandler.cpp + mhPickHandlerFwd.cpp + mhPickHandlerRegistry.cpp + mhUsdPickHandler.cpp +) + +set(HEADERS + mhPickContext.h + mhPickContextFwd.h + mhPickHandler.h + mhPickHandlerFwd.h + mhPickHandlerRegistry.h + mhUsdPickHandler.h +) + +# ----------------------------------------------------------------------------- +# promoted headers +# ----------------------------------------------------------------------------- +mayaUsd_promoteHeaderList( + HEADERS + ${HEADERS} + BASEDIR + ${TARGET_NAME}/pick +) + +# ----------------------------------------------------------------------------- +# install +# ----------------------------------------------------------------------------- +install(FILES ${HEADERS} + DESTINATION + ${CMAKE_INSTALL_PREFIX}/include/mayaHydraLib/pick +) diff --git a/lib/mayaHydra/hydraExtensions/pick/mhPickContext.cpp b/lib/mayaHydra/hydraExtensions/pick/mhPickContext.cpp new file mode 100644 index 0000000000..c1b2bafe35 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/pick/mhPickContext.cpp @@ -0,0 +1,18 @@ +// +// 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. +// + +// Trivial inclusion to ensure header compiles on its own. +#include diff --git a/lib/mayaHydra/hydraExtensions/pick/mhPickContext.h b/lib/mayaHydra/hydraExtensions/pick/mhPickContext.h new file mode 100644 index 0000000000..71ef9d40d8 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/pick/mhPickContext.h @@ -0,0 +1,52 @@ +// +// 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 MH_PICK_CONTEXT_H +#define MH_PICK_CONTEXT_H + +#include +#include + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE +class HdRenderIndex; +class MayaHydraSceneIndexRegistry; +PXR_NAMESPACE_CLOSE_SCOPE + +namespace MAYAHYDRA_NS_DEF { + +/// \class PickContext +/// +/// Provides an interface that pick handlers can call to obtain information +/// needed to implement picking. +/// +class PickContext +{ +public: + + MAYAHYDRALIB_API + virtual std::shared_ptr + sceneIndexRegistry() const = 0; + + MAYAHYDRALIB_API + virtual PXR_NS::HdRenderIndex* renderIndex() const = 0; +}; + +} + +#endif diff --git a/lib/mayaHydra/hydraExtensions/pick/mhPickContextFwd.cpp b/lib/mayaHydra/hydraExtensions/pick/mhPickContextFwd.cpp new file mode 100644 index 0000000000..29ecc07611 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/pick/mhPickContextFwd.cpp @@ -0,0 +1,18 @@ +// +// 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. +// + +// Trivial inclusion to ensure header compiles on its own. +#include diff --git a/lib/mayaHydra/hydraExtensions/pick/mhPickContextFwd.h b/lib/mayaHydra/hydraExtensions/pick/mhPickContextFwd.h new file mode 100644 index 0000000000..2acc2e0cfd --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/pick/mhPickContextFwd.h @@ -0,0 +1,30 @@ +// +// 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 MH_PICK_CONTEXT_FWD_H +#define MH_PICK_CONTEXT_FWD_H + +#include + +namespace MAYAHYDRA_NS_DEF { + +class PickContext; + +using PickContextPtr = PickContext*; +using PickContextConstPtr = const PickContext*; + +} + +#endif diff --git a/lib/mayaHydra/hydraExtensions/pick/mhPickHandler.cpp b/lib/mayaHydra/hydraExtensions/pick/mhPickHandler.cpp new file mode 100644 index 0000000000..715d79b224 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/pick/mhPickHandler.cpp @@ -0,0 +1,17 @@ +// +// 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 diff --git a/lib/mayaHydra/hydraExtensions/pick/mhPickHandler.h b/lib/mayaHydra/hydraExtensions/pick/mhPickHandler.h new file mode 100644 index 0000000000..ba423ac498 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/pick/mhPickHandler.h @@ -0,0 +1,95 @@ +// +// 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 MH_PICK_HANDLER_H +#define MH_PICK_HANDLER_H + +#include +#include + +#include + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE +struct HdxPickHit; +PXR_NAMESPACE_CLOSE_SCOPE + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace MAYAHYDRA_NS_DEF { + +/// \class PickHandler +/// +/// The pick handler performs the picking to selection mapping, from the Hydra +/// scene index pick result to the Maya-centric selection output. +/// +/// The pick handler takes the Hydra scene index pick result, with its Hydra +/// scene index path, computes the corresponding Maya application scene item +/// from it, and places the Maya scene item in either the Maya selection list +/// (for Maya DG items) or UFE selection (non Maya DG items). + +class PickHandler +{ +public: + + struct Input; + struct Output; + + MAYAHYDRALIB_API + virtual bool handlePickHit( + const Input& pickInput, Output& pickOutput + ) const = 0; +}; + +/// \class PickHandler::Input +/// +/// Picking input consists of the Hydra pick hit and the Maya selection state. +struct PickHandler::Input { + Input( + const HdxPickHit& pickHitArg, + const MHWRender::MSelectionInfo& pickInfoArg + ) : pickHit(pickHitArg), pickInfo(pickInfoArg) {} + + const HdxPickHit& pickHit; + const MHWRender::MSelectionInfo& pickInfo; +}; + +/// \class PickHandler::Output +/// +/// Picking output can go either to the UFE representation of the Maya selection +/// (which supports non-Maya objects), or the classic MSelectionList +/// representation of the Maya selection (which only supports Maya objects). It +/// is up to the implementer of the pick handler to decide which is used. If the +/// Maya selection is used, there must be a world space hit point in one to one +/// correspondence with each Maya selection item placed into the MSelectionList. +struct PickHandler::Output { + Output( + MSelectionList& mayaSn, + MPointArray& worldSpaceHitPts, + const Ufe::NamedSelection::Ptr& ufeSn + ) : mayaSelection(mayaSn), mayaWorldSpaceHitPts(worldSpaceHitPts), + ufeSelection(ufeSn) {} + + MSelectionList& mayaSelection; + MPointArray& mayaWorldSpaceHitPts; + const Ufe::NamedSelection::Ptr& ufeSelection; +}; + +} + +#endif diff --git a/lib/mayaHydra/hydraExtensions/pick/mhPickHandlerFwd.cpp b/lib/mayaHydra/hydraExtensions/pick/mhPickHandlerFwd.cpp new file mode 100644 index 0000000000..fa925ad7c4 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/pick/mhPickHandlerFwd.cpp @@ -0,0 +1,18 @@ +// +// 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. +// + +// Trivial inclusion to ensure header compiles on its own. +#include diff --git a/lib/mayaHydra/hydraExtensions/pick/mhPickHandlerFwd.h b/lib/mayaHydra/hydraExtensions/pick/mhPickHandlerFwd.h new file mode 100644 index 0000000000..37925b8a48 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/pick/mhPickHandlerFwd.h @@ -0,0 +1,32 @@ +// +// 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 MH_PICK_HANDLER_FWD_H +#define MH_PICK_HANDLER_FWD_H + +#include + +#include + +namespace MAYAHYDRA_NS_DEF { + +class PickHandler; + +using PickHandlerPtr = std::shared_ptr; +using PickHandlerConstPtr = std::shared_ptr; + +} + +#endif diff --git a/lib/mayaHydra/hydraExtensions/pick/mhPickHandlerRegistry.cpp b/lib/mayaHydra/hydraExtensions/pick/mhPickHandlerRegistry.cpp new file mode 100644 index 0000000000..9f5fbac62c --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/pick/mhPickHandlerRegistry.cpp @@ -0,0 +1,159 @@ +// +// 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 + +#include + +using namespace MayaHydra; + +namespace { + +std::map pickHandlers; +PickContextConstPtr pickContext = nullptr; + +} + +PXR_NAMESPACE_OPEN_SCOPE +TF_INSTANTIATE_SINGLETON(PickHandlerRegistry); +PXR_NAMESPACE_CLOSE_SCOPE + +namespace MAYAHYDRA_NS_DEF { + +/* static */ +PickHandlerRegistry& PickHandlerRegistry::Instance() +{ + return PXR_NS::TfSingleton::GetInstance(); +} + +bool PickHandlerRegistry::Register(const SdfPath& prefix, const PickHandlerConstPtr& pickHandler) +{ + // Can't register an empty path, or an absolute root path prefix. + if (prefix.IsEmpty() || prefix.IsAbsoluteRootPath()) { + return false; + } + + // No entries yet? Add. + if (pickHandlers.empty()) { + pickHandlers.emplace(prefix, pickHandler); + return true; + } + + // At least one entry. Skip all entries before argument prefix. The + // iterator points to an entry with matching or greater key. + auto it = pickHandlers.lower_bound(prefix); + + // Reached the end with no entries before argument prefix? Last entry is + // strictly smaller than. If the last entry is a prefix to the one we're + // trying to add, fail, else add. + if (it == pickHandlers.end()) { + auto rit = pickHandlers.rbegin(); + if (prefix.HasPrefix(rit->first)) { + return false; + } + pickHandlers.emplace_hint(it, prefix, pickHandler); + return true; + } + + // Already in the map or a descendant already in the map? Fail. + if (it->first.HasPrefix(prefix)) { + return false; + } + + // At the first entry and it's not a match or a descendant? Add entry. + if (it == pickHandlers.begin()) { + pickHandlers.emplace_hint(it, prefix, pickHandler); + return true; + } + + // Somewhere in the middle of the map. Go back one entry. Is it a match, + // a descendant, or an ancestor? Fail. + it = std::prev(it); + if (it->first.HasPrefix(prefix) || prefix.HasPrefix(it->first)) { + return false; + } + + // All checks pass: add entry. + pickHandlers.emplace_hint(it, prefix, pickHandler); + return true; +} + +bool PickHandlerRegistry::Unregister(const SdfPath& prefix) +{ + auto found = pickHandlers.find(prefix); + if (found == pickHandlers.end()) { + return false; + } + pickHandlers.erase(found); + return true; +} + +PickHandlerConstPtr PickHandlerRegistry::GetHandler(const SdfPath& path) const +{ + // No entries yet? Fail. + if (pickHandlers.empty()) { + return {}; + } + + // At least one entry. Skip all entries before argument prefix. The + // iterator points to an entry with matching or greater key. + auto it = pickHandlers.lower_bound(path); + + // Reached the end with no entries before argument path? Last entry is + // strictly smaller than, so if it's a prefix to the path we're querying, + // return handler, else fail. + if (it == pickHandlers.end()) { + auto rit = pickHandlers.rbegin(); + return (path.HasPrefix(rit->first)) ? rit->second : nullptr; + } + + // Not at the end. Query is exactly in the map? Return handler. + if (it->first == path) { + return it->second; + } + + // Query path is a prefix to what's in the map? Fail. + if (it->first.HasPrefix(path)) { + return nullptr; + } + + // At the first entry. If query is a descendant, return handler, else fail. + if (it == pickHandlers.begin()) { + return (path.HasPrefix(it->first)) ? it->second : nullptr; + } + + // Somewhere in the middle of the map. Go back one entry. If query is a + // descendant, return handler, else fail. + it = std::prev(it); + return (path.HasPrefix(it->first)) ? it->second : nullptr; +} + +void PickHandlerRegistry::SetPickContext(const PickContextConstPtr& context) +{ + pickContext = context; +} + +PickContextConstPtr PickHandlerRegistry::GetPickContext() const +{ + return pickContext; +} + +} diff --git a/lib/mayaHydra/hydraExtensions/pick/mhPickHandlerRegistry.h b/lib/mayaHydra/hydraExtensions/pick/mhPickHandlerRegistry.h new file mode 100644 index 0000000000..700c0175c5 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/pick/mhPickHandlerRegistry.h @@ -0,0 +1,81 @@ +// +// 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 MH_PICK_HANDLER_REGISTRY_H +#define MH_PICK_HANDLER_REGISTRY_H + +#include +#include +#include + +#include +#include + +namespace MAYAHYDRA_NS_DEF { + +/// \class PickHandlerRegistry +/// +/// A registry of pick handlers, indexed by scene index path. +/// +/// The pick handler registry has the following properties: +/// - All entries are unique. +/// - No entry is a prefix (ancestor) of another entry. +/// +class PickHandlerRegistry { +public: + + MAYAHYDRALIB_API + static PickHandlerRegistry& Instance(); + + //! Register a pick handler to deal with all Hydra scene index prims + //! under prefix. An empty prefix, or a prefix that is the absolute + //! root, are illegal. + /*! + \return False if an ancestor, descendant, or prefix itself is found in the registry, true otherwise. + */ + MAYAHYDRALIB_API + bool Register(const PXR_NS::SdfPath& prefix, const PickHandlerConstPtr& pickHandler); + //! Unregister pick handler for prefix. + /*! + \return False if prefix itself was not found in the registry, true otherwise. + */ + MAYAHYDRALIB_API + bool Unregister(const PXR_NS::SdfPath& prefix); + + //! Get a pick handler for the argument Hydra scene index path. This + //! handler has a prefix that is an ancestor of the argument path. If no + //! pick handler is found, returns a null pointer. + MAYAHYDRALIB_API + PickHandlerConstPtr GetHandler(const PXR_NS::SdfPath& path) const; + + //! Set and get the pick context object for pick handlers to use. + MAYAHYDRALIB_API + void SetPickContext(const PickContextConstPtr& context); + MAYAHYDRALIB_API + PickContextConstPtr GetPickContext() const; + +private: + + PickHandlerRegistry() = default; + ~PickHandlerRegistry() = default; + PickHandlerRegistry(const PickHandlerRegistry&) = delete; + PickHandlerRegistry& operator=(const PickHandlerRegistry&) = delete; + + friend class PXR_NS::TfSingleton; +}; + +} + +#endif diff --git a/lib/mayaHydra/hydraExtensions/pick/mhUsdPickHandler.cpp b/lib/mayaHydra/hydraExtensions/pick/mhUsdPickHandler.cpp new file mode 100644 index 0000000000..5b968c8b4c --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/pick/mhUsdPickHandler.cpp @@ -0,0 +1,351 @@ +// +// 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 l anguage governing permissions and +// limitations under the License. +// + +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// Copy-pasted and adapted from maya-usd's +// https://github.com/Autodesk/maya-usd/blob/dev/lib/mayaUsd/base/tokens.h +// https://github.com/Autodesk/maya-usd/blob/dev/lib/mayaUsd/base/tokens.cpp + +// Tokens that are used as picking optionVars in MayaUSD +// +// clang-format off +#define MAYAUSD_PICK_OPTIONVAR_TOKENS \ + /* The kind to be selected when viewport picking. */ \ + /* After resolving the picked prim, a search from */ \ + /* that prim up the USD namespace hierarchy will */ \ + /* be performed looking for a prim that matches */ \ + /* the kind in the optionVar. If no prim matches, */ \ + /* or if the selection kind is unspecified or */ \ + /* empty, the exact prim picked in the viewport */ \ + /* is selected. */ \ + ((SelectionKind, "mayaUsd_SelectionKind")) \ + /* The method to use to resolve viewport picking */ \ + /* when the picked object is a point instance. */ \ + /* The default behavior is "PointInstancer" which */ \ + /* will resolve to the PointInstancer prim that */ \ + /* generated the point instance. The optionVar */ \ + /* can also be set to "Instances" which will */ \ + /* resolve to individual point instances, or to */ \ + /* "Prototypes" which will resolve to the prim */ \ + /* that is being instanced by the point instance. */ \ + ((PointInstancesPickMode, "mayaUsd_PointInstancesPickMode")) \ +// clang-format on + +TF_DEFINE_PRIVATE_TOKENS(MayaUsdPickOptionVars, MAYAUSD_PICK_OPTIONVAR_TOKENS); + +// Copy-pasted and adapted from maya-usd's +// https://github.com/Autodesk/maya-usd/blob/dev/lib/mayaUsd/render/vp2RenderDelegate/proxyRenderDelegate.h +// https://github.com/Autodesk/maya-usd/blob/dev/lib/mayaUsd/render/vp2RenderDelegate/proxyRenderDelegate.cpp + +// clang-format off +TF_DEFINE_PRIVATE_TOKENS( + _pointInstancesPickModeTokens, + + (PointInstancer) + (Instances) + (Prototypes) +); +// clang-format on + +PXR_NAMESPACE_CLOSE_SCOPE + +PXR_NAMESPACE_USING_DIRECTIVE +using namespace MayaHydra; + +namespace { + +// Copy pasted from +// https://github.com/Autodesk/maya-usd/blob/dev/lib/mayaUsd/render/vp2RenderDelegate/proxyRenderDelegate.cpp + +//! \brief Query the Kind to be selected from viewport. +//! \return A Kind token (https://graphics.pixar.com/usd/docs/api/kind_page_front.html). If the +//! token is empty or non-existing in the hierarchy, the exact prim that gets picked +//! in the viewport will be selected. +TfToken GetSelectionKind() +{ + static const MString kOptionVarName(MayaUsdPickOptionVars->SelectionKind.GetText()); + + if (MGlobal::optionVarExists(kOptionVarName)) { + MString optionVarValue = MGlobal::optionVarStringValue(kOptionVarName); + return TfToken(optionVarValue.asChar()); + } + return TfToken(); +} + +//! \brief Returns the prim or an ancestor of it that is of the given kind. +// +// If neither the prim itself nor any of its ancestors above it in the +// namespace hierarchy have an authored kind that matches, an invalid null +// prim is returned. +UsdPrim GetPrimOrAncestorWithKind(const UsdPrim& prim, const TfToken& kind) +{ + UsdPrim iterPrim = prim; + TfToken primKind; + + while (iterPrim) { + if (UsdModelAPI(iterPrim).GetKind(&primKind) && KindRegistry::IsA(primKind, kind)) { + break; + } + + iterPrim = iterPrim.GetParent(); + } + + return iterPrim; +} + +//! Pick resolution behavior to use when the picked object is a point instance. +enum UsdPointInstancesPickMode +{ + //! The PointInstancer prim that generated the point instance is picked. If + //! multiple nested PointInstancers are involved, the top-level + //! PointInstancer is the one picked. If a selection kind is specified, the + //! traversal up the hierarchy looking for a kind match will begin at that + //! PointInstancer. + PointInstancer = 0, + //! The specific point instance is picked. These are represented as + //! UsdSceneItems with UFE paths to a PointInstancer prim and a non-negative + //! instanceIndex for the specific point instance. In this mode, any setting + //! for selection kind is ignored. + Instances, + //! The prototype being instanced by the point instance is picked. If a + //! selection kind is specified, the traversal up the hierarchy looking for + //! a kind match will begin at the prototype prim. + Prototypes +}; + +//! \brief Query the pick mode to use when picking point instances in the viewport. +//! \return A UsdPointInstancesPickMode enum value indicating the pick mode behavior +//! to employ when the picked object is a point instance. +//! +//! This function retrieves the value for the point instances pick mode optionVar +//! and converts it into a UsdPointInstancesPickMode enum value. If the optionVar +//! has not been set or otherwise has an invalid value, the default pick mode of +//! PointInstancer is returned. +UsdPointInstancesPickMode GetPointInstancesPickMode() +{ + static const MString kOptionVarName(MayaUsdPickOptionVars->PointInstancesPickMode.GetText()); + + auto pickMode = UsdPointInstancesPickMode::PointInstancer; + + if (MGlobal::optionVarExists(kOptionVarName)) { + const TfToken pickModeToken(MGlobal::optionVarStringValue(kOptionVarName).asChar()); + + if (pickModeToken == _pointInstancesPickModeTokens->Instances) { + pickMode = UsdPointInstancesPickMode::Instances; + } else if (pickModeToken == _pointInstancesPickModeTokens->Prototypes) { + pickMode = UsdPointInstancesPickMode::Prototypes; + } + } + + return pickMode; +} + +SdfPath instancerPrimOrigin(const HdxInstancerContext& instancerContext) +{ + // When USD prims are converted to Hydra prims (including point instancers), + // they are given a prim origin data source which provides the information + // as to which prim in the USD data model produced the rprim in the Hydra + // scene index scene. This is what is used here to provide the Hydra scene + // path to USD scene path picking to selection mapping. + auto schema = HdPrimOriginSchema(instancerContext.instancerPrimOrigin); + if (!schema) { + return {}; + } + + return schema.GetOriginPath(HdPrimOriginSchemaTokens->scenePath); +} + +UsdPickHandler::HitPath pickInstance( + const HdxPrimOriginInfo& primOrigin, const HdxPickHit& hit +) +{ + // We match VP2 behavior and return the instance on the top-level instancer. + const auto& instancerContext = primOrigin.instancerContexts.front(); + return { + instancerPrimOrigin(instancerContext), instancerContext.instanceId}; +} + +UsdPickHandler::HitPath pickPrototype( + const HdxPrimOriginInfo& primOrigin, const HdxPickHit& hit +) +{ + // The prototype path is the prim origin path in the USD data model. + return {primOrigin.GetFullPath(), -1}; +} + +UsdPickHandler::HitPath pickInstancer( + const HdxPrimOriginInfo& primOrigin, const HdxPickHit& hit +) +{ + // To return the top-level instancer, we use the first instancer context + // prim origin. To return the innermost instancer, we would use the last + // instancer context prim origin. + return {instancerPrimOrigin(primOrigin.instancerContexts.front()), -1}; +} + +Ufe::Path usdPathToUfePath( + const MayaHydraSceneIndexRegistrationPtr& registration, + const SdfPath& usdPath +) +{ + return registration ? registration->interpretRprimPathFn( + registration->pluginSceneIndex, usdPath) : Ufe::Path(); +} + +} + +namespace MAYAHYDRA_NS_DEF { + +UsdPickHandler::HitPath UsdPickHandler::hitPath(const HdxPickHit& hit) const { + auto primOrigin = HdxPrimOriginInfo::FromPickHit( + renderIndex(), hit); + + if (hit.instancerId.IsEmpty()) { + return {primOrigin.GetFullPath(), -1}; + } + + // If there is a Hydra instancer, distinguish between native instancing + // (implicit USD prototype created by USD itself) and point instancing + // (explicitly authored USD prototypes). As per HdxInstancerContext + // documentation: + // + // [...] "exactly one of instancePrimOrigin or instancerPrimOrigin will + // contain data depending on whether the instancing at the current + // level was implicit or not, respectively." + const auto& instancerContext = primOrigin.instancerContexts.front(); + + if (instancerContext.instancePrimOrigin) { + // Implicit prototype instancing (i.e. USD native instancing). + auto schema = HdPrimOriginSchema(instancerContext.instancePrimOrigin); + if (!TF_VERIFY(schema, "Cannot build prim origin schema for USD native instance.")) { + return {SdfPath(), -1}; + } + return {schema.GetOriginPath(HdPrimOriginSchemaTokens->scenePath), -1}; + } + + // Explicit prototype instancing (i.e. USD point instancing). + std::function pickFn[] = {pickInstancer, pickInstance, pickPrototype}; + + // Retrieve pick mode from mayaUsd optionVar, to see if we're picking + // instances, the instancer itself, or the prototype instanced by the + // point instance. + return pickFn[GetPointInstancesPickMode()](primOrigin, hit); +} + +bool UsdPickHandler::handlePickHit( + const Input& pickInput, Output& pickOutput +) const +{ + if (!sceneIndexRegistry()) { + TF_FATAL_ERROR("Picking called while no scene index registry exists"); + return false; + } + + if (!renderIndex()) { + TF_FATAL_ERROR("Picking called while no render index exists"); + return false; + } + + auto registration = sceneIndexRegistry()->GetSceneIndexRegistrationForRprim(pickInput.pickHit.objectId); + + if (!registration) { + return false; + } + + // For the USD pick handler pick results are directly returned with USD + // scene paths, so no need to remove scene index plugin path prefix. + const auto& [pickedUsdPath, instanceNdx] = hitPath(pickInput.pickHit); + + const auto pickedMayaPath = usdPathToUfePath(registration, pickedUsdPath); + const auto snMayaPath = + // As per https://stackoverflow.com/questions/46114214 + // structured bindings cannot be captured by a lambda in C++ 17, + // so pass in pickedUsdPath and instanceNdx as lambda arguments. + [&pickedMayaPath, ®istration]( + const SdfPath& pickedUsdPath, int instanceNdx) { + + if (instanceNdx >= 0) { + // Point instance: add the instance index to the path. + // Appending a numeric component to the path to identify a + // point instance cannot be done on the picked SdfPath, as + // numeric path components are not allowed by SdfPath. Do so + // here with Ufe::Path, which has no such restriction. + return pickedMayaPath + std::to_string(instanceNdx); + } + + // Not an instance: adjust picked path for selection kind. + auto snKind = GetSelectionKind(); + if (snKind.IsEmpty()) { + return pickedMayaPath; + } + + // Get the prim from the stage and path, to access the + // UsdModelAPI for the prim. + auto proxyShapeObj = registration->dagNode.object(); + if (proxyShapeObj.isNull()) { + TF_FATAL_ERROR("No mayaUsd proxy shape object corresponds to USD pick"); + return pickedMayaPath; + } + + MayaUsdAPI::ProxyStage proxyStage{proxyShapeObj}; + auto prim = proxyStage.getUsdStage()->GetPrimAtPath(pickedUsdPath); + prim = GetPrimOrAncestorWithKind(prim, snKind); + const auto usdPath = prim ? prim.GetPath() : pickedUsdPath; + + return usdPathToUfePath(registration, usdPath); + }(pickedUsdPath, instanceNdx); + + auto si = Ufe::Hierarchy::createItem(snMayaPath); + if (!si) { + return false; + } + + pickOutput.ufeSelection->append(si); + return true; +} + +HdRenderIndex* UsdPickHandler::renderIndex() const +{ + return PickHandlerRegistry::Instance().GetPickContext()->renderIndex(); +} + +std::shared_ptr +UsdPickHandler::sceneIndexRegistry() const +{ + return PickHandlerRegistry::Instance().GetPickContext()->sceneIndexRegistry(); +} + +} diff --git a/lib/mayaHydra/hydraExtensions/pick/mhUsdPickHandler.h b/lib/mayaHydra/hydraExtensions/pick/mhUsdPickHandler.h new file mode 100644 index 0000000000..05f77cdee1 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/pick/mhUsdPickHandler.h @@ -0,0 +1,68 @@ +// +// 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 MH_USD_PICK_HANDLER_H +#define MH_USD_PICK_HANDLER_H + +#include +#include +#include + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE +class HdRenderIndex; +class MayaHydraSceneIndexRegistry; +PXR_NAMESPACE_CLOSE_SCOPE + +namespace MAYAHYDRA_NS_DEF { + +/// \class UsdPickHandler +/// +/// The pick handler performs the picking to selection mapping for USD data. +/// It places its output in the PickOutput UFE selection. + +class UsdPickHandler : public PickHandler { +public: + + UsdPickHandler() = default; + + // Describe a pick hit path. The SdfPath is in the original data model + // scene (USD), not in the scene index scene. + using HitPath = std::tuple; + + UsdPickHandler(HdRenderIndex* renderIndex); + + bool handlePickHit( + const Input& pickInput, Output& pickOutput + ) const override; + +private: + + // Return the closest path and the instance index in the scene index scene + // that corresponds to the pick hit. If the pick hit is not an instance, + // the instance index will be -1. + HitPath hitPath(const HdxPickHit& hit) const; + + PXR_NS::HdRenderIndex* renderIndex() const; + std::shared_ptr + sceneIndexRegistry() const; +}; + +} + +#endif diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.cpp b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.cpp index d14ace33fc..34ed9a2d09 100644 --- a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.cpp +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.cpp @@ -39,6 +39,8 @@ #include #include #include +#include +#include #include @@ -60,6 +62,38 @@ namespace { static std::mutex _adaptersToRecreateMutex; static std::mutex _adaptersToRebuildMutex; + +// Pick handler for the Maya scene index. As the Maya pick handler and the +// Maya scene index are circularly dependent (the Maya pick handler calls +// MayaHydraSceneIndex::AddPickHitToSelectionList() in the Maya scene index +// interface, and the Maya scene index builds the Maya pick handler), they are +// both defined here in the same implementation file. + +class MayaPickHandler : public MayaHydra::PickHandler { + PXR_NS::MayaHydraSceneIndex& _mayaSceneIndex; + +public: + + MayaPickHandler(PXR_NS::MayaHydraSceneIndex& mayaSceneIndex) : + _mayaSceneIndex(mayaSceneIndex) {} + + bool handlePickHit( + const Input& pickInput, Output& pickOutput + ) const override + { + // Maya does not create Hydra instances, so if the pick hit instancer + // ID isn't empty, it's not a Maya pick hit. + if (!pickInput.pickHit.instancerId.IsEmpty()) { + return false; + } + + return _mayaSceneIndex.AddPickHitToSelectionList( + pickInput.pickHit, pickInput.pickInfo, + pickOutput.mayaSelection, pickOutput.mayaWorldSpaceHitPts + ); + } +}; + } PXR_NAMESPACE_OPEN_SCOPE @@ -407,6 +441,10 @@ MayaHydraSceneIndex::MayaHydraSceneIndex( _mayaDefaultMaterial = MayaHydraSceneIndex::CreateMayaDefaultMaterial(); _fallbackMaterial = SdfPath::EmptyPath(); // Empty path for hydra fallback material }); + + // Add our pick handler to the pick handler registry. + auto pickHandler = std::make_shared(*this); + TF_AXIOM(MayaHydra::PickHandlerRegistry::Instance().Register(_rprimPath, pickHandler)); } MayaHydraSceneIndex::~MayaHydraSceneIndex() @@ -414,6 +452,9 @@ MayaHydraSceneIndex::~MayaHydraSceneIndex() //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. + + // Remove our pick handler from the pick handler registry. + TF_AXIOM(MayaHydra::PickHandlerRegistry::Instance().Unregister(_rprimPath)); } void MayaHydraSceneIndex::RemoveCallbacksAndDeleteAdapters() diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mhMayaUsdProxyShapeSceneIndex.cpp b/lib/mayaHydra/hydraExtensions/sceneIndex/mhMayaUsdProxyShapeSceneIndex.cpp index 44c7832164..1527ef8d22 100644 --- a/lib/mayaHydra/hydraExtensions/sceneIndex/mhMayaUsdProxyShapeSceneIndex.cpp +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mhMayaUsdProxyShapeSceneIndex.cpp @@ -16,6 +16,9 @@ #include "mhMayaUsdProxyShapeSceneIndex.h" +#include +#include + #include #if defined(MAYAHYDRALIB_MAYAUSDAPI_ENABLED) @@ -27,15 +30,18 @@ PXR_NAMESPACE_USING_DIRECTIVE namespace MAYAHYDRA_NS_DEF { -MayaUsdProxyShapeSceneIndex::MayaUsdProxyShapeSceneIndex(const MAYAUSDAPI_NS::ProxyStage& proxyStage, - const HdSceneIndexBaseRefPtr& sceneIndexChainLastElement, - const UsdImagingStageSceneIndexRefPtr& usdImagingStageSceneIndex, - const MObjectHandle& dagNodeHandle) +MayaUsdProxyShapeSceneIndex::MayaUsdProxyShapeSceneIndex( + const MAYAUSDAPI_NS::ProxyStage& proxyStage, + const HdSceneIndexBaseRefPtr& sceneIndexChainLastElement, + const UsdImagingStageSceneIndexRefPtr& usdImagingStageSceneIndex, + const MObjectHandle& dagNodeHandle, + const PXR_NS::SdfPath& prefix) : ParentClass(sceneIndexChainLastElement) , InputSceneIndexUtils(sceneIndexChainLastElement) , _usdImagingStageSceneIndex(usdImagingStageSceneIndex) , _proxyStage(proxyStage) , _dagNodeHandle(dagNodeHandle) + , _prefix(prefix) { TfWeakPtr ptr(this); _stageSetNoticeKey = TfNotice::Register(ptr, &MayaUsdProxyShapeSceneIndex::_StageSet); @@ -43,6 +49,11 @@ MayaUsdProxyShapeSceneIndex::MayaUsdProxyShapeSceneIndex(const MAYAUSDAPI_NS::Pr _objectsChangedNoticeKey = TfNotice::Register(ptr, &MayaUsdProxyShapeSceneIndex::_ObjectsChanged); Fvp::Instruments::instance().set(kNbPopulateCalls, VtValue(_nbPopulateCalls)); + + // Add our pick handler to the pick handler registry. All USD scene indices + // could share the same pick handler, but create a new one for simplicity. + auto pickHandler = std::make_shared(); + TF_AXIOM(PickHandlerRegistry::Instance().Register(prefix, pickHandler)); } MayaUsdProxyShapeSceneIndex::~MayaUsdProxyShapeSceneIndex() @@ -50,14 +61,18 @@ MayaUsdProxyShapeSceneIndex::~MayaUsdProxyShapeSceneIndex() TfNotice::Revoke(_stageSetNoticeKey); TfNotice::Revoke(_stageInvalidateNoticeKey); TfNotice::Revoke(_objectsChangedNoticeKey); + + TF_AXIOM(PickHandlerRegistry::Instance().Unregister(_prefix)); } -MayaUsdProxyShapeSceneIndexRefPtr MayaUsdProxyShapeSceneIndex::New(const MAYAUSDAPI_NS::ProxyStage& proxyStage, - const HdSceneIndexBaseRefPtr& sceneIndexChainLastElement, - const UsdImagingStageSceneIndexRefPtr& usdImagingStageSceneIndex, - const MObjectHandle& dagNodeHandle) +MayaUsdProxyShapeSceneIndexRefPtr MayaUsdProxyShapeSceneIndex::New( + const MAYAUSDAPI_NS::ProxyStage& proxyStage, + const HdSceneIndexBaseRefPtr& sceneIndexChainLastElement, + const UsdImagingStageSceneIndexRefPtr& usdImagingStageSceneIndex, + const MObjectHandle& dagNodeHandle, + const PXR_NS::SdfPath& prefix) { - return TfCreateRefPtr(new MayaUsdProxyShapeSceneIndex(proxyStage, sceneIndexChainLastElement, usdImagingStageSceneIndex, dagNodeHandle)); + return TfCreateRefPtr(new MayaUsdProxyShapeSceneIndex(proxyStage, sceneIndexChainLastElement, usdImagingStageSceneIndex, dagNodeHandle, prefix)); } void MayaUsdProxyShapeSceneIndex::UpdateTime() diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mhMayaUsdProxyShapeSceneIndex.h b/lib/mayaHydra/hydraExtensions/sceneIndex/mhMayaUsdProxyShapeSceneIndex.h index c3dc8ea25f..d26bee08dc 100644 --- a/lib/mayaHydra/hydraExtensions/sceneIndex/mhMayaUsdProxyShapeSceneIndex.h +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mhMayaUsdProxyShapeSceneIndex.h @@ -69,16 +69,19 @@ class MayaUsdProxyShapeSceneIndex : public HdSingleInputFilteringSceneIndexBase New(const MAYAUSDAPI_NS::ProxyStage& proxyStage, const HdSceneIndexBaseRefPtr& sceneIndexChainLastElement, const UsdImagingStageSceneIndexRefPtr& usdImagingStageSceneIndex, - const MObjectHandle& dagNodeHandle); + const MObjectHandle& dagNodeHandle, + const PXR_NS::SdfPath& prefix + ); // From HdSceneIndexBase HdSceneIndexPrim GetPrim(const SdfPath& primPath) const override; SdfPathVector GetChildPrimPaths(const SdfPath& primPath) const override; - MayaUsdProxyShapeSceneIndex(const MAYAUSDAPI_NS::ProxyStage& proxyStage, - const HdSceneIndexBaseRefPtr& sceneIndexChainLastElement, - const UsdImagingStageSceneIndexRefPtr& usdImagingStageSceneIndex, - const MObjectHandle& dagNodeHandle); + MayaUsdProxyShapeSceneIndex(const MAYAUSDAPI_NS::ProxyStage& proxyStage, + const HdSceneIndexBaseRefPtr& sceneIndexChainLastElement, + const UsdImagingStageSceneIndexRefPtr& usdImagingStageSceneIndex, + const MObjectHandle& dagNodeHandle, + const PXR_NS::SdfPath& prefix); virtual ~MayaUsdProxyShapeSceneIndex(); @@ -113,6 +116,7 @@ class MayaUsdProxyShapeSceneIndex : public HdSingleInputFilteringSceneIndexBase TfNotice::Key _stageInvalidateNoticeKey; TfNotice::Key _objectsChangedNoticeKey; long int _nbPopulateCalls{0}; + PXR_NS::SdfPath _prefix; }; } // namespace MAYAHYDRA_NS_DEF diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/registration.cpp b/lib/mayaHydra/hydraExtensions/sceneIndex/registration.cpp index 02232e3581..877ccc20c2 100644 --- a/lib/mayaHydra/hydraExtensions/sceneIndex/registration.cpp +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/registration.cpp @@ -14,16 +14,14 @@ // limitations under the License. // -#include "mayaHydraLib/hydraUtils.h" +#include "mayaHydraLib/mixedUtils.h" #include "mayaHydraLib/sceneIndex/registration.h" #include "mayaHydraLib/sceneIndex/mhMayaUsdProxyShapeSceneIndex.h" #include #include #include -#ifdef CODE_COVERAGE_WORKAROUND #include -#endif #include #include @@ -104,13 +102,6 @@ HdDataSourceBaseHandle createInstanceSelectionDataSource(const SdfPath& instance return HdDataSourceBase::Cast(selectionBuilder.Build()); } -HdDataSourceBaseHandle createFullPrimSelectionDataSource() -{ - HdSelectionSchema::Builder selectionBuilder; - selectionBuilder.SetFullySelected(HdRetainedTypedSampledDataSource::New(true)); - return HdDataSourceBase::Cast(selectionBuilder.Build()); -} - /// \class PathInterfaceSceneIndex /// /// Implement the path interface for plugin scene indices. @@ -170,7 +161,7 @@ class PathInterfaceSceneIndex : public Fvp::PathInterfaceSceneIndexBase const auto lastComponentString = secondSegment.components().back().string(); HdDataSourceBaseHandle selectionDataSource = lastComponentIsNumeric ? createInstanceSelectionDataSource(primPath, std::stoi(lastComponentString)) - : createFullPrimSelectionDataSource(); + : Fvp::createFullySelectedDataSource(); Fvp::PrimSelections primSelections({{primPath, selectionDataSource}}); // Propagate selection to propagated prototypes @@ -386,16 +377,7 @@ bool MayaHydraSceneIndexRegistry::_RemoveSceneIndexForNode(const MObject& dagNod void MayaHydraSceneIndexRegistry::_AddSceneIndexForNode(MObject& dagNode) { - constexpr char kSceneIndexPluginSuffix[] = {"_PluginNode"}; const MayaHydraSceneIndexRegistrationPtr registration(new MayaUsdSceneIndexRegistration()); - MFnDependencyNode dependNodeFn(dagNode); - // To match plugin TfType registration, name must begin with upper case. - const std::string sceneIndexPluginName([&](){ - std::string name = dependNodeFn.typeName().asChar(); - name[0] = toupper(name[0]); - name += kSceneIndexPluginSuffix; - return name;}()); - const TfToken sceneIndexPluginId(sceneIndexPluginName); MStatus status; MDagPath dagPath(MDagPath::getAPathTo(dagNode, &status)); @@ -404,20 +386,9 @@ void MayaHydraSceneIndexRegistry::_AddSceneIndexForNode(MObject& dagNode) } registration->dagNode = MObjectHandle(dagNode); - - // Create a unique scene index path prefix by starting with the - // Dag node name, and checking for uniqueness under the scene - // index plugin parent rprim. If not unique, add an - // incrementing numerical suffix until it is. - const auto sceneIndexPluginPath = SdfPath::AbsoluteRootPath().AppendChild(sceneIndexPluginId); - const auto newName = uniqueChildName( - _renderIndexProxy->GetMergingSceneIndex(), - sceneIndexPluginPath, - SanitizeNameForSdfPath(dependNodeFn.name().asChar()) - ); - - registration->sceneIndexPathPrefix = sceneIndexPluginPath.AppendChild(newName); - + registration->sceneIndexPathPrefix = sceneIndexPathPrefix( + _renderIndexProxy->GetMergingSceneIndex(), dagNode); + #ifdef MAYAHYDRALIB_MAYAUSDAPI_ENABLED //We receive only dag nodes of type MayaUsdProxyShapeNode @@ -441,24 +412,39 @@ void MayaHydraSceneIndexRegistry::_AddSceneIndexForNode(MObject& dagNode) HdSceneIndexBaseRefPtr finalSceneIndex = nullptr; UsdImagingStageSceneIndexRefPtr stageSceneIndex = nullptr; + // We are explicitly adding a prefixing scene index just downstream (after) + // the MayaUsdProxyShapeSceneIndex. We don't want to automatically add an + // additional prefixing scene index to the PathInterfaceSceneIndex (which + // is downstream of the prefixing stream index), which would double the + // prefix. Therefore, set the scene index prefix here to be 'null' (the + // absolute root path). PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBaseRefPtr dataProducerSceneIndexData = Fvp::DataProducerSceneIndexInterfaceImp::get().addUsdStageSceneIndex(createInfo, finalSceneIndex, stageSceneIndex, - registration->sceneIndexPathPrefix, (void*)&dagNode); + SdfPath::AbsoluteRootPath(), (void*)&dagNode); if (nullptr == dataProducerSceneIndexData || nullptr == finalSceneIndex || nullptr == stageSceneIndex){ TF_CODING_ERROR("Error (nullptr == dataProducerSceneIndexData || nullptr == finalSceneIndex || nullptr == stageSceneIndex) !"); } - //Create maya usd proxy shape scene index, since this scene index contains maya data, it cannot be added by the flow viewport API - auto mayaUsdProxyShapeSceneIndex = MAYAHYDRA_NS_DEF::MayaUsdProxyShapeSceneIndex::New(proxyStage, finalSceneIndex, stageSceneIndex, MObjectHandle(dagNode)); + // Create Maya USD proxy shape scene index. Since this scene index + // contains Maya data, it cannot be added by the Flow Viewport API. + // Pass in the scene index prefix for the proxy shape scene index, so it + // can register a pick handler. + auto mayaUsdProxyShapeSceneIndex = MAYAHYDRA_NS_DEF::MayaUsdProxyShapeSceneIndex::New(proxyStage, finalSceneIndex, stageSceneIndex, MObjectHandle(dagNode), registration->sceneIndexPathPrefix); registration->pluginSceneIndex = mayaUsdProxyShapeSceneIndex; registration->interpretRprimPathFn = &(MAYAHYDRA_NS_DEF::MayaUsdProxyShapeSceneIndex::InterpretRprimPath); mayaUsdProxyShapeSceneIndex->Populate(); + // This sets the required prefix just downstream (after) the + // MayaUsdProxyShapeSceneIndex, as required. auto pfsi = HdPrefixingSceneIndex::New( registration->pluginSceneIndex, registration->sceneIndexPathPrefix); - //Add the PathInterfaceSceneIndex which must be the last scene index, it is used for selection highlighting + // Add the PathInterfaceSceneIndex which must be the last scene index, it + // is used by selection highlighting. The scene index prefix is passed in + // not to add in a prefix, which is done explicitly by the prefixing scene + // index above. Rather, it is so the path interface scene index can build + // the scene index path from an application path. registration->rootSceneIndex = PathInterfaceSceneIndex::New( pfsi, registration->sceneIndexPathPrefix, diff --git a/lib/mayaHydra/mayaPlugin/renderOverride.cpp b/lib/mayaHydra/mayaPlugin/renderOverride.cpp index 144916ac48..33c43e2f1e 100644 --- a/lib/mayaHydra/mayaPlugin/renderOverride.cpp +++ b/lib/mayaHydra/mayaPlugin/renderOverride.cpp @@ -29,6 +29,8 @@ #include #include +#include +#include #include #ifdef CODE_COVERAGE_WORKAROUND @@ -114,273 +116,19 @@ int _profilerCategory = MProfiler::addCategory( "MtohRenderOverride (mayaHydra)", "Events from mayaHydra render override"); -PXR_NAMESPACE_OPEN_SCOPE - -// Copy-pasted and adapted from maya-usd's -// https://github.com/Autodesk/maya-usd/blob/dev/lib/mayaUsd/base/tokens.h -// https://github.com/Autodesk/maya-usd/blob/dev/lib/mayaUsd/base/tokens.cpp - -// Tokens that are used as picking optionVars in MayaUSD -// -// clang-format off -#define MAYAUSD_PICK_OPTIONVAR_TOKENS \ - /* The kind to be selected when viewport picking. */ \ - /* After resolving the picked prim, a search from */ \ - /* that prim up the USD namespace hierarchy will */ \ - /* be performed looking for a prim that matches */ \ - /* the kind in the optionVar. If no prim matches, */ \ - /* or if the selection kind is unspecified or */ \ - /* empty, the exact prim picked in the viewport */ \ - /* is selected. */ \ - ((SelectionKind, "mayaUsd_SelectionKind")) \ - /* The method to use to resolve viewport picking */ \ - /* when the picked object is a point instance. */ \ - /* The default behavior is "PointInstancer" which */ \ - /* will resolve to the PointInstancer prim that */ \ - /* generated the point instance. The optionVar */ \ - /* can also be set to "Instances" which will */ \ - /* resolve to individual point instances, or to */ \ - /* "Prototypes" which will resolve to the prim */ \ - /* that is being instanced by the point instance. */ \ - ((PointInstancesPickMode, "mayaUsd_PointInstancesPickMode")) \ -// clang-format on - -TF_DEFINE_PRIVATE_TOKENS(MayaUsdPickOptionVars, MAYAUSD_PICK_OPTIONVAR_TOKENS); - -// Copy-pasted and adapted from maya-usd's -// https://github.com/Autodesk/maya-usd/blob/dev/lib/mayaUsd/render/vp2RenderDelegate/proxyRenderDelegate.h -// https://github.com/Autodesk/maya-usd/blob/dev/lib/mayaUsd/render/vp2RenderDelegate/proxyRenderDelegate.cpp - -// clang-format off -TF_DEFINE_PRIVATE_TOKENS( - _pointInstancesPickModeTokens, - - (PointInstancer) - (Instances) - (Prototypes) -); -// clang-format on - -PXR_NAMESPACE_CLOSE_SCOPE +using namespace MayaHydra; namespace { PXR_NAMESPACE_USING_DIRECTIVE -static const SdfPath MAYA_NATIVE_ROOT = SdfPath("/MayaHydraViewportRenderer"); - -// Copy pasted from -// https://github.com/Autodesk/maya-usd/blob/dev/lib/mayaUsd/render/vp2RenderDelegate/proxyRenderDelegate.cpp - -//! \brief Query the Kind to be selected from viewport. -//! \return A Kind token (https://graphics.pixar.com/usd/docs/api/kind_page_front.html). If the -//! token is empty or non-existing in the hierarchy, the exact prim that gets picked -//! in the viewport will be selected. -TfToken GetSelectionKind() -{ - static const MString kOptionVarName(MayaUsdPickOptionVars->SelectionKind.GetText()); - - if (MGlobal::optionVarExists(kOptionVarName)) { - MString optionVarValue = MGlobal::optionVarStringValue(kOptionVarName); - return TfToken(optionVarValue.asChar()); - } - return TfToken(); -} - -//! \brief Returns the prim or an ancestor of it that is of the given kind. -// -// If neither the prim itself nor any of its ancestors above it in the -// namespace hierarchy have an authored kind that matches, an invalid null -// prim is returned. -UsdPrim GetPrimOrAncestorWithKind(const UsdPrim& prim, const TfToken& kind) -{ - UsdPrim iterPrim = prim; - TfToken primKind; - - while (iterPrim) { - if (UsdModelAPI(iterPrim).GetKind(&primKind) && KindRegistry::IsA(primKind, kind)) { - break; - } - - iterPrim = iterPrim.GetParent(); - } - - return iterPrim; -} - -//! Pick resolution behavior to use when the picked object is a point instance. -enum UsdPointInstancesPickMode -{ - //! The PointInstancer prim that generated the point instance is picked. If - //! multiple nested PointInstancers are involved, the top-level - //! PointInstancer is the one picked. If a selection kind is specified, the - //! traversal up the hierarchy looking for a kind match will begin at that - //! PointInstancer. - PointInstancer = 0, - //! The specific point instance is picked. These are represented as - //! UsdSceneItems with UFE paths to a PointInstancer prim and a non-negative - //! instanceIndex for the specific point instance. In this mode, any setting - //! for selection kind is ignored. - Instances, - //! The prototype being instanced by the point instance is picked. If a - //! selection kind is specified, the traversal up the hierarchy looking for - //! a kind match will begin at the prototype prim. - Prototypes -}; - -//! \brief Query the pick mode to use when picking point instances in the viewport. -//! \return A UsdPointInstancesPickMode enum value indicating the pick mode behavior -//! to employ when the picked object is a point instance. -//! -//! This function retrieves the value for the point instances pick mode optionVar -//! and converts it into a UsdPointInstancesPickMode enum value. If the optionVar -//! has not been set or otherwise has an invalid value, the default pick mode of -//! PointInstancer is returned. -UsdPointInstancesPickMode GetPointInstancesPickMode() -{ - static const MString kOptionVarName(MayaUsdPickOptionVars->PointInstancesPickMode.GetText()); - - auto pickMode = UsdPointInstancesPickMode::PointInstancer; - - if (MGlobal::optionVarExists(kOptionVarName)) { - const TfToken pickModeToken(MGlobal::optionVarStringValue(kOptionVarName).asChar()); - - if (pickModeToken == _pointInstancesPickModeTokens->Instances) { - pickMode = UsdPointInstancesPickMode::Instances; - } else if (pickModeToken == _pointInstancesPickModeTokens->Prototypes) { - pickMode = UsdPointInstancesPickMode::Prototypes; - } - } - - return pickMode; -} - -struct PickInput { - PickInput( - const HdxPickHit& pickHitArg, - const MHWRender::MSelectionInfo& pickInfoArg - ) : pickHit(pickHitArg), pickInfo(pickInfoArg) {} - - const HdxPickHit& pickHit; - const MHWRender::MSelectionInfo& pickInfo; -}; - -// Picking output can go either to the UFE representation of the Maya selection -// (which supports non-Maya objects), or the classic MSelectionList -// representation of the Maya selection (which only supports Maya objects). It -// is up to the implementer of the pick handler to decide which is used. If the -// Maya selection is used, there must be a world space hit point in one to one -// correspondence with each Maya selection item placed into the MSelectionList. -struct PickOutput { - PickOutput( - MSelectionList& mayaSn, - MPointArray& worldSpaceHitPts, - const Ufe::NamedSelection::Ptr& ufeSn - ) : mayaSelection(mayaSn), mayaWorldSpaceHitPts(worldSpaceHitPts), - ufeSelection(ufeSn) {} - - MSelectionList& mayaSelection; - MPointArray& mayaWorldSpaceHitPts; - const Ufe::NamedSelection::Ptr& ufeSelection; -}; - -// The SdfPath is in the original data model scene (USD), not in -// the scene index scene. -using HitPath = std::tuple; - -SdfPath instancerPrimOrigin(const HdxInstancerContext& instancerContext) -{ - // When USD prims are converted to Hydra prims (including point instancers), - // they are given a prim origin data source which provides the information - // as to which prim in the USD data model produced the rprim in the Hydra - // scene index scene. This is what is used here to provide the Hydra scene - // path to USD scene path picking to selection mapping. - auto schema = HdPrimOriginSchema(instancerContext.instancerPrimOrigin); - if (!schema) { - return {}; - } - - return schema.GetOriginPath(HdPrimOriginSchemaTokens->scenePath); -} - -HitPath pickInstance( - const HdxPrimOriginInfo& primOrigin, const HdxPickHit& hit -) -{ - // We match VP2 behavior and return the instance on the top-level instancer. - const auto& instancerContext = primOrigin.instancerContexts.front(); - return { - instancerPrimOrigin(instancerContext), instancerContext.instanceId}; -} - -HitPath pickPrototype( - const HdxPrimOriginInfo& primOrigin, const HdxPickHit& hit -) -{ - // The prototype path is the prim origin path in the USD data model. - return {primOrigin.GetFullPath(), -1}; -} - -HitPath pickInstancer( - const HdxPrimOriginInfo& primOrigin, const HdxPickHit& hit -) -{ - // To return the top-level instancer, we use the first instancer context - // prim origin. To return the innermost instancer, we would use the last - // instancer context prim origin. - return {instancerPrimOrigin(primOrigin.instancerContexts.front()), -1}; -} - -Ufe::Path usdPathToUfePath( - const MayaHydraSceneIndexRegistrationPtr& registration, - const SdfPath& usdPath -) -{ - return registration ? registration->interpretRprimPathFn( - registration->pluginSceneIndex, usdPath) : Ufe::Path(); -} +const SdfPath MAYA_NATIVE_ROOT = SdfPath("/MayaHydraViewportRenderer"); inline bool areDifferentForOneOfTheseBits(unsigned int val1, unsigned int val2, unsigned int bitsToTest) { return ((val1 & bitsToTest) != (val2 & bitsToTest)); } -} - -PXR_NAMESPACE_OPEN_SCOPE - -class MtohRenderOverride::PickHandlerBase { -public: - - virtual bool handlePickHit( - const PickInput& pickInput, PickOutput& pickOutput - ) const = 0; - -protected: - - PickHandlerBase(MtohRenderOverride& renderOverride) : - _renderOverride(renderOverride) {} - - MayaHydraSceneIndexRefPtr mayaSceneIndex() const { - return _renderOverride._mayaHydraSceneIndex; - } - - std::shared_ptr - sceneIndexRegistry() const { - return _renderOverride._sceneIndexRegistry; - } - - HdRenderIndex* renderIndex() const { return _renderOverride._renderIndex; } - -private: - - MtohRenderOverride& _renderOverride; -}; - -PXR_NAMESPACE_CLOSE_SCOPE - -namespace { - // Replace the builtin and fixed colorize selection and selection tasks from // Hydra with our own Flow Viewport selection task. The Hydra tasks are not // configurable and cannot be replaced by plugin behavior. Currently, the Flow @@ -408,152 +156,6 @@ void replaceSelectionTask(PXR_NS::HdTaskSharedPtrVector* tasks) *found = HdTaskSharedPtr(new Fvp::SelectionTask); } -class MayaPickHandler : public MtohRenderOverride::PickHandlerBase { -public: - - MayaPickHandler(MtohRenderOverride& renderOverride) : - PickHandlerBase(renderOverride) {} - - bool handlePickHit( - const PickInput& pickInput, PickOutput& pickOutput - ) const override - { - if (!mayaSceneIndex()) { - TF_FATAL_ERROR("Picking called while no Maya scene index exists"); - return false; - } - - // Maya does not create Hydra instances, so if the pick hit instancer - // ID isn't empty, it's not a Maya pick hit. - if (!pickInput.pickHit.instancerId.IsEmpty()) { - return false; - } - - return mayaSceneIndex()->AddPickHitToSelectionList( - pickInput.pickHit, pickInput.pickInfo, - pickOutput.mayaSelection, pickOutput.mayaWorldSpaceHitPts - ); - } -}; - -class UsdPickHandler : public MtohRenderOverride::PickHandlerBase { -public: - - UsdPickHandler(MtohRenderOverride& renderOverride) : - PickHandlerBase(renderOverride) {} - - // Return the closest path and the instance index in the scene index scene - // that corresponds to the pick hit. If the pick hit is not an instance, - // the instance index will be -1. - HitPath hitPath(const HdxPickHit& hit) const { - auto primOrigin = HdxPrimOriginInfo::FromPickHit( - renderIndex(), hit); - - if (hit.instancerId.IsEmpty()) { - return {primOrigin.GetFullPath(), -1}; - } - - // If there is a Hydra instancer, distinguish between native instancing - // (implicit USD prototype created by USD itself) and point instancing - // (explicitly authored USD prototypes). As per HdxInstancerContext - // documentation: - // - // [...] "exactly one of instancePrimOrigin or instancerPrimOrigin will - // contain data depending on whether the instancing at the current - // level was implicit or not, respectively." - const auto& instancerContext = primOrigin.instancerContexts.front(); - - if (instancerContext.instancePrimOrigin) { - // Implicit prototype instancing (i.e. USD native instancing). - auto schema = HdPrimOriginSchema(instancerContext.instancePrimOrigin); - if (!TF_VERIFY(schema, "Cannot build prim origin schema for USD native instance.")) { - return {SdfPath(), -1}; - } - return {schema.GetOriginPath(HdPrimOriginSchemaTokens->scenePath), -1}; - } - - // Explicit prototype instancing (i.e. USD point instancing). - std::function pickFn[] = {pickInstancer, pickInstance, pickPrototype}; - - // Retrieve pick mode from mayaUsd optionVar, to see if we're picking - // instances, the instancer itself, or the prototype instanced by the - // point instance. - return pickFn[GetPointInstancesPickMode()](primOrigin, hit); - } - - bool handlePickHit( - const PickInput& pickInput, PickOutput& pickOutput - ) const override - { - if (!sceneIndexRegistry()) { - TF_FATAL_ERROR("Picking called while no scene index registry exists"); - return false; - } - - if (!renderIndex()) { - TF_FATAL_ERROR("Picking called while no render index exists"); - return false; - } - - auto registration = sceneIndexRegistry()->GetSceneIndexRegistrationForRprim(pickInput.pickHit.objectId); - - if (!registration) { - return false; - } - - // For the USD pick handler pick results are directly returned with USD - // scene paths, so no need to remove scene index plugin path prefix. - const auto& [pickedUsdPath, instanceNdx] = hitPath(pickInput.pickHit); - - const auto pickedMayaPath = usdPathToUfePath(registration, pickedUsdPath); - const auto snMayaPath = - // As per https://stackoverflow.com/questions/46114214 - // structured bindings cannot be captured by a lambda in C++ 17, - // so pass in pickedUsdPath and instanceNdx as lambda arguments. - [&pickedMayaPath, ®istration]( - const SdfPath& pickedUsdPath, int instanceNdx) { - - if (instanceNdx >= 0) { - // Point instance: add the instance index to the path. - // Appending a numeric component to the path to identify a - // point instance cannot be done on the picked SdfPath, as - // numeric path components are not allowed by SdfPath. Do so - // here with Ufe::Path, which has no such restriction. - return pickedMayaPath + std::to_string(instanceNdx); - } - - // Not an instance: adjust picked path for selection kind. - auto snKind = GetSelectionKind(); - if (snKind.IsEmpty()) { - return pickedMayaPath; - } - - // Get the prim from the stage and path, to access the - // UsdModelAPI for the prim. - auto proxyShapeObj = registration->dagNode.object(); - if (proxyShapeObj.isNull()) { - TF_FATAL_ERROR("No mayaUsd proxy shape object corresponds to USD pick"); - return pickedMayaPath; - } - - MayaUsdAPI::ProxyStage proxyStage{proxyShapeObj}; - auto prim = proxyStage.getUsdStage()->GetPrimAtPath(pickedUsdPath); - prim = GetPrimOrAncestorWithKind(prim, snKind); - const auto usdPath = prim ? prim.GetPath() : pickedUsdPath; - - return usdPathToUfePath(registration, usdPath); - }(pickedUsdPath, instanceNdx); - - auto si = Ufe::Hierarchy::createItem(snMayaPath); - if (!si) { - return false; - } - - pickOutput.ufeSelection->append(si); - return true; - } -}; - } PXR_NAMESPACE_OPEN_SCOPE @@ -645,13 +247,6 @@ MtohRenderOverride::MtohRenderOverride(const MtohRendererDescription& desc) , _ufeSn(Ufe::NamedSelection::get("MayaSelectTool")) , _mayaSelectionObserver(std::make_shared(*this)) , _isUsingHdSt(desc.rendererName == MtohTokens->HdStormRendererPlugin) - // unique_ptr is not copyable, so can't use initializer_list, which copies. - , _pickHandlers([this](){ - std::vector> v; - v.push_back(std::make_unique(*this)); - v.push_back(std::make_unique(*this)); - return v; - }()) { TF_DEBUG(MAYAHYDRALIB_RENDEROVERRIDE_RESOURCES) .Msg( @@ -1327,6 +922,10 @@ void MtohRenderOverride::_InitHydraResources(const MHWRender::MDrawContext& draw _sceneIndexRegistry.reset(new MayaHydraSceneIndexRegistry(_renderIndexProxy)); } + // We provide the pick context for pick handlers, so set the pick handler + // registry accordingly. + PickHandlerRegistry::Instance().SetPickContext(this); + //Create internal scene indices chain _inputSceneIndexOfFilteringSceneIndicesChain = _renderIndexProxy->GetMergingSceneIndex(); @@ -1436,6 +1035,9 @@ void MtohRenderOverride::ClearHydraResources(bool fullReset) _viewport = GfVec4d(0, 0, 0, 0); _initializationSucceeded = false; _initializationAttempted = false; + + // Remove the pick context from pick handlers. + PickHandlerRegistry::Instance().SetPickContext(nullptr); } void MtohRenderOverride::_CreateSceneIndicesChainAfterMergingSceneIndex(const MHWRender::MDrawContext& drawContext) @@ -1696,27 +1298,23 @@ void MtohRenderOverride::_PopulateSelectionList( return; } - PickOutput pickOutput(selectionList, worldSpaceHitPts, _ufeSn); + PickHandler::Output pickOutput(selectionList, worldSpaceHitPts, _ufeSn); MStatus status; for (const HdxPickHit& hit : hits) { - PickInput pickInput(hit, selectInfo); + PickHandler::Input pickInput(hit, selectInfo); - _PickHandler(hit)->handlePickHit(pickInput, pickOutput); + auto pickHandler = _PickHandler(hit); + if (TF_VERIFY(pickHandler, "No pick handler found for pick hit %s!", hit.objectId.GetText())) { + _PickHandler(hit)->handlePickHit(pickInput, pickOutput); + } } } -const MtohRenderOverride::PickHandlerBase* +PickHandlerConstPtr MtohRenderOverride::_PickHandler(const HdxPickHit& pickHit) const { - // As of 19-Mar-2024, we only have two kinds of pick handlers, one for Maya - // objects, the other for USD objects. USD objects are generated by - // mayaUsd proxy shape Maya nodes, which add one registration to the - // MayaHydraSceneIndexRegistry per proxy shape node. We use the trivial - // strategy of choosing the USD pick handler if there is a registration - // that matches the pick hit, otherwise the Maya pick handler is used. - // This will need to be revised for extensibility. - return _sceneIndexRegistry->GetSceneIndexRegistrationForRprim(pickHit.objectId) ? _pickHandlers[1].get() : _pickHandlers[0].get(); + return PickHandlerRegistry::Instance().GetHandler(pickHit.objectId); } void MtohRenderOverride::_PickByRegion( @@ -1973,4 +1571,15 @@ bool MtohRenderOverride::_NeedToRecreateTheSceneIndicesChain(unsigned int curren return false; } +std::shared_ptr +MtohRenderOverride::sceneIndexRegistry() const +{ + return _sceneIndexRegistry; +} + +HdRenderIndex* MtohRenderOverride::renderIndex() const +{ + return _renderIndex; +} + PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/mayaPlugin/renderOverride.h b/lib/mayaHydra/mayaPlugin/renderOverride.h index 409b6ebed6..a155eb9840 100644 --- a/lib/mayaHydra/mayaPlugin/renderOverride.h +++ b/lib/mayaHydra/mayaPlugin/renderOverride.h @@ -39,6 +39,8 @@ #include #include #include +#include +#include #include #include @@ -89,12 +91,10 @@ using HdxPickHitVector = std::vector; /*! \brief MtohRenderOverride is a rendering override class for the viewport to use Hydra instead of * VP2.0. */ -class MtohRenderOverride : public MHWRender::MRenderOverride +class MtohRenderOverride : public MHWRender::MRenderOverride, + public MayaHydra::PickContext { public: - // Picking support. - class PickHandlerBase; - friend PickHandlerBase; MtohRenderOverride(const MtohRendererDescription& desc); ~MtohRenderOverride() override; @@ -150,6 +150,12 @@ class MtohRenderOverride : public MHWRender::MRenderOverride MSelectionList& selectionList, MPointArray& worldSpaceHitPts) override; + // MayaHydra::PickContext overrides. + std::shared_ptr + sceneIndexRegistry() const override; + + HdRenderIndex* renderIndex() const override; + private: typedef std::pair PanelCallbacks; typedef std::vector PanelCallbacksList; @@ -202,7 +208,7 @@ class MtohRenderOverride : public MHWRender::MRenderOverride // Determine the pick handler which should handle a pick hit, to transform // the pick hit into a selection. - const PickHandlerBase* _PickHandler(const HdxPickHit& hit) const; + MayaHydra::PickHandlerConstPtr _PickHandler(const HdxPickHit& hit) const; // Callbacks static void _ClearHydraCallback(void* data); @@ -301,9 +307,6 @@ class MtohRenderOverride : public MHWRender::MRenderOverride unsigned int _oldDisplayStyle {0}; bool _useDefaultMaterial; bool _xRayEnabled; - - // Picking support. - const std::vector> _pickHandlers; }; PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/mayaHydra/ufeExtensions/CMakeLists.txt b/lib/mayaHydra/ufeExtensions/CMakeLists.txt index c1e4bc80c7..4fa29d433e 100644 --- a/lib/mayaHydra/ufeExtensions/CMakeLists.txt +++ b/lib/mayaHydra/ufeExtensions/CMakeLists.txt @@ -7,12 +7,14 @@ add_library(${TARGET_NAME} SHARED) # ----------------------------------------------------------------------------- target_sources(${TARGET_NAME} PRIVATE - Global.cpp + cvtTypeUtils.cpp + Global.cpp ) set(HEADERS api.h - Global.h + cvtTypeUtils.h + Global.h ) # ----------------------------------------------------------------------------- @@ -64,15 +66,15 @@ endif() # ----------------------------------------------------------------------------- target_link_libraries(${TARGET_NAME} - PUBLIC - sdf - tf - usd - ${MAYA_LIBRARIES} - PRIVATE + PUBLIC + sdf + tf + usd + ${MAYA_LIBRARIES} + PRIVATE $<$:${UFE_LIBRARY}> - $<$:hio> - ${PYTHON_LIBRARIES} + $<$:hio> + ${PYTHON_LIBRARIES} ) # ----------------------------------------------------------------------------- diff --git a/lib/mayaHydra/ufeExtensions/Global.h b/lib/mayaHydra/ufeExtensions/Global.h index 405dab75d2..3ee3cb7c6e 100644 --- a/lib/mayaHydra/ufeExtensions/Global.h +++ b/lib/mayaHydra/ufeExtensions/Global.h @@ -34,7 +34,7 @@ namespace UfeExtensions { using SdfPath = PXR_NS::SdfPath; //Copied from usdImaging/usdImaging/delegate.h -static constexpr int ALL_INSTANCES = -1; +constexpr int ALL_INSTANCES = -1; UFEEXTENSIONS_API Ufe::Rtid getMayaRunTimeId(); diff --git a/lib/mayaHydra/ufeExtensions/cvtTypeUtils.cpp b/lib/mayaHydra/ufeExtensions/cvtTypeUtils.cpp new file mode 100644 index 0000000000..7f763660c7 --- /dev/null +++ b/lib/mayaHydra/ufeExtensions/cvtTypeUtils.cpp @@ -0,0 +1,16 @@ +// +// 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 "cvtTypeUtils.h" diff --git a/lib/mayaHydra/ufeExtensions/cvtTypeUtils.h b/lib/mayaHydra/ufeExtensions/cvtTypeUtils.h new file mode 100644 index 0000000000..f1726a56f6 --- /dev/null +++ b/lib/mayaHydra/ufeExtensions/cvtTypeUtils.h @@ -0,0 +1,65 @@ +// +// 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. +// + +// Parts from +// https://github.com/Autodesk/maya-usd/blob/dev/lib/usdUfe/ufe/Utils.h + +#ifndef UFEEXTENSIONS_CVT_TYPE_UTILS_H +#define UFEEXTENSIONS_CVT_TYPE_UTILS_H + +#include + +#include + +#include + +#include // memcpy + +// Type conversion utilities. + +namespace UfeExtensions { + +//! Copy the argument matrix into the return matrix. +inline Ufe::Matrix4d toUfe(const PXR_NS::GfMatrix4d& src) +{ + Ufe::Matrix4d dst; + std::memcpy(&dst.matrix[0][0], src.GetArray(), sizeof(double) * 16); + return dst; +} + +//! Copy the argument matrix into the return matrix. +inline PXR_NS::GfMatrix4d toUsd(const Ufe::Matrix4d& src) +{ + PXR_NS::GfMatrix4d dst; + std::memcpy(dst.GetArray(), &src.matrix[0][0], sizeof(double) * 16); + return dst; +} + +//! Copy the argument vector into the return vector. +inline Ufe::Vector3d toUfe(const PXR_NS::GfVec3d& src) +{ + return Ufe::Vector3d(src[0], src[1], src[2]); +} + +//! Copy the argument vector into the return vector. +inline PXR_NS::GfVec3d toUsd(const Ufe::Vector3d& src) +{ + return PXR_NS::GfVec3d(src.x(), src.y(), src.z()); +} + +} // namespace UfeExtensions + +#endif diff --git a/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt b/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt index 05c32f14c3..57dfca4d76 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt +++ b/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt @@ -48,10 +48,13 @@ set(INTERACTIVE_TEST_SCRIPT_FILES testViewportFilters.py cpp/testColorPreferences.py cpp/testCppFramework.py + cpp/testDataProducerExample.py cpp/testMayaSceneFlattening.py cpp/testMayaUsdUfeItems.py cpp/testMergingSceneIndex.py cpp/testPathInterface.py + cpp/testPathMapperRegistry.py + cpp/testPickHandlerRegistry.py cpp/testSelectionSceneIndex.py cpp/testWireframeSelectionHighlightSceneIndex.py cpp/testFlowViewportAPIViewportInformation.py diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt b/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt index 2d96417632..5004d038b4 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt @@ -34,7 +34,10 @@ target_sources(${TARGET_NAME} testMeshAdapterTransform.cpp testFlowViewportAPIFilterPrims.cpp testSceneCorrectness.cpp + testSelection.cpp testPrimInstancing.cpp + testPathMapperRegistry.cpp + testPickHandlerRegistry.cpp testPicking.cpp testUsdAnim.cpp testUsdPointInstancePicking.cpp diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testDataProducerExample.py b/test/lib/mayaUsd/render/mayaToHydra/cpp/testDataProducerExample.py new file mode 100644 index 0000000000..400de4a8a3 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testDataProducerExample.py @@ -0,0 +1,137 @@ +# 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 maya.cmds as cmds +import fixturesUtils +import mtohUtils +import ufe + +class TestDataProducerExample(mtohUtils.MayaHydraBaseTestCase): + # MayaHydraBaseTestCase.setUpClass requirement. + _file = __file__ + + _pluginsToLoad = ['mayaHydraCppTests', 'mayaHydraFlowViewportAPILocator'] + _pluginWasLoaded = {} + + def createScene(self): + self._locator = cmds.createNode('MhFlowViewportAPILocator') + cmds.setAttr(self._locator + '.numCubesX', 3) + cmds.setAttr(self._locator + '.numCubesY', 3) + cmds.setAttr(self._locator + '.numCubesZ', 3) + + @classmethod + def setUpClass(cls): + super(TestDataProducerExample, cls).setUpClass() + for p in cls._pluginsToLoad: + loaded = cmds.pluginInfo(p, q=True, loaded=True) + cls._pluginWasLoaded[p] = loaded + if not loaded: + cmds.loadPlugin(p, quiet=True) + + @classmethod + def tearDownClass(cls): + super(TestDataProducerExample, cls).tearDownClass() + # Clean out the scene to allow all plugins to unload cleanly. + cmds.file(new=True, force=True) + for p in cls._pluginsToLoad: + if not cls._pluginWasLoaded[p]: + cmds.unloadPlugin(p) + + def setUp(self): + super(TestDataProducerExample, self).setUp() + self.createScene() + cmds.refresh() + + def cube000PathString(self): + return '|transform1|' + self._locator + ',/cube_0_0_0' + + def cube222PathString(self): + return '|transform1|' + self._locator + ',/cube_2_2_2' + + def test_Pick(self): + # Pick an exterior cube to ensure we don't pick a hidden one. + cmds.mayaHydraCppTest(self.cube222PathString(), f='TestUsdPicking.pick') + + def test_Select(self): + # Make a selection, ensure the Hydra selection changes. + sn = ufe.GlobalSelection.get() + sn.clear() + + # Empty Maya selection, therefore no fully selected path in the scene + # index. + cmds.mayaHydraCppTest(f='TestSelection.fullySelectedPaths') + + item = ufe.Hierarchy.createItem(ufe.PathString.path(self.cube222PathString())) + sn.append(item) + + # Item added to the Maya selection, it should be fully selected in the + # scene index. + cmds.mayaHydraCppTest( + self.cube222PathString(), f='TestSelection.fullySelectedPaths') + + def test_Hide(self): + # Select a cube, hide it, demonstrate it's hidden. + sn = ufe.GlobalSelection.get() + sn.clear() + item = ufe.Hierarchy.createItem( + ufe.PathString.path(self.cube222PathString())) + sn.append(item) + + # Cube is not hidden yet, and is present in the scene index. + o3d = ufe.Object3d.object3d(item) + self.assertTrue(o3d.visibility()) + cmds.mayaHydraCppTest(self.cube222PathString(), f='TestHydraPrim.isFound') + + # Hide it, undo, redo. + cmds.hide() + self.assertFalse(o3d.visibility()) + cmds.mayaHydraCppTest(self.cube222PathString(), f='TestHydraPrim.isNotFound') + cmds.undo() + self.assertTrue(o3d.visibility()) + cmds.mayaHydraCppTest(self.cube222PathString(), f='TestHydraPrim.isFound') + cmds.redo() + self.assertFalse(o3d.visibility()) + cmds.mayaHydraCppTest(self.cube222PathString(), f='TestHydraPrim.isNotFound') + + def test_Move(self): + # Select a cube, move it, demonstrate it's moved. Use the cube closest + # to the parent origin, to avoid dealing with inter-cube spacing and + # cube indexing. + sn = ufe.GlobalSelection.get() + sn.clear() + item = ufe.Hierarchy.createItem( + ufe.PathString.path(self.cube000PathString())) + sn.append(item) + + # Cube hasn't moved yet. + t3d = ufe.Transform3d.transform3d(item) + + def assertTranslationAlmostEqual(expected): + self.assertEqual(t3d.translation().vector, expected) + cmds.mayaHydraCppTest(self.cube000PathString(), str(expected[0]), + str(expected[1]), str(expected[2]), + f='TestHydraPrim.translation') + + assertTranslationAlmostEqual([0, 0, 0]) + + # Move it, undo, redo. + cmds.move(3, 4, 5) + assertTranslationAlmostEqual([3, 4, 5]) + cmds.undo() + assertTranslationAlmostEqual([0, 0, 0]) + cmds.redo() + assertTranslationAlmostEqual([3, 4, 5]) + +if __name__ == '__main__': + fixturesUtils.runTests(globals()) diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testFlowViewportAPIAddPrims.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testFlowViewportAPIAddPrims.cpp index 8b1cc5a39f..9994ef4652 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/testFlowViewportAPIAddPrims.cpp +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testFlowViewportAPIAddPrims.cpp @@ -60,7 +60,7 @@ TEST(FlowViewportAPI, addPrimitives) //hydraViewportDataProducerSceneIndexExample is what will inject the 3D grid of Hydra cube mesh primitives into the viewport Fvp::DataProducerSceneIndexExample hydraViewportDataProducerSceneIndexExample; - const std::string firstCubePath (TfStringPrintf("/cube_%p0_0_0", &hydraViewportDataProducerSceneIndexExample)); + const std::string firstCubePath (TfStringPrintf("/cube_%p/cube_0_0_0", &hydraViewportDataProducerSceneIndexExample)); //Setup cube grid parameters hydraViewportDataProducerSceneIndexExample.setCubeGridParams(cubeGridParams); @@ -88,7 +88,9 @@ TEST(FlowViewportAPI, addPrimitives) hydraViewportDataProducerSceneIndexExample.setContainerNode(&parentSphereShapeMOject); //Add the data producer scene index which will create the cube grid in the viewport and the scene indices chain to handle visibility/transform updates and node delete/undelete - hydraViewportDataProducerSceneIndexExample.addDataProducerSceneIndex(); + hydraViewportDataProducerSceneIndexExample.addDataProducerSceneIndex( + SdfPath(TfStringPrintf("/cube_%p", &hydraViewportDataProducerSceneIndexExample)) +); //Setup inspector for the first viewport scene index const SceneIndicesVector& sceneIndices = GetTerminalSceneIndices(); diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testHydraPrim.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testHydraPrim.cpp index 99ce31aa27..533d344f3d 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/testHydraPrim.cpp +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testHydraPrim.cpp @@ -15,6 +15,8 @@ #include "testUtils.h" +#include + #include #include @@ -24,6 +26,20 @@ PXR_NAMESPACE_USING_DIRECTIVE using namespace MayaHydra; +namespace { + +SdfPath fromAppPath(const Ufe::Path& appPath) +{ + auto siRoot = GetTerminalSceneIndices().front(); + + // Translate the application path into a scene index path using the + // selection scene index. + const auto snSi = findSelectionSceneIndexInTree(siRoot); + return snSi->SceneIndexPath(appPath); +} + +} + TEST(TestHydraPrim, fromAppPath) { const auto& sceneIndices = GetTerminalSceneIndices(); @@ -43,3 +59,52 @@ TEST(TestHydraPrim, fromAppPath) ASSERT_FALSE(sceneIndexPath.IsEmpty()); } + +TEST(TestHydraPrim, isFound) +{ + 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])); + + const auto sceneIndexPath = fromAppPath(appPath); + + ASSERT_TRUE(siRoot->GetPrim(sceneIndexPath).dataSource); +} + +TEST(TestHydraPrim, isNotFound) +{ + 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])); + + const auto sceneIndexPath = fromAppPath(appPath); + + ASSERT_FALSE(siRoot->GetPrim(sceneIndexPath).dataSource); +} + +TEST(TestHydraPrim, translation) +{ + const auto& sceneIndices = GetTerminalSceneIndices(); + auto siRoot = sceneIndices.front(); + + auto [argc, argv] = getTestingArgs(); + ASSERT_EQ(argc, 4); + const Ufe::Path appPath{Ufe::PathString::path(argv[0])}; + const GfVec3d expectedTranslation( + std::stod(argv[1]), std::stod(argv[2]), std::stod(argv[3])); + + const auto sceneIndexPath = fromAppPath(appPath); + const auto prim = siRoot->GetPrim(sceneIndexPath); + GfMatrix4d m; + ASSERT_TRUE(MayaHydra::GetXformMatrixFromPrim(prim, m)); + const auto primTranslation = m.ExtractTranslation(); + + constexpr double epsilon{1e-7}; + ASSERT_TRUE(GfIsClose(primTranslation, expectedTranslation, epsilon)); +} diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testPathMapperRegistry.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPathMapperRegistry.cpp new file mode 100644 index 0000000000..b9c5959003 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPathMapperRegistry.cpp @@ -0,0 +1,109 @@ +// 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 + +PXR_NAMESPACE_USING_DIRECTIVE +using namespace MayaHydra; + +namespace { + +class TestPathMapper : public Fvp::PathMapper { +public: + + TestPathMapper() = default; + + static Fvp::PathMapperConstPtr create() { + return std::make_shared(); + } + + Fvp::PrimSelections + UfePathToPrimSelections(const Ufe::Path&) const override { return {}; } +}; + +} + +TEST(TestPathMapperRegistry, testRegistry) +{ + // Exercise the path mapper registry. + auto& r = Fvp::PathMapperRegistry::Instance(); + + auto dummy = TestPathMapper::create(); + + // Can't register for an empty path. + ASSERT_FALSE(r.Register(Ufe::Path(), dummy)); + + std::vector registered; + auto fooBarM = TestPathMapper::create(); + auto fooBarP = Ufe::PathString::path("|foo|bar"); + auto fooP = Ufe::PathString::path("|foo"); + + ASSERT_TRUE(r.Register(fooBarP, fooBarM)); + ASSERT_EQ(r.GetMapper(fooBarP), fooBarM); + registered.push_back(fooBarP); + + // fooBarM is the mapper for its own path and descendants, not ancestors + // or unrelated paths. + ASSERT_EQ(r.GetMapper(Ufe::PathString::path("|foo|bar|bli")), fooBarM); + ASSERT_FALSE(r.GetMapper(fooP)); + ASSERT_FALSE(r.GetMapper(Ufe::PathString::path("|bar"))); + ASSERT_FALSE(r.GetMapper(Ufe::PathString::path("|zebra"))); + + // Add mappers for siblings, legal. + auto fooBackM = TestPathMapper::create(); + auto fooRedM = TestPathMapper::create(); + auto fooBackP = Ufe::PathString::path("|foo|back"); + auto fooRedP = Ufe::PathString::path("|foo|red"); + + ASSERT_TRUE(r.Register(fooBackP, fooBackM)); + ASSERT_TRUE(r.Register(fooRedP, fooRedM)); + registered.push_back(fooBackP); + registered.push_back(fooRedP); + + ASSERT_EQ(r.GetMapper(Ufe::PathString::path("|foo|bar|bli")), fooBarM); + ASSERT_EQ(r.GetMapper(Ufe::PathString::path("|foo|back|bli")), fooBackM); + ASSERT_EQ(r.GetMapper(Ufe::PathString::path("|foo|red|bli")), fooRedM); + + // Add mappers for ancestors, descendants, illegal. + ASSERT_FALSE(r.Register(fooP, dummy)); + ASSERT_FALSE(r.Register(Ufe::PathString::path("|foo|bar|bli"), dummy)); + + // Add other mappers to the registry. + auto appleP = Ufe::PathString::path("|apple"); + auto wizardP = Ufe::PathString::path("|wizard"); + auto appleM = TestPathMapper::create(); + auto wizardM = TestPathMapper::create(); + + ASSERT_TRUE(r.Register(appleP, appleM)); + ASSERT_TRUE(r.Register(wizardP, wizardM)); + registered.push_back(appleP); + registered.push_back(wizardP); + + ASSERT_EQ(r.GetMapper(Ufe::PathString::path("|apple|pear")), appleM); + ASSERT_EQ(r.GetMapper(Ufe::PathString::path("|wizard|sorcerer")), wizardM); + + // Clean up. + for (const auto& h : registered) { + ASSERT_TRUE(r.Unregister(h)); + } +} diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testPathMapperRegistry.py b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPathMapperRegistry.py new file mode 100644 index 0000000000..24b3fdeba3 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPathMapperRegistry.py @@ -0,0 +1,29 @@ +# 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 maya.cmds as cmds +import fixturesUtils +import mtohUtils +from testUtils import PluginLoaded + +class TestPathMapperRegistry(mtohUtils.MayaHydraBaseTestCase): + # MayaHydraBaseTestCase.setUpClass requirement. + _file = __file__ + + def test_pathMapperRegistry(self): + with PluginLoaded('mayaHydraCppTests'): + cmds.mayaHydraCppTest(f="TestPathMapperRegistry.testRegistry") + +if __name__ == '__main__': + fixturesUtils.runTests(globals()) diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testPickHandlerRegistry.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPickHandlerRegistry.cpp new file mode 100644 index 0000000000..a63d92f9ed --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPickHandlerRegistry.cpp @@ -0,0 +1,109 @@ +// 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 + +PXR_NAMESPACE_USING_DIRECTIVE +using namespace MayaHydra; + +namespace { + +class TestPickHandler : public MayaHydra::PickHandler { +public: + + TestPickHandler() = default; + + static PickHandlerConstPtr create() { + return std::make_shared(); + } + + bool handlePickHit(const Input&, Output&) const override { return true; } +}; + +} + +TEST(TestPickHandlerRegistry, testRegistry) +{ + // Exercise the pick handler registry. + auto& r = PickHandlerRegistry::Instance(); + + auto dummy = TestPickHandler::create(); + + // Can't register for an empty prefix. + ASSERT_FALSE(r.Register(SdfPath(), dummy)); + + // Can't register for an absolute root prefix. + ASSERT_FALSE(r.Register(SdfPath::AbsoluteRootPath(), dummy)); + + std::vector registered; + auto fooBarH = TestPickHandler::create(); + SdfPath fooBarP("/foo/bar"); + SdfPath fooP("/foo"); + + ASSERT_TRUE(r.Register(fooBarP, fooBarH)); + ASSERT_EQ(r.GetHandler(fooBarP), fooBarH); + registered.push_back(fooBarP); + + // fooBarH is the handler for its own path and descendants, not ancestors + // or unrelated paths. + ASSERT_EQ(r.GetHandler(SdfPath("/foo/bar/bli")), fooBarH); + ASSERT_FALSE(r.GetHandler(fooP)); + ASSERT_FALSE(r.GetHandler(SdfPath("/bar"))); + ASSERT_FALSE(r.GetHandler(SdfPath("/zebra"))); + + // Add handlers for siblings, legal. + auto fooBackH = TestPickHandler::create(); + auto fooRedH = TestPickHandler::create(); + SdfPath fooBackP("/foo/back"); + SdfPath fooRedP("/foo/red"); + + ASSERT_TRUE(r.Register(fooBackP, fooBackH)); + ASSERT_TRUE(r.Register(fooRedP, fooRedH)); + registered.push_back(fooBackP); + registered.push_back(fooRedP); + + ASSERT_EQ(r.GetHandler(SdfPath("/foo/bar/bli")), fooBarH); + ASSERT_EQ(r.GetHandler(SdfPath("/foo/back/bli")), fooBackH); + ASSERT_EQ(r.GetHandler(SdfPath("/foo/red/bli")), fooRedH); + + // Add handlers for ancestors, descendants, illegal. + ASSERT_FALSE(r.Register(fooP, dummy)); + ASSERT_FALSE(r.Register(SdfPath("/foo/bar/bli"), dummy)); + + // Add handlers to the head, tail of the map. + SdfPath appleP("/apple"); + SdfPath wizardP("/wizard"); + auto appleH = TestPickHandler::create(); + auto wizardH = TestPickHandler::create(); + + ASSERT_TRUE(r.Register(appleP, appleH)); + ASSERT_TRUE(r.Register(wizardP, wizardH)); + registered.push_back(appleP); + registered.push_back(wizardP); + + ASSERT_EQ(r.GetHandler(SdfPath("/apple/pear")), appleH); + ASSERT_EQ(r.GetHandler(SdfPath("/wizard/sorcerer")), wizardH); + + // Clean up. + for (const auto& h : registered) { + ASSERT_TRUE(r.Unregister(h)); + } +} diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testPickHandlerRegistry.py b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPickHandlerRegistry.py new file mode 100644 index 0000000000..50d8535b59 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPickHandlerRegistry.py @@ -0,0 +1,29 @@ +# 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 maya.cmds as cmds +import fixturesUtils +import mtohUtils +from testUtils import PluginLoaded + +class TestPickHandlerRegistry(mtohUtils.MayaHydraBaseTestCase): + # MayaHydraBaseTestCase.setUpClass requirement. + _file = __file__ + + def test_pickHandlerRegistry(self): + with PluginLoaded('mayaHydraCppTests'): + cmds.mayaHydraCppTest(f="TestPickHandlerRegistry.testRegistry") + +if __name__ == '__main__': + fixturesUtils.runTests(globals()) diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testSelection.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testSelection.cpp new file mode 100644 index 0000000000..003039a0a9 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testSelection.cpp @@ -0,0 +1,89 @@ +// 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 + +PXR_NAMESPACE_USING_DIRECTIVE + +using namespace MayaHydra; + +// Check for correspondence between Maya selection and Hydra scene index +// selection. +TEST(TestSelection, fullySelectedPaths) +{ + const auto& sceneIndices = GetTerminalSceneIndices(); + ASSERT_GT(sceneIndices.size(), 0u); + auto siRoot = sceneIndices.front(); + + // Translate the application path into a scene index path using the + // selection scene index. + // The Flow Viewport selection scene index is in the scene index tree. + const auto snSi = findSelectionSceneIndexInTree(siRoot); + ASSERT_TRUE(snSi); + + if (testingArgsEmpty()) { + // Check for empty scene index selection. + ASSERT_TRUE(snSi->GetFullySelectedPaths().empty()); + return; + } + + // Non-empty selection. + auto [argc, argv] = getTestingArgs(); + + const Ufe::Path selected(Ufe::PathString::path(argv[0])); + + const auto sceneIndexPath = snSi->SceneIndexPath(selected); + + ASSERT_FALSE(sceneIndexPath.IsEmpty()); + + const auto prim = siRoot->GetPrim(sceneIndexPath); + ASSERT_TRUE(prim.dataSource); + + // On selection, the prim is given a selections data source. + auto dataSourceNames = prim.dataSource->GetNames(); + ASSERT_NE(std::find(dataSourceNames.begin(), dataSourceNames.end(), HdSelectionsSchemaTokens->selections), dataSourceNames.end()); + + auto snDataSource = prim.dataSource->Get(HdSelectionsSchemaTokens->selections); + ASSERT_TRUE(snDataSource); + auto selectionsSchema = HdSelectionsSchema::GetFromParent(prim.dataSource); + ASSERT_TRUE(selectionsSchema); + + // Only one selection in the selections schema. + ASSERT_EQ(selectionsSchema.GetNumElements(), 1u); + auto selectionSchema = selectionsSchema.GetElement(0); + + // Prim is fully selected. + auto ds = selectionSchema.GetFullySelected(); + ASSERT_TRUE(ds); + ASSERT_TRUE(ds->GetTypedValue(0.0f)); + + // Selection scene index says the prim is selected. + ASSERT_TRUE(snSi->IsFullySelected(sceneIndexPath)); + ASSERT_TRUE(snSi->HasFullySelectedAncestorInclusive(sceneIndexPath)); + auto fullySelectedPaths = snSi->GetFullySelectedPaths(); + ASSERT_EQ(fullySelectedPaths.size(), 1u); + ASSERT_NE(std::find(fullySelectedPaths.cbegin(), fullySelectedPaths.cend(), + sceneIndexPath), fullySelectedPaths.cend()); +} diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.cpp index 3f46e2eb92..a25a4ba00c 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.cpp +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.cpp @@ -36,6 +36,7 @@ #include #include #include +#include namespace { std::pair testingArgs{0, nullptr}; @@ -355,6 +356,13 @@ bool dataSourceMatchesReference( return outputString == referenceString; } +bool testingArgsEmpty() +{ + // See mayaHydraCppTestsCmd.cpp:constructGoogleTestArgs() documentation. + auto [argc, argv] = getTestingArgs(); + return (std::strcmp(argv[0], "dummy") == 0); +} + void mouseMoveTo(QWidget* widget, QPoint localMousePos) { QMouseEvent mouseMoveEvent( diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.h b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.h index 63a82708cc..d9f2471533 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.h +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.h @@ -407,6 +407,12 @@ class DecimalStreamingOverride { }; #endif +/** + * @brief Predicate to return if global command-line arguments are empty. + * + */ +bool testingArgsEmpty(); + /** * @brief Send a mouse move event to a widget to move the mouse at a given position. *