From ba3b9b4395af7ae5312701016a4e95119e15ee1c Mon Sep 17 00:00:00 2001 From: David Lanier Date: Thu, 23 Nov 2023 16:53:15 +0100 Subject: [PATCH] HYDRA-599 : Add data producer scene index interface in viewport API --- lib/flowViewport/API/CMakeLists.txt | 1 + .../API/fvpDataProducerSceneIndexInterface.h | 102 +++ .../API/interfacesImp/CMakeLists.txt | 2 + .../fvpDataProducerSceneIndexInterfaceImp.cpp | 230 +++++++ .../fvpDataProducerSceneIndexInterfaceImp.h | 76 +++ .../fvpInformationInterfaceImp.cpp | 2 +- .../CMakeLists.txt | 3 + ...ataProducerSceneIndexDataAbstractFactory.h | 38 ++ .../fvpDataProducerSceneIndexDataBase.cpp | 193 ++++++ .../fvpDataProducerSceneIndexDataBase.h | 112 ++++ ...ormationAndSceneIndicesPerViewportData.cpp | 56 +- ...nformationAndSceneIndicesPerViewportData.h | 17 +- ...nAndSceneIndicesPerViewportDataManager.cpp | 124 +++- ...ionAndSceneIndicesPerViewportDataManager.h | 13 +- lib/flowViewport/API/samples/CMakeLists.txt | 2 + .../fvpDataProducerSceneIndexExample.cpp | 564 ++++++++++++++++ .../fvpDataProducerSceneIndexExample.h | 187 ++++++ lib/flowViewport/sceneIndex/CMakeLists.txt | 2 + .../fvpParentDataModifierSceneIndex.cpp | 57 ++ .../fvpParentDataModifierSceneIndex.h | 97 +++ lib/mayaHydra/CMakeLists.txt | 1 + .../flowViewportAPIExamples/CMakeLists.txt | 1 + .../flowViewportAPIMayaLocator/CMakeLists.txt | 102 +++ .../flowViewportAPIMayaLocator.cpp | 607 ++++++++++++++++++ lib/mayaHydra/hydraExtensions/mayaUtils.cpp | 78 +++ lib/mayaHydra/hydraExtensions/mayaUtils.h | 35 + .../hydraExtensions/sceneIndex/CMakeLists.txt | 6 + ...ayaHydraMayaDataProducerSceneIndexData.cpp | 163 +++++ .../mayaHydraMayaDataProducerSceneIndexData.h | 72 +++ ...aProducerSceneIndexDataConcreteFactory.cpp | 29 + ...ataProducerSceneIndexDataConcreteFactory.h | 41 ++ .../sceneIndex/mayaHydraSceneIndex.h | 2 +- .../mayaHydraSceneIndexDataFactoriesSetup.cpp | 37 ++ .../mayaHydraSceneIndexDataFactoriesSetup.h | 38 ++ lib/mayaHydra/mayaPlugin/renderOverride.cpp | 52 +- lib/mayaHydra/mayaPlugin/renderOverride.h | 8 +- .../mayaUsd/render/mayaToHydra/CMakeLists.txt | 2 + .../FlowViewportAPITest/add_NodeCreated.png | Bin 0 -> 9129 bytes .../FlowViewportAPITest/add_NodeDeleted.png | Bin 0 -> 2105 bytes .../add_NodeDeletedRedo.png | Bin 0 -> 2105 bytes .../add_NodeDeletedUndo.png | Bin 0 -> 5757 bytes .../add_NodeDeletedUndoAgain.png | Bin 0 -> 5757 bytes .../FlowViewportAPITest/add_NodeHidden.png | Bin 0 -> 2105 bytes .../FlowViewportAPITest/add_NodeMoved.png | Bin 0 -> 5757 bytes .../add_NodeMovedAfterDeletionAndUndo.png | Bin 0 -> 12882 bytes .../FlowViewportAPITest/add_NodeUnhidden.png | Bin 0 -> 5757 bytes .../add_VP2AndThenBackToStorm.png | Bin 0 -> 12882 bytes .../cubeGrid_AfterModifs.png | Bin 0 -> 2735 bytes .../cubeGrid_BeforeModifs.png | Bin 0 -> 8616 bytes .../cubeGrid_VP2AndThenBackToStorm.png | Bin 0 -> 15761 bytes .../cubeGrid_WithInstancing.png | Bin 0 -> 2735 bytes .../cubeGrid_WithInstancingModifs.png | Bin 0 -> 15761 bytes .../multipleNodes_AfterModifs.png | Bin 0 -> 6198 bytes ...tipleNodes_AfterModifsRemoveInstancing.png | Bin 0 -> 6198 bytes .../multipleNodes_BeforeModifs.png | Bin 0 -> 7616 bytes .../multipleNodes_Node1Hidden.png | Bin 0 -> 4303 bytes .../multipleNodes_Node1Unhidden.png | Bin 0 -> 6198 bytes .../multipleNodes_VP2AndThenBackToStorm.png | Bin 0 -> 6198 bytes ...iewports_VP2AndThenBackToStorm_modPan2.png | Bin 0 -> 3265 bytes ...iewports_VP2AndThenBackToStorm_modPan4.png | Bin 0 -> 5821 bytes .../multipleViewports_VP2_modPan2.png | Bin 0 -> 2225 bytes .../multipleViewports_VP2_modPan4.png | Bin 0 -> 2155 bytes ...pleViewports_sphereFiltered_viewPanel2.png | Bin 0 -> 2404 bytes ...pleViewports_sphereFiltered_viewPanel4.png | Bin 0 -> 5084 bytes ...eViewports_sphereUnfiltered_viewPanel2.png | Bin 0 -> 3230 bytes ...eViewports_sphereUnfiltered_viewPanel4.png | Bin 0 -> 5876 bytes .../multipleViewports_viewPanel2.png | Bin 0 -> 3265 bytes .../multipleViewports_viewPanel4.png | Bin 0 -> 5821 bytes .../render/mayaToHydra/cpp/CMakeLists.txt | 2 + .../cpp/testFlowViewportAPIAddPrims.cpp | 132 ++++ .../cpp/testFlowViewportAPIAddPrims.py | 38 ++ .../render/mayaToHydra/testFlowViewportAPI.py | 346 ++++++++++ test/testUtils/imageUtils.py | 9 +- 73 files changed, 3607 insertions(+), 72 deletions(-) create mode 100644 lib/flowViewport/API/fvpDataProducerSceneIndexInterface.h create mode 100644 lib/flowViewport/API/interfacesImp/fvpDataProducerSceneIndexInterfaceImp.cpp create mode 100644 lib/flowViewport/API/interfacesImp/fvpDataProducerSceneIndexInterfaceImp.h create mode 100644 lib/flowViewport/API/perViewportSceneIndicesData/fvpDataProducerSceneIndexDataAbstractFactory.h create mode 100644 lib/flowViewport/API/perViewportSceneIndicesData/fvpDataProducerSceneIndexDataBase.cpp create mode 100644 lib/flowViewport/API/perViewportSceneIndicesData/fvpDataProducerSceneIndexDataBase.h create mode 100644 lib/flowViewport/API/samples/fvpDataProducerSceneIndexExample.cpp create mode 100644 lib/flowViewport/API/samples/fvpDataProducerSceneIndexExample.h create mode 100644 lib/flowViewport/sceneIndex/fvpParentDataModifierSceneIndex.cpp create mode 100644 lib/flowViewport/sceneIndex/fvpParentDataModifierSceneIndex.h create mode 100644 lib/mayaHydra/flowViewportAPIExamples/CMakeLists.txt create mode 100644 lib/mayaHydra/flowViewportAPIExamples/flowViewportAPIMayaLocator/CMakeLists.txt create mode 100644 lib/mayaHydra/flowViewportAPIExamples/flowViewportAPIMayaLocator/flowViewportAPIMayaLocator.cpp create mode 100644 lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraMayaDataProducerSceneIndexData.cpp create mode 100644 lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraMayaDataProducerSceneIndexData.h create mode 100644 lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraMayaDataProducerSceneIndexDataConcreteFactory.cpp create mode 100644 lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraMayaDataProducerSceneIndexDataConcreteFactory.h create mode 100644 lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndexDataFactoriesSetup.cpp create mode 100644 lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndexDataFactoriesSetup.h create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeCreated.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeDeleted.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeDeletedRedo.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeDeletedUndo.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeDeletedUndoAgain.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeHidden.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeMoved.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeMovedAfterDeletionAndUndo.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeUnhidden.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_VP2AndThenBackToStorm.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/cubeGrid_AfterModifs.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/cubeGrid_BeforeModifs.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/cubeGrid_VP2AndThenBackToStorm.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/cubeGrid_WithInstancing.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/cubeGrid_WithInstancingModifs.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleNodes_AfterModifs.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleNodes_AfterModifsRemoveInstancing.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleNodes_BeforeModifs.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleNodes_Node1Hidden.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleNodes_Node1Unhidden.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleNodes_VP2AndThenBackToStorm.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_VP2AndThenBackToStorm_modPan2.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_VP2AndThenBackToStorm_modPan4.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_VP2_modPan2.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_VP2_modPan4.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_sphereFiltered_viewPanel2.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_sphereFiltered_viewPanel4.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_sphereUnfiltered_viewPanel2.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_sphereUnfiltered_viewPanel4.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_viewPanel2.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_viewPanel4.png create mode 100644 test/lib/mayaUsd/render/mayaToHydra/cpp/testFlowViewportAPIAddPrims.cpp create mode 100644 test/lib/mayaUsd/render/mayaToHydra/cpp/testFlowViewportAPIAddPrims.py create mode 100644 test/lib/mayaUsd/render/mayaToHydra/testFlowViewportAPI.py diff --git a/lib/flowViewport/API/CMakeLists.txt b/lib/flowViewport/API/CMakeLists.txt index 49a2fbc270..2229b0c326 100644 --- a/lib/flowViewport/API/CMakeLists.txt +++ b/lib/flowViewport/API/CMakeLists.txt @@ -4,6 +4,7 @@ set(HEADERS fvpVersionInterface.h fvpInformationInterface.h fvpInformationClient.h + fvpDataProducerSceneIndexInterface.h ) # ----------------------------------------------------------------------------- diff --git a/lib/flowViewport/API/fvpDataProducerSceneIndexInterface.h b/lib/flowViewport/API/fvpDataProducerSceneIndexInterface.h new file mode 100644 index 0000000000..0dbf58e32e --- /dev/null +++ b/lib/flowViewport/API/fvpDataProducerSceneIndexInterface.h @@ -0,0 +1,102 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef FLOW_VIEWPORT_API_DATA_PRODUCER_SCENE_INDEX_INTERFACE_H +#define FLOW_VIEWPORT_API_DATA_PRODUCER_SCENE_INDEX_INTERFACE_H + +//Local headers +#include "flowViewport/api.h" + +//Hydra headers +#include + +namespace FVP_NS_DEF +{ + /** + * Interface to manage data producer scene indices in an Hydra viewport. A data producer scene index is a scene index that adds primitives to the current rendering. + * These new primitives are created without the need of a DCC object or a USD stage. + * To get an instance of the DataProducerSceneIndexInterface class, please use : + * Fvp::DataProducerSceneIndexInterface& dataProducerSceneIndexInterface = Fvp::DataProducerSceneIndexInterface::get(); + */ + class DataProducerSceneIndexInterface + { + public: + + /// Interface accessor + static FVP_API DataProducerSceneIndexInterface& get(); + + /// Use this string in the viewport identifier parameters, named "hydraViewportId" in this class, to apply the data producer scene index to all viewports. + static FVP_API const std::string allViewports; + + /// Use this string in the AddDataProducerSceneIndex method for the "rendererNames" parameter to apply to all renderers. + static FVP_API const std::string allRenderers; + + /** + * @brief Adds a custom data producer scene index. + * + * Adds a custom data producer scene index and associate it to be used in the same rendering as the hydra viewport whose identifier is hydraViewportId + * (or all hydra viewports if hydraViewportId is DataProducerSceneIndexInterface::allViewports). + * Basically, we merge this scene index with the others scene indices from the viewport which are the usd stages, the DCC native + * data and any others custom data producer scene indices like this one. + * + * @param[in] customDataProducerSceneIndex is the custom scene index to add. + * + * @param[in] dccNode is a MObject* for Maya or an INode* for 3ds max, if you provide the pointer value, then we automatically track some events such as transform + * or visibility updated and we hide automatically the primitives from the data producer scene index. + * If it is a nullptr, we won't do anything if the node's attributes changes. + * Basically, this is a way for you to set the DCC node as a parent node for all your primitives from the scene index. + * + * @param[in] hydraViewportId is an hydra viewport string identifier to which _customDataProducerSceneIndex needs to be associated to. + * Set it to DataProducerSceneIndexInterface::allViewports to add this data producer scene index to all viewports. + * To retrieve a specific hydra viewport identifier, please use the InformationInterface class. + * + * @param[in] rendererNames : are the Hydra renderer names to which this scene index should be added. + * This is only used when hydraViewportId is set to DataProducerSceneIndexInterface::allViewports, meaning you want to add this scene index to all viewports + * that are using these renderers. + * To apply to multiple renderers, use a separator such as ",". E.g : "GL, Arnold". We are actually looking for the render delegate's name in this string. + * Set this parameter to DataProducerSceneIndexInterface::allRenderers to add your scene index to all viewports whatever their renderer is. + * + * @param[in] customDataProducerSceneIndexRootPathForInsertion is the root path for insertion used as a second parameter of HdRenderIndex::InsertSceneIndex method. + * e.g : renderIndex.InsertSceneIndex(_customDataProducerSceneIndex, _customDataProducerSceneIndexRootPathForInsertion); + * + + */ + virtual void addDataProducerSceneIndex(const PXR_NS::HdSceneIndexBaseRefPtr& customDataProducerSceneIndex, + void* dccNode = nullptr, + const std::string& hydraViewportId = allViewports, + const std::string& rendererNames = allRenderers, + const PXR_NS::SdfPath& customDataProducerSceneIndexRootPathForInsertion = PXR_NS::SdfPath::AbsoluteRootPath() + ) = 0; + + /** + * @brief Removes a custom data producer scene index. + * + * Removes a custom data producer scene index, this scene index will not participate any more to the rendering of the given viewport(s). + * + * @param[in] customDataProducerSceneIndex is the custom scene index to remove. + * + * @param[in] hydraViewportId is the hydra viewport string identifier to which _customDataProducerSceneIndex was associated to or DataProducerSceneIndexInterface::allViewports + * if it was applied to all viewports. + + */ + virtual void removeViewportDataProducerSceneIndex(const PXR_NS::HdSceneIndexBaseRefPtr& customDataProducerSceneIndex, + const std::string& hydraViewportId = allViewports + ) = 0; + }; + +}//end of namespace FVP_NS_DEF + +#endif //FLOW_VIEWPORT_API_DATA_PRODUCER_SCENE_INDEX_INTERFACE_H diff --git a/lib/flowViewport/API/interfacesImp/CMakeLists.txt b/lib/flowViewport/API/interfacesImp/CMakeLists.txt index 5fbd5d89f4..f0390f6bdd 100644 --- a/lib/flowViewport/API/interfacesImp/CMakeLists.txt +++ b/lib/flowViewport/API/interfacesImp/CMakeLists.txt @@ -6,12 +6,14 @@ target_sources(${TARGET_NAME} fvpSelectionInterfaceImp.cpp fvpVersionInterfaceImp.cpp fvpInformationInterfaceImp.cpp + fvpDataProducerSceneIndexInterfaceImp.cpp ) set(HEADERS fvpSelectionInterfaceImp.h fvpVersionInterfaceImp.h fvpInformationInterfaceImp.h + fvpDataProducerSceneIndexInterfaceImp.h ) # ----------------------------------------------------------------------------- diff --git a/lib/flowViewport/API/interfacesImp/fvpDataProducerSceneIndexInterfaceImp.cpp b/lib/flowViewport/API/interfacesImp/fvpDataProducerSceneIndexInterfaceImp.cpp new file mode 100644 index 0000000000..5a475e8f02 --- /dev/null +++ b/lib/flowViewport/API/interfacesImp/fvpDataProducerSceneIndexInterfaceImp.cpp @@ -0,0 +1,230 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. +// + +//Local headers +#include "fvpDataProducerSceneIndexInterfaceImp.h" +#include "fvpInformationInterfaceImp.h" +#include "flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.h" +#include "flowViewport/sceneIndex/fvpRenderIndexProxy.h" + +//Hydra headers +#include + +//STL Headers +#include + +namespace +{ + std::mutex _dataProducerSceneIndicesThatApplyToAllViewports_mutex; + + // Are the scene indices that need to be applied to all viewports + std::set _dataProducerSceneIndicesThatApplyToAllViewports; + + // Abstract factory to create the scene index data, an implementation is provided by the DCC + FVP_NS::DataProducerSceneIndexDataAbstractFactory* _sceneIndexDataFactory{nullptr}; +} + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace FVP_NS_DEF { + +const std::string DataProducerSceneIndexInterface::allViewports = "allViewports"; +const std::string DataProducerSceneIndexInterface::allRenderers = "allRenderers"; + +DataProducerSceneIndexInterface& DataProducerSceneIndexInterface::get() +{ + return DataProducerSceneIndexInterfaceImp::get(); +} + +DataProducerSceneIndexInterfaceImp& DataProducerSceneIndexInterfaceImp::get() +{ + static DataProducerSceneIndexInterfaceImp theInterface; + return theInterface; +} + +void DataProducerSceneIndexInterfaceImp::addDataProducerSceneIndex(const PXR_NS::HdSceneIndexBaseRefPtr& customDataProducerSceneIndex, + void* dccNode /*= nullptr*/, + const std::string& hydraViewportId /*= allViewports*/, + const std::string& rendererNames /*= allRenderers*/, + const PXR_NS::SdfPath& customDataProducerSceneIndexRootPathForInsertion /*= PXR_NS::SdfPath::AbsoluteRootPath()*/) +{ + //_viewportSceneIndex can be a reference on a nullptr meaning the user wants _customDataProducerSceneIndex to be applied in all viewports. + PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBaseRefPtr dataProducerSceneIndexData = + _CreateDataProducerSceneIndexData(customDataProducerSceneIndex, rendererNames, customDataProducerSceneIndexRootPathForInsertion, dccNode); + + if (DataProducerSceneIndexInterface::allViewports == hydraViewportId){ + //Apply this dataProducer scene index to all viewports + _AddDataProducerSceneIndexToAllViewports(dataProducerSceneIndexData); + } else{ + //Apply this dataProducer scene index to a single viewport + const ViewportInformationAndSceneIndicesPerViewportData* viewportInfoAndData = + ViewportInformationAndSceneIndicesPerViewportDataManager::Get().GetViewportInfoAndDataFromViewportId(hydraViewportId); + if (viewportInfoAndData){ + _AddDataProducerSceneIndexToThisViewport(viewportInfoAndData->GetViewportInformation(), dataProducerSceneIndexData); + } + } +} + +void DataProducerSceneIndexInterfaceImp::removeAllViewportDataProducerSceneIndices(ViewportInformationAndSceneIndicesPerViewportData& viewportInformationAndSceneIndicesPerViewportData) +{ + auto& renderIndexProxy = viewportInformationAndSceneIndicesPerViewportData.GetRenderIndexProxy(); + if(nullptr == renderIndexProxy){ + return; + } + + auto& dataProducerSceneIndicesDataForthisViewport = viewportInformationAndSceneIndicesPerViewportData.GetDataProducerSceneIndicesData(); + + for (auto& dataProducerSceneIndicesData : dataProducerSceneIndicesDataForthisViewport){ + //Remove it from the render index + if (dataProducerSceneIndicesData){ + const auto& sceneIndex = dataProducerSceneIndicesData->GetDataProducerLastSceneIndexChain(); + if (sceneIndex){ + renderIndexProxy->RemoveSceneIndex(sceneIndex); + }else{ + TF_CODING_ERROR("dataProducerSceneIndexData->GetDataProducerLastSceneIndexChain() is a nullptr, that should never happen here."); + } + } + } + + dataProducerSceneIndicesDataForthisViewport.clear(); +} + +void DataProducerSceneIndexInterfaceImp::removeViewportDataProducerSceneIndex(const PXR_NS::HdSceneIndexBaseRefPtr& customDataProducerSceneIndex, + const std::string& hydraViewportId /*= allViewports*/) +{ + if (DataProducerSceneIndexInterface::allViewports == hydraViewportId){ + //It was applied to all viewports + + ViewportInformationAndSceneIndicesPerViewportDataSet& allViewportsInfoAndSceneIndices = + ViewportInformationAndSceneIndicesPerViewportDataManager::Get().GetAllViewportInfoAndData(); + + //We need to remove it from all viewports where it was applied. + for (auto& viewportInfoAndData : allViewportsInfoAndSceneIndices){ + ViewportInformationAndSceneIndicesPerViewportData& nonConstViewportInfoAndData = const_cast(viewportInfoAndData); + nonConstViewportInfoAndData.RemoveViewportDataProducerSceneIndex(customDataProducerSceneIndex); + } + + //Also remove it from the _dataProducerSceneIndicesThatApplyToAllViewports array + auto findResult = std::find_if(_dataProducerSceneIndicesThatApplyToAllViewports.begin(), _dataProducerSceneIndicesThatApplyToAllViewports.end(), + [&customDataProducerSceneIndex](const PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBaseRefPtr& dataProducerSIData) { + return (dataProducerSIData && dataProducerSIData->GetDataProducerSceneIndex() == customDataProducerSceneIndex);} + ); + if (findResult != _dataProducerSceneIndicesThatApplyToAllViewports.end()){ + _dataProducerSceneIndicesThatApplyToAllViewports.erase(findResult);// Which also decreases ref count + } + }else{ + //It was applied to a single viewport + auto viewportInformationAndSceneIndicesPerViewportData = ViewportInformationAndSceneIndicesPerViewportDataManager::Get().GetViewportInfoAndDataFromViewportId(hydraViewportId); + viewportInformationAndSceneIndicesPerViewportData->RemoveViewportDataProducerSceneIndex(customDataProducerSceneIndex); + } +} + +void DataProducerSceneIndexInterfaceImp::_AddDataProducerSceneIndexToAllViewports(const PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBaseRefPtr& dataProducerSceneIndexData) +{ + //Remove const from _dataProducerSceneIndexData + PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBaseRefPtr dataProducerSceneIndexDataNonConst = dataProducerSceneIndexData; + + //This is a block for the mutex lifetime + { + std::lock_guard lockDataProducerSceneIndicesDataPerViewport(_dataProducerSceneIndicesThatApplyToAllViewports_mutex); + + //Check if it is already inside our array + auto findResult = _dataProducerSceneIndicesThatApplyToAllViewports.find(dataProducerSceneIndexDataNonConst); + if (findResult != _dataProducerSceneIndicesThatApplyToAllViewports.cend()){ + return; + } + + //It is not already in dataProducerSceneIndexDataSet + //Add it with the dataProducer scene indices that need to be applied to all viewports + _dataProducerSceneIndicesThatApplyToAllViewports.insert(dataProducerSceneIndexDataNonConst); + } + + //Apply it to all existing hydra viewports + InformationInterface::ViewportInformationSet viewportsInformation; + InformationInterfaceImp::Get().GetViewportsInformation(viewportsInformation); + for (const auto& viewportInfo : viewportsInformation){ + _AddDataProducerSceneIndexToThisViewport(viewportInfo, dataProducerSceneIndexData); + } +} + +PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBaseRefPtr DataProducerSceneIndexInterfaceImp::_CreateDataProducerSceneIndexData(const HdSceneIndexBaseRefPtr& customDataProducerSceneIndex, + const std::string& rendererNames, + const SdfPath& customDataProducerSceneIndexRootPathForInsertion, + void* dccNode) +{ + TF_AXIOM(_sceneIndexDataFactory); + + if (! _sceneIndexDataFactory){ + TF_CODING_ERROR("_sceneIndexDataFactory is a nullptr, it should have been provided by a call to GetDataProducerSceneIndexInterfaceImp()->SetSceneIndexDataFactory"); + return nullptr; + } + + const PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBase::CreationParameters params(customDataProducerSceneIndex, rendererNames, customDataProducerSceneIndexRootPathForInsertion, dccNode); + return _sceneIndexDataFactory->createDataProducerSceneIndexDataBase(params); +} + +void DataProducerSceneIndexInterfaceImp::_AddDataProducerSceneIndexToThisViewport(const InformationInterface::ViewportInformation& viewportInformation, + const PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBaseRefPtr& dataProducerSceneIndexData) +{ + TF_AXIOM(dataProducerSceneIndexData); + + const std::string& hydraViewportId = viewportInformation._viewportId; + TF_AXIOM(hydraViewportId.length() > 0); + + //Check if there is some filtering per Hydra renderer + const std::string& viewportRendererName = viewportInformation._rendererName; + const std::string& dataProducerSceneIndexApplyToRendererNames = dataProducerSceneIndexData->GetRendererNames(); + if ( (! viewportRendererName.empty() )&& (dataProducerSceneIndexApplyToRendererNames != DataProducerSceneIndexInterface::allRenderers) ){ + //Filtering per renderer is applied + if (std::string::npos == dataProducerSceneIndexApplyToRendererNames.find(viewportRendererName)){ + return; //Ignore the current hydra viewport renderer name is not part of the supported renderers for this dataProducer scene index + } + } + + ViewportInformationAndSceneIndicesPerViewportData* viewportInformationAndSceneIndicesPerViewportData = + ViewportInformationAndSceneIndicesPerViewportDataManager::Get().GetViewportInfoAndDataFromViewportId(hydraViewportId); + TF_AXIOM(viewportInformationAndSceneIndicesPerViewportData ); + + auto& dataProducerSceneIndicesDataForthisViewport = viewportInformationAndSceneIndicesPerViewportData->GetDataProducerSceneIndicesData(); + auto findResult = dataProducerSceneIndicesDataForthisViewport.find(dataProducerSceneIndexData); + if (findResult != dataProducerSceneIndicesDataForthisViewport.end()){ + return; //Already in our array + } + + dataProducerSceneIndicesDataForthisViewport.insert(dataProducerSceneIndexData);//dataProducerSceneIndexData can be shared between multiple viewports + + //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(), dataProducerSceneIndexData->GetCustomDataProducerSceneIndexRootPathForInsertion()); + } +} + +void DataProducerSceneIndexInterfaceImp::hydraViewportSceneIndexAdded(const InformationInterface::ViewportInformation& viewportInfo) +{ + //Add the dataProducer scene indices that apply to all viewports to this newly created hydra viewport + std::lock_guard lockDataProducerSceneIndicesDataPerViewport(_dataProducerSceneIndicesThatApplyToAllViewports_mutex); + for (const PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBaseRefPtr& dataProducerSceneIndexData : _dataProducerSceneIndicesThatApplyToAllViewports){ + _AddDataProducerSceneIndexToThisViewport(viewportInfo, dataProducerSceneIndexData); + } +} + +void DataProducerSceneIndexInterfaceImp::setSceneIndexDataFactory(DataProducerSceneIndexDataAbstractFactory& factory) +{ + _sceneIndexDataFactory = &factory; +} + +} //End of namespace FVP_NS_DEF diff --git a/lib/flowViewport/API/interfacesImp/fvpDataProducerSceneIndexInterfaceImp.h b/lib/flowViewport/API/interfacesImp/fvpDataProducerSceneIndexInterfaceImp.h new file mode 100644 index 0000000000..0766bb994c --- /dev/null +++ b/lib/flowViewport/API/interfacesImp/fvpDataProducerSceneIndexInterfaceImp.h @@ -0,0 +1,76 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + + +#ifndef FLOW_VIEWPORT_API_INTERFACESIMP_DATA_PRODUCER_SCENE_INDEX_INTERFACE_IMP_H +#define FLOW_VIEWPORT_API_INTERFACESIMP_DATA_PRODUCER_SCENE_INDEX_INTERFACE_IMP_H + +//Local headers +#include "flowViewport/api.h" +#include "flowViewport/API/fvpDataProducerSceneIndexInterface.h" +#include "flowViewport/API/fvpInformationInterface.h" +#include "flowViewport/API/perViewportSceneIndicesData/fvpDataProducerSceneIndexDataAbstractFactory.h" + +//STL Headers +#include + +namespace FVP_NS_DEF { + +class ViewportInformationAndSceneIndicesPerViewportData; + +///Is a singleton, use Fvp::DataProducerSceneIndexInterfaceImp& dataProducerSceneIndexInterfaceImp = Fvp::DataProducerSceneIndexInterfaceImp::Get() to get an instance of that interface +class DataProducerSceneIndexInterfaceImp : public DataProducerSceneIndexInterface +{ +public: + DataProducerSceneIndexInterfaceImp() = default; + virtual ~DataProducerSceneIndexInterfaceImp() = default; + + ///Interface accessor + static FVP_API DataProducerSceneIndexInterfaceImp& get(); + + ///From FVP_NS_DEF::DataProducerSceneIndexInterface + void addDataProducerSceneIndex(const PXR_NS::HdSceneIndexBaseRefPtr& customDataProducerSceneIndex, + void* dccNode = nullptr, + const std::string& hydraViewportId = allViewports, + const std::string& rendererNames = allRenderers, + const PXR_NS::SdfPath& customDataProducerSceneIndexRootPathForInsertion = PXR_NS::SdfPath::AbsoluteRootPath() + )override; + void removeViewportDataProducerSceneIndex(const PXR_NS::HdSceneIndexBaseRefPtr& customDataProducerSceneIndex, + const std::string& hydraViewportId = allViewports)override; + + //Called by flow viewport + void hydraViewportSceneIndexAdded(const InformationInterface::ViewportInformation& viewportInfo); + void removeAllViewportDataProducerSceneIndices(ViewportInformationAndSceneIndicesPerViewportData& viewportInformationAndSceneIndicesPerViewportData); + + //Called by the DCC + FVP_API + void setSceneIndexDataFactory(DataProducerSceneIndexDataAbstractFactory& factory); + +protected: + void _AddDataProducerSceneIndexToAllViewports(const PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBaseRefPtr& dataProducerSceneIndexData); + void _AddDataProducerSceneIndexToThisViewport(const InformationInterface::ViewportInformation& viewportInformation, const PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBaseRefPtr& dataProducerSceneIndexData); + + PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBaseRefPtr _CreateDataProducerSceneIndexData( const PXR_NS::HdSceneIndexBaseRefPtr& customDataProducerSceneIndex, + const std::string& rendererNames, + const PXR_NS::SdfPath& customDataProducerSceneIndexRootPathForInsertion, + void* dccNode); +}; + +} //End of namespace FVP_NS_DEF + + +#endif // FLOW_VIEWPORT_API_INTERFACESIMP_DATA_PRODUCER_SCENE_INDEX_INTERFACE_IMP_H \ No newline at end of file diff --git a/lib/flowViewport/API/interfacesImp/fvpInformationInterfaceImp.cpp b/lib/flowViewport/API/interfacesImp/fvpInformationInterfaceImp.cpp index 6424177818..5970556a70 100644 --- a/lib/flowViewport/API/interfacesImp/fvpInformationInterfaceImp.cpp +++ b/lib/flowViewport/API/interfacesImp/fvpInformationInterfaceImp.cpp @@ -89,7 +89,7 @@ void InformationInterfaceImp::GetViewportsInformation(ViewportInformationSet& ou { outHydraViewportInformationArray.clear(); const ViewportInformationAndSceneIndicesPerViewportDataSet& allViewportInformationAndSceneIndicesPerViewportData = - ViewportInformationAndSceneIndicesPerViewportDataManager::Get().GetViewportInfoAndSceneIndicesPerViewportData(); + ViewportInformationAndSceneIndicesPerViewportDataManager::Get().GetAllViewportInfoAndData(); for (const ViewportInformationAndSceneIndicesPerViewportData& viewportInformationAndSceneIndicesPerViewportData : allViewportInformationAndSceneIndicesPerViewportData){ const InformationInterface::ViewportInformation& viewportInfo = viewportInformationAndSceneIndicesPerViewportData.GetViewportInformation(); outHydraViewportInformationArray.insert(viewportInfo); diff --git a/lib/flowViewport/API/perViewportSceneIndicesData/CMakeLists.txt b/lib/flowViewport/API/perViewportSceneIndicesData/CMakeLists.txt index dac1c529e1..b06b020251 100644 --- a/lib/flowViewport/API/perViewportSceneIndicesData/CMakeLists.txt +++ b/lib/flowViewport/API/perViewportSceneIndicesData/CMakeLists.txt @@ -5,11 +5,14 @@ target_sources(${TARGET_NAME} PRIVATE fvpViewportInformationAndSceneIndicesPerViewportData.cpp fvpViewportInformationAndSceneIndicesPerViewportDataManager.cpp + fvpDataProducerSceneIndexDataBase.cpp ) set(HEADERS fvpViewportInformationAndSceneIndicesPerViewportData.h fvpViewportInformationAndSceneIndicesPerViewportDataManager.h + fvpDataProducerSceneIndexDataAbstractFactory.h + fvpDataProducerSceneIndexDataBase.h ) # ----------------------------------------------------------------------------- diff --git a/lib/flowViewport/API/perViewportSceneIndicesData/fvpDataProducerSceneIndexDataAbstractFactory.h b/lib/flowViewport/API/perViewportSceneIndicesData/fvpDataProducerSceneIndexDataAbstractFactory.h new file mode 100644 index 0000000000..dcd577e4b1 --- /dev/null +++ b/lib/flowViewport/API/perViewportSceneIndicesData/fvpDataProducerSceneIndexDataAbstractFactory.h @@ -0,0 +1,38 @@ +// +// Copyright 2023 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef FLOW_VIEWPORT_API_PERVIEWPORTSCENEINDICESDATA_DATAPRODUCERSCENEINDEXDATAABSTRACTFACTORY_H +#define FLOW_VIEWPORT_API_PERVIEWPORTSCENEINDICESDATA_DATAPRODUCERSCENEINDEXDATAABSTRACTFACTORY_H + +//Flow Viewport headers +#include "fvpDataProducerSceneIndexDataBase.h" + +namespace FVP_NS_DEF { + +/** Since Flow viewport is DCC agnostic, the DCC will implement a concrete factory subclassing that class to provide specific +* DCC implementation of the classes mentioned. +*/ +class DataProducerSceneIndexDataAbstractFactory +{ +public: + /// The DCC will create a subclass of DataProducerSceneIndexDataBaseRefPtr with specific DCC variables that flow viewport cannot manage since it's DCC agnostic + virtual PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBaseRefPtr createDataProducerSceneIndexDataBase( + const PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBase::CreationParameters& params) = 0; +}; + +}//End of namespace FVP_NS_DEF { + +#endif //FLOW_VIEWPORT_API_PERVIEWPORTSCENEINDICESDATA_DATAPRODUCERSCENEINDEXDATAABSTRACTFACTORY_H + diff --git a/lib/flowViewport/API/perViewportSceneIndicesData/fvpDataProducerSceneIndexDataBase.cpp b/lib/flowViewport/API/perViewportSceneIndicesData/fvpDataProducerSceneIndexDataBase.cpp new file mode 100644 index 0000000000..af5bac06bf --- /dev/null +++ b/lib/flowViewport/API/perViewportSceneIndicesData/fvpDataProducerSceneIndexDataBase.cpp @@ -0,0 +1,193 @@ +// +// Copyright 2023 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. +// + +/* DataProducerSceneIndexDataBase is storing information about a custom dataProducer scene index. +* Since an instance of the DataProducerSceneIndexDataBase class can be shared between multiple viewports, we need ref counting. +*/ + +//Local headers +#include "fvpDataProducerSceneIndexDataBase.h" +#include "flowViewport/sceneIndex/fvpParentDataModifierSceneIndex.h" + +//Hydra headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +namespace FVP_NS_DEF { + +/* DataProducerSceneIndexDataBase is storing information about a custom dataProducer scene index which is a scene index that create new primitives. +* Since an instance of the DataProducerSceneIndexDataBase class can be shared between multiple viewports in our records, we need ref counting. +*/ +DataProducerSceneIndexDataBase::DataProducerSceneIndexDataBase(const CreationParameters& params) +{ + _parentMatrix.SetIdentity(); + + _dataProducerSceneIndex = params._customDataProducerSceneIndex; + _customDataProducerSceneIndexRootPathForInsertion = params._customDataProducerSceneIndexRootPathForInsertion; + _lastSceneIndexChain = params._customDataProducerSceneIndex; + _rendererNames = params._rendererNames; + _dccNode = params._dccNode; +} + +void DataProducerSceneIndexDataBase::UpdateHydraTransformFromParentPath() +{ + if (! _parentDataModifierSceneIndex){ + return; + } + + //Update the matrix in the filtering scene index + _parentDataModifierSceneIndex->SetParentTransformMatrix(_parentMatrix); + + //This is still a WIP trying to dirty the prims correctly... + /*HdDataSourceLocatorSet locators; + locators.append(HdXformSchema::GetDefaultLocator()); + locators.append(HdVisibilitySchema::GetDefaultLocator()); + locators.append(HdPrimvarsSchema::GetDefaultLocator()); + _retainedSceneIndex->DirtyPrims({{ _parentPath, locators}}); + */ + + //Set the transform prim as dirty so it gets recomputed + /*HdDataSourceLocatorSet locators; + HdDirtyBitsTranslator::RprimDirtyBitsToLocatorSet(HdTokens->transform, HdChangeTracker::AllSceneDirtyBits, &locators); + + //Set dirty this field in the retained scene index for the parent prim + //_retainedSceneIndex->DirtyPrims( { {_parentPath, locators} } ); + */ + + //Update by removing the prim and adding it again + RemoveParentPrimFromSceneIndex(); + AddParentPrimToSceneIndex(); +} + +void DataProducerSceneIndexDataBase::UpdateVisibilityFromDCCNode(bool isVisible) +{ + if (! _parentDataModifierSceneIndex){ + return; + } + //Update the visibility in the filtering scene index + _parentDataModifierSceneIndex->SetParentVisibility(isVisible); + + //This is still a WIP trying to dirty the prims correctly... + /* //Set the transform prim as dirty so it gets recomputed + HdDataSourceLocatorSet locators; + HdDirtyBitsTranslator::RprimDirtyBitsToLocatorSet(HdTokens->transform, HdChangeTracker::DirtyVisibility, &locators); + + //Set dirty this field in the retained scene index for the parent prim + //_retainedSceneIndex->DirtyPrims({{_parentPath, locators}}); + */ + + //Update by removing the prim and adding it again + RemoveParentPrimFromSceneIndex(); + AddParentPrimToSceneIndex(); +} + +void DataProducerSceneIndexDataBase::AddParentPrimToSceneIndex() +{ + if(_visible){ + return; + } + + //Arrays of added prims + HdRetainedSceneIndex::AddedPrimEntries addedPrims; + + //We are creating a XForm prim which has only 2 attributes a matrix and a visibility. + //This prim will be the parent of all dataProducer scene index primitives so we can change their transform or visibility from the parent + HdRetainedSceneIndex::AddedPrimEntry parentPrimEntry; + parentPrimEntry.primPath = _parentPath; + parentPrimEntry.primType = HdTokens->transform; + parentPrimEntry.dataSource = HdRetainedContainerDataSource::New( + HdXformSchemaTokens->xform, + HdXformSchema::Builder().SetMatrix(HdRetainedTypedSampledDataSource::New(_parentMatrix)).Build(), + + HdVisibilitySchemaTokens->visibility, + HdVisibilitySchema::BuildRetained(HdRetainedTypedSampledDataSource::New(true)) + ); + + addedPrims.emplace_back(parentPrimEntry); + + + //Add new prims to the scene index + _retainedSceneIndex->AddPrims(addedPrims); + _visible = true; +} + +void DataProducerSceneIndexDataBase::RemoveParentPrimFromSceneIndex() +{ + if(!_visible){ + return; + } + + _retainedSceneIndex->RemovePrims({ _parentPath}); + _visible = false; +} + +void DataProducerSceneIndexDataBase::_CreateSceneIndexChainForDataProducerSceneIndex() +{ + //Create a parent path to parent the whole _dataProducerSceneIndex prims, try to use the DCC node name + std::string nodeName = GetDCCNodeName(); + if (nodeName.empty()){ + _parentPath = SdfPath(TfStringPrintf("/DataProducerSI_%p", &(*_dataProducerSceneIndex))); + }else{ + //A nodeName was provided by the DCC implementation, and it was sanitized for Hydra + _parentPath = SdfPath(std::string("/")+nodeName); + } + + //Create a retainedsceneindex to inject a parent path to the be the parent of _dataProducerSceneIndex + _retainedSceneIndex = HdRetainedSceneIndex::New(); + // Add a prim inside which will be the parent of _dataProducerSceneIndex, its SdfPath will updated in _inOutData._parentPath + AddParentPrimToSceneIndex(); + + //Create a filtering scene index to update the information (transform, visibility,...) from the parent prim. + _parentDataModifierSceneIndex = ParentDataModifierSceneIndex::New(_retainedSceneIndex); + _parentDataModifierSceneIndex->SetParentPath(_parentPath);//Set the parent path inside + _parentDataModifierSceneIndex->SetParentTransformMatrix(_parentMatrix); + + //Add a prefixing scene index to _dataProducerSceneIndex to set the parent which we added to the retainedsceneindex + HdPrefixingSceneIndexRefPtr prefixingSceneIndex = HdPrefixingSceneIndex::New(_dataProducerSceneIndex, _parentPath); + + //Use a merging scene index to merge the prefixing and the retainedsceneindex + HdMergingSceneIndexRefPtr mergingSceneIndex = HdMergingSceneIndex::New(); + mergingSceneIndex->AddInputScene(_parentDataModifierSceneIndex, SdfPath::AbsoluteRootPath()); + mergingSceneIndex->AddInputScene(prefixingSceneIndex, SdfPath::AbsoluteRootPath()); + + //Add a flattening scene index to the merging scene index, flatten only transform and visibility, meaning that transform and visibility should not have been already flatten previously ! + static HdContainerDataSourceHandle const flattenedTransformAndVisibilityDataSourceHandle = + HdRetainedContainerDataSource::New( + HdVisibilitySchema::GetSchemaToken(), + HdMakeDataSourceContainingFlattenedDataSourceProvider::Make(), + HdXformSchema::GetSchemaToken(), + HdMakeDataSourceContainingFlattenedDataSourceProvider::Make() + ); + + _lastSceneIndexChain = HdFlatteningSceneIndex::New(mergingSceneIndex, flattenedTransformAndVisibilityDataSourceHandle);//Flatten +} + +}//end of namespace FVP_NS_DEF + +PXR_NAMESPACE_CLOSE_SCOPE \ No newline at end of file diff --git a/lib/flowViewport/API/perViewportSceneIndicesData/fvpDataProducerSceneIndexDataBase.h b/lib/flowViewport/API/perViewportSceneIndicesData/fvpDataProducerSceneIndexDataBase.h new file mode 100644 index 0000000000..32f878c977 --- /dev/null +++ b/lib/flowViewport/API/perViewportSceneIndicesData/fvpDataProducerSceneIndexDataBase.h @@ -0,0 +1,112 @@ +// +// Copyright 2023 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef FLOW_VIEWPORT_API_PERVIEWPORTSCENEINDICESDATA_DATA_PRODUCER_SCENE_INDEX_DATA_BASE_H +#define FLOW_VIEWPORT_API_PERVIEWPORTSCENEINDICESDATA_DATA_PRODUCER_SCENE_INDEX_DATA_BASE_H + +//Local headers +#include "flowViewport/api.h" +#include "flowViewport/sceneIndex/fvpParentDataModifierSceneIndex.h" + +//Hydra headers +#include +#include + +//The Pixar's namespace needs to be at the highest namespace level for TF_DECLARE_WEAK_AND_REF_PTRS to work. +PXR_NAMESPACE_OPEN_SCOPE + +namespace FVP_NS_DEF { + +class DataProducerSceneIndexDataBase;//Predeclaration +TF_DECLARE_WEAK_AND_REF_PTRS(DataProducerSceneIndexDataBase);//Be able to use Ref counting pointers on DataProducerSceneIndexDataBase + +/** DataProducerSceneIndexDataBase is storing information about a custom dataProducer scene index. +* Since an instance of the DataProducerSceneIndexDataBase class can be shared between multiple viewports in our records, we need ref counting. +*/ + class FVP_API DataProducerSceneIndexDataBase : public TfRefBase, public TfWeakBase +{ +public: + + ///Structure passed as a parameter to create an instance of DataProducerSceneIndexDataBase + struct CreationParameters + { + CreationParameters( const HdSceneIndexBaseRefPtr& customDataProducerSceneIndex, + const std::string& rendererNames, + const SdfPath& customDataProducerSceneIndexRootPathForInsertion, + void* dccNode) : + _customDataProducerSceneIndex(customDataProducerSceneIndex), _rendererNames(rendererNames), + _customDataProducerSceneIndexRootPathForInsertion(customDataProducerSceneIndexRootPathForInsertion),_dccNode(dccNode) + {} + + //See below for an explanation of these parameters + const HdSceneIndexBaseRefPtr& _customDataProducerSceneIndex; + const std::string& _rendererNames; + const SdfPath& _customDataProducerSceneIndexRootPathForInsertion; + void* _dccNode; + }; + + ~DataProducerSceneIndexDataBase() override = default; + + virtual void AddParentPrimToSceneIndex(); + virtual void RemoveParentPrimFromSceneIndex(); + + const HdSceneIndexBaseRefPtr& GetDataProducerSceneIndex() const {return _dataProducerSceneIndex;} + const HdSceneIndexBaseRefPtr& GetDataProducerLastSceneIndexChain() const {return _lastSceneIndexChain;} + const SdfPath& GetCustomDataProducerSceneIndexRootPathForInsertion()const{return _customDataProducerSceneIndexRootPathForInsertion;} + const std::string& GetRendererNames() const {return _rendererNames;} + + /// Provide the node name from the DCC to be overriden in a DCC specific subclass + virtual std::string GetDCCNodeName() const {return "";} + + void UpdateVisibilityFromDCCNode(bool isVisible); + void UpdateHydraTransformFromParentPath(); + +protected: + + void _CreateSceneIndexChainForDataProducerSceneIndex(); + DataProducerSceneIndexDataBase(const CreationParameters& params); + + /// data producer scene index + HdSceneIndexBaseRefPtr _dataProducerSceneIndex = nullptr; + /// data producer scene index rootPath for insertion (used in HdRenderIndex::InsertSceneIndex) + SdfPath _customDataProducerSceneIndexRootPathForInsertion; + /// Are the Hydra renderer(s) to which this scene index should be applied (e.g : "GL, Arnold") or DataProducerSceneIndexInterface::allViewports to apply to all viewports + std::string _rendererNames; + /// Is the DCC node so a MObject* for Maya or an INode* for 3ds Max + void* _dccNode; + + //The following members are optional and only used when a dccNode was passed in the constructor + /** Is a filtering scene index that modifies the parent prim from the retained scene index to update the transfor/visibility when it is updated in the DCC. + It is used only when a dccNode was passed.*/ + ParentDataModifierSceneIndexRefPtr _parentDataModifierSceneIndex = nullptr; + /// ParentPath prim used to be the parent of all prims from _dataProducerSceneIndex, used only when a dccNode was passed + SdfPath _parentPath; + /// Is the last scene index of the scene index chain when a dccNode was passed. + HdSceneIndexBaseRefPtr _lastSceneIndexChain = nullptr; + /// Is the retained scene index holding the parent prim for _dataProducerSceneIndex. It is used only when a dccNode was passed. + HdRetainedSceneIndexRefPtr _retainedSceneIndex = nullptr; + + /// Is the world matrix of the parent prim in the retained scene index. It is used only when a dccNode was passed. + GfMatrix4d _parentMatrix; + /// Is the visibility state of the parent prim in the retained scene index. It is used only when a dccNode was passed. + bool _visible = false; +}; + +}//End of namespace FVP_NS_DEF + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif //FLOW_VIEWPORT_API_PERVIEWPORTSCENEINDICESDATA_DATA_PRODUCER_SCENE_INDEX_DATA_BASE_H + diff --git a/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportData.cpp b/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportData.cpp index 06ef8cb680..01d916061b 100644 --- a/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportData.cpp +++ b/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportData.cpp @@ -18,21 +18,65 @@ #include "fvpViewportInformationAndSceneIndicesPerViewportData.h" #include "flowViewport/API/interfacesImp/fvpInformationInterfaceImp.h" #include "flowViewport/sceneIndex/fvpRenderIndexProxy.h" +#include "flowViewport/API/interfacesImp/fvpDataProducerSceneIndexInterfaceImp.h" + +//Hydra headers +#include PXR_NAMESPACE_USING_DIRECTIVE namespace FVP_NS_DEF { -ViewportInformationAndSceneIndicesPerViewportData::ViewportInformationAndSceneIndicesPerViewportData(const InformationInterface::ViewportInformation& viewportInformation) - : _viewportInformation(viewportInformation) +ViewportInformationAndSceneIndicesPerViewportData::ViewportInformationAndSceneIndicesPerViewportData(const InformationInterface::ViewportInformation& viewportInformation, + const Fvp::RenderIndexProxyPtr& renderIndexProxy) + : _viewportInformation(viewportInformation), _renderIndexProxy(renderIndexProxy) { + if (_renderIndexProxy){ + _viewportInformation._rendererName = _renderIndexProxy->GetRendererDisplayName(); + } } -void ViewportInformationAndSceneIndicesPerViewportData::SetRenderIndexProxy(const Fvp::RenderIndexProxyPtr& renderIndexProxy) +ViewportInformationAndSceneIndicesPerViewportData::~ViewportInformationAndSceneIndicesPerViewportData() { - _renderIndexProxy = renderIndexProxy; - if (_renderIndexProxy){ - _viewportInformation._rendererName = _renderIndexProxy->GetRendererDisplayName(); + DataProducerSceneIndexInterfaceImp::get().removeAllViewportDataProducerSceneIndices(*this); +} + +void ViewportInformationAndSceneIndicesPerViewportData::RemoveViewportDataProducerSceneIndex(const PXR_NS::HdSceneIndexBaseRefPtr& customDataProducerSceneIndex) +{ + auto findResult = std::find_if(_dataProducerSceneIndicesData.begin(), _dataProducerSceneIndicesData.end(), + [&customDataProducerSceneIndex](const PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBaseRefPtr& dataProducerSIData) { return dataProducerSIData->GetDataProducerSceneIndex() == customDataProducerSceneIndex;}); + if (findResult != _dataProducerSceneIndicesData.end()) { + // Remove the dataProducer scene index from the merging scene index through the render proxy + if (_renderIndexProxy){ + const PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBaseRefPtr& dataProducerSceneIndexData = (*findResult); + if (dataProducerSceneIndexData && dataProducerSceneIndexData->GetDataProducerLastSceneIndexChain()) { + _renderIndexProxy->RemoveSceneIndex( + dataProducerSceneIndexData->GetDataProducerLastSceneIndexChain()); + } else { + TF_CODING_ERROR( + "dataProducerSceneIndexData->GetDataProducerLastSceneIndexChain() is a " + "nullptr, that should never happen here."); + } + } + + // Remove the data from our records + _dataProducerSceneIndicesData.erase(findResult); // This also decreases the ref count + } +} + +void ViewportInformationAndSceneIndicesPerViewportData::_AddAllDataProducerSceneIndexToMergingSCeneIndex() +{ + if( nullptr == _renderIndexProxy){ + return; + } + + //Add all data producer scene index to the merging scene index through the render index proxy + for (auto& dataProducerSceneIndexData : _dataProducerSceneIndicesData){ + // Add the dataProducer scene index to the merging scene index + if (dataProducerSceneIndexData && dataProducerSceneIndexData->GetDataProducerLastSceneIndexChain()) { + _renderIndexProxy->InsertSceneIndex(dataProducerSceneIndexData->GetDataProducerLastSceneIndexChain(), + dataProducerSceneIndexData->GetCustomDataProducerSceneIndexRootPathForInsertion()); + } } } diff --git a/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportData.h b/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportData.h index 06abbf7a77..2037f251e3 100644 --- a/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportData.h +++ b/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportData.h @@ -21,6 +21,7 @@ #include "flowViewport/api.h" #include "flowViewport/API/fvpInformationInterface.h" #include "flowViewport/sceneIndex/fvpRenderIndexProxyFwd.h" +#include "fvpDataProducerSceneIndexDataBase.h" namespace FVP_NS_DEF { @@ -31,8 +32,9 @@ namespace FVP_NS_DEF { class ViewportInformationAndSceneIndicesPerViewportData { public: - ViewportInformationAndSceneIndicesPerViewportData(const InformationInterface::ViewportInformation& viewportInformation); - ~ViewportInformationAndSceneIndicesPerViewportData() = default; + ViewportInformationAndSceneIndicesPerViewportData(const InformationInterface::ViewportInformation& viewportInformation, + const Fvp::RenderIndexProxyPtr& renderIndexProxy); + ~ViewportInformationAndSceneIndicesPerViewportData(); const InformationInterface::ViewportInformation& GetViewportInformation()const { return _viewportInformation;} const PXR_NS::HdSceneIndexBaseRefPtr& GetLastFilteringSceneIndexOfTheChain() const {return _lastFilteringSceneIndexOfTheChain;} @@ -40,7 +42,10 @@ class ViewportInformationAndSceneIndicesPerViewportData const Fvp::RenderIndexProxyPtr GetRenderIndexProxy() const {return _renderIndexProxy;} void SetInputSceneIndex(const PXR_NS::HdSceneIndexBaseRefPtr& inputSceneIndex) {_inputSceneIndex = inputSceneIndex;} const PXR_NS::HdSceneIndexBaseRefPtr& GetInputSceneIndex() const {return _inputSceneIndex;} - + const std::set& GetDataProducerSceneIndicesData() const {return _dataProducerSceneIndicesData;} + std::set& GetDataProducerSceneIndicesData() {return _dataProducerSceneIndicesData;} + void RemoveViewportDataProducerSceneIndex(const PXR_NS::HdSceneIndexBaseRefPtr& customDataProducerSceneIndex); + //Needed by std::set bool operator < (const ViewportInformationAndSceneIndicesPerViewportData& other)const{ return _viewportInformation < other._viewportInformation; //Is for std::set. @@ -50,6 +55,9 @@ class ViewportInformationAndSceneIndicesPerViewportData ///Hydra viewport information InformationInterface::ViewportInformation _viewportInformation; + ///Are the custom dataProducer scene indices added to this viewport + std::set _dataProducerSceneIndicesData; + ///Is the scene index we should use as an input for the custom filtering scene indices chain PXR_NS::HdSceneIndexBaseRefPtr _inputSceneIndex {nullptr}; @@ -58,6 +66,9 @@ class ViewportInformationAndSceneIndicesPerViewportData ///Is a render index proxy per viewport to avoid accessing directly the render index Fvp::RenderIndexProxyPtr _renderIndexProxy {nullptr}; + + /// When the render proxy is added to this class, we may have to apply all the _dataProducerSceneIndicesData to this viewport, this is what this function does. + void _AddAllDataProducerSceneIndexToMergingSCeneIndex(); }; using ViewportInformationAndSceneIndicesPerViewportDataSet = std::set; diff --git a/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.cpp b/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.cpp index 0f8d800749..2e552fdf59 100644 --- a/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.cpp +++ b/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.cpp @@ -16,6 +16,7 @@ //Local headers #include "fvpViewportInformationAndSceneIndicesPerViewportDataManager.h" +#include "flowViewport/API/interfacesImp/fvpDataProducerSceneIndexInterfaceImp.h" #include "flowViewport/API/interfacesImp/fvpInformationInterfaceImp.h" #include "flowViewport/sceneIndex/fvpRenderIndexProxy.h" @@ -28,6 +29,7 @@ namespace { std::mutex _viewportInformationAndSceneIndicesPerViewportDataSet_mutex; + std::set dummyEmptyArray; } PXR_NAMESPACE_USING_DIRECTIVE @@ -41,69 +43,129 @@ ViewportInformationAndSceneIndicesPerViewportDataManager& ViewportInformationAnd } //A new Hydra viewport was created -void ViewportInformationAndSceneIndicesPerViewportDataManager::AddViewportInformation(const InformationInterface::ViewportInformation& viewportInfo) +void ViewportInformationAndSceneIndicesPerViewportDataManager::AddViewportInformation(const InformationInterface::ViewportInformation& viewportInfo, const Fvp::RenderIndexProxyPtr& renderIndexProxy) { //Add it in our array if it is not already inside { std::lock_guard lock(_viewportInformationAndSceneIndicesPerViewportDataSet_mutex); - if ((_viewportsInformationAndSceneIndicesPerViewportData.count(viewportInfo) == 1)){ + const auto& viewportId = viewportInfo._viewportId; + auto findResult = std::find_if(_viewportsInformationAndSceneIndicesPerViewportData.begin(), _viewportsInformationAndSceneIndicesPerViewportData.end(), + [&viewportId](const ViewportInformationAndSceneIndicesPerViewportData& other) { return other.GetViewportInformation()._viewportId == viewportId;}); + if (findResult != _viewportsInformationAndSceneIndicesPerViewportData.end()){ return;//It is already inside our array } - _viewportsInformationAndSceneIndicesPerViewportData.insert(ViewportInformationAndSceneIndicesPerViewportData(viewportInfo)); + _viewportsInformationAndSceneIndicesPerViewportData.insert(ViewportInformationAndSceneIndicesPerViewportData(viewportInfo, renderIndexProxy)); } + //Call this to let the data producer scene indices that apply to all viewports to be added to this new viewport as well + DataProducerSceneIndexInterfaceImp::get().hydraViewportSceneIndexAdded(viewportInfo); + //Let the registered clients know a new viewport has been added InformationInterfaceImp::Get().SceneIndexAdded(viewportInfo); } void ViewportInformationAndSceneIndicesPerViewportDataManager::RemoveViewportInformation(const std::string& modelPanel) { - //Block for the lifetime of the lock - { - std::lock_guard lock(_viewportInformationAndSceneIndicesPerViewportDataSet_mutex); + std::lock_guard lock(_viewportInformationAndSceneIndicesPerViewportDataSet_mutex); - auto findResult = std::find_if(_viewportsInformationAndSceneIndicesPerViewportData.begin(), _viewportsInformationAndSceneIndicesPerViewportData.end(), - [&modelPanel](const ViewportInformationAndSceneIndicesPerViewportData& other) { return other.GetViewportInformation()._viewportId == modelPanel;}); - if (findResult != _viewportsInformationAndSceneIndicesPerViewportData.end()){ + auto findResult = std::find_if(_viewportsInformationAndSceneIndicesPerViewportData.begin(), _viewportsInformationAndSceneIndicesPerViewportData.end(), + [&modelPanel](const ViewportInformationAndSceneIndicesPerViewportData& other) { return other.GetViewportInformation()._viewportId == modelPanel;}); + if (findResult != _viewportsInformationAndSceneIndicesPerViewportData.end()){ - InformationInterfaceImp::Get().SceneIndexRemoved(findResult->GetViewportInformation()); + InformationInterfaceImp::Get().SceneIndexRemoved(findResult->GetViewportInformation()); - const Fvp::RenderIndexProxyPtr renderIndexProxy = findResult->GetRenderIndexProxy();//Get the pointer on the renderIndexProxy + const Fvp::RenderIndexProxyPtr& renderIndexProxy = findResult->GetRenderIndexProxy();//Get the pointer on the renderIndexProxy - if(renderIndexProxy){ - //Destroy the custom filtering scene indices chain - //Following code is equivalent to calling FilteringSceneIndicesChainManager::GetManager().DestroyFilteringSceneIndicesChain(*renderIndexProxy); - //But we cannot do so because of the lock above. - auto renderIndex = renderIndexProxy->GetRenderIndex(); - if (renderIndex && findResult->GetLastFilteringSceneIndexOfTheChain()){ - renderIndex->RemoveSceneIndex(findResult->GetLastFilteringSceneIndexOfTheChain());//Remove the whole chain from the render index - } + if(renderIndexProxy){ + //Destroy the custom filtering scene indices chain + auto renderIndex = renderIndexProxy->GetRenderIndex(); + const auto& filteringSceneIndex = findResult->GetLastFilteringSceneIndexOfTheChain(); + if (renderIndex && filteringSceneIndex){ + renderIndex->RemoveSceneIndex(filteringSceneIndex);//Remove the whole chain from the render index } - - _viewportsInformationAndSceneIndicesPerViewportData.erase(findResult); } + + _viewportsInformationAndSceneIndicesPerViewportData.erase(findResult); } } -void ViewportInformationAndSceneIndicesPerViewportDataManager::UpdateRenderIndexProxy(const std::string& modelPanel, const Fvp::RenderIndexProxyPtr& renderIndexProxy) +const ViewportInformationAndSceneIndicesPerViewportData* ViewportInformationAndSceneIndicesPerViewportDataManager::GetViewportInfoAndDataFromViewportId(const std::string& viewportId)const { - if (! renderIndexProxy){ - return; + std::lock_guard lock(_viewportInformationAndSceneIndicesPerViewportDataSet_mutex); + + auto findResult = std::find_if(_viewportsInformationAndSceneIndicesPerViewportData.cbegin(), _viewportsInformationAndSceneIndicesPerViewportData.cend(), + [&viewportId](const ViewportInformationAndSceneIndicesPerViewportData& other) { return other.GetViewportInformation()._viewportId == viewportId;}); + if (findResult != _viewportsInformationAndSceneIndicesPerViewportData.cend()){ + const ViewportInformationAndSceneIndicesPerViewportData& data = (*findResult); + return &data; } - auto findResult = std::find_if(_viewportsInformationAndSceneIndicesPerViewportData.begin(), _viewportsInformationAndSceneIndicesPerViewportData.end(), - [&modelPanel](const ViewportInformationAndSceneIndicesPerViewportData& other) { return other.GetViewportInformation()._viewportId == modelPanel;}); + return nullptr; +} + +ViewportInformationAndSceneIndicesPerViewportData* ViewportInformationAndSceneIndicesPerViewportDataManager::GetViewportInfoAndDataFromViewportId(const std::string& viewportId) +{ + std::lock_guard lock(_viewportInformationAndSceneIndicesPerViewportDataSet_mutex); + + ViewportInformationAndSceneIndicesPerViewportDataSet::iterator findResult = std::find_if(_viewportsInformationAndSceneIndicesPerViewportData.begin(), _viewportsInformationAndSceneIndicesPerViewportData.end(), + [&viewportId](const ViewportInformationAndSceneIndicesPerViewportData& other) { return other.GetViewportInformation()._viewportId == viewportId;}); if (findResult != _viewportsInformationAndSceneIndicesPerViewportData.end()){ - if(findResult->GetRenderIndexProxy()){ - return; //Already updated + ViewportInformationAndSceneIndicesPerViewportData& data = const_cast(*findResult); + return &data; + } + + return nullptr; +} + +const std::set& +ViewportInformationAndSceneIndicesPerViewportDataManager::GetDataProducerSceneIndicesDataFromViewportId(const std::string& viewportId)const +{ + std::lock_guard lock(_viewportInformationAndSceneIndicesPerViewportDataSet_mutex); + + for (const auto& viewportInformationAndSceneIndicesPerViewportData : _viewportsInformationAndSceneIndicesPerViewportData){ + const auto& viewportIdFromContainer = viewportInformationAndSceneIndicesPerViewportData.GetViewportInformation()._viewportId; + if (viewportIdFromContainer == viewportId){ + return viewportInformationAndSceneIndicesPerViewportData.GetDataProducerSceneIndicesData(); } + } + + return dummyEmptyArray; +} + +bool ViewportInformationAndSceneIndicesPerViewportDataManager::ModelPanelIsAlreadyRegistered(const std::string& modelPanel)const +{ + std::lock_guard lock(_viewportInformationAndSceneIndicesPerViewportDataSet_mutex); + + auto findResult = std::find_if(_viewportsInformationAndSceneIndicesPerViewportData.cbegin(), _viewportsInformationAndSceneIndicesPerViewportData.cend(), + [&modelPanel](const ViewportInformationAndSceneIndicesPerViewportData& other) { return other.GetViewportInformation()._viewportId == modelPanel;}); - auto& item = *findResult; - ViewportInformationAndSceneIndicesPerViewportData& nonConstItem = const_cast(item); - nonConstItem.SetRenderIndexProxy(renderIndexProxy); + return (findResult != _viewportsInformationAndSceneIndicesPerViewportData.cend()); +} + +void ViewportInformationAndSceneIndicesPerViewportDataManager::RemoveAllViewportsInformation() +{ + //Block for the lifetime of the lock + std::lock_guard lock(_viewportInformationAndSceneIndicesPerViewportDataSet_mutex); + + for(auto& viewportInfoAndData :_viewportsInformationAndSceneIndicesPerViewportData){ + + InformationInterfaceImp::Get().SceneIndexRemoved(viewportInfoAndData.GetViewportInformation()); + + const Fvp::RenderIndexProxyPtr& renderIndexProxy = viewportInfoAndData.GetRenderIndexProxy();//Get the pointer on the renderIndexProxy + + if(renderIndexProxy){ + //Destroy the custom filtering scene indices chain + auto renderIndex = renderIndexProxy->GetRenderIndex(); + const auto& filteringSceneIndex = viewportInfoAndData.GetLastFilteringSceneIndexOfTheChain(); + if (renderIndex && filteringSceneIndex){ + renderIndex->RemoveSceneIndex(filteringSceneIndex);//Remove the whole chain from the render index + } + } } + + _viewportsInformationAndSceneIndicesPerViewportData.clear();//Delete all of them } } //End of namespace FVP_NS_DEF { diff --git a/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.h b/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.h index 3f202a8849..9d57cadac1 100644 --- a/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.h +++ b/lib/flowViewport/API/perViewportSceneIndicesData/fvpViewportInformationAndSceneIndicesPerViewportDataManager.h @@ -40,14 +40,21 @@ class FVP_API ViewportInformationAndSceneIndicesPerViewportDataManager static ViewportInformationAndSceneIndicesPerViewportDataManager& Get(); ///A new Hydra viewport was created - void AddViewportInformation(const InformationInterface::ViewportInformation& _viewportInfo); + void AddViewportInformation(const InformationInterface::ViewportInformation& viewportInfo, const Fvp::RenderIndexProxyPtr& renderIndexProxy); ///An Hydra viewport was deleted void RemoveViewportInformation(const std::string& modelPanel); - const ViewportInformationAndSceneIndicesPerViewportDataSet& GetViewportInfoAndSceneIndicesPerViewportData() const {return _viewportsInformationAndSceneIndicesPerViewportData;} + const ViewportInformationAndSceneIndicesPerViewportDataSet& GetAllViewportInfoAndData() const {return _viewportsInformationAndSceneIndicesPerViewportData;} + ViewportInformationAndSceneIndicesPerViewportDataSet& GetAllViewportInfoAndData() {return _viewportsInformationAndSceneIndicesPerViewportData;} - void UpdateRenderIndexProxy(const std::string& modelPanel, const Fvp::RenderIndexProxyPtr& renderIndexProxy); + const ViewportInformationAndSceneIndicesPerViewportData* GetViewportInfoAndDataFromViewportId(const std::string& viewportId)const; + ViewportInformationAndSceneIndicesPerViewportData* GetViewportInfoAndDataFromViewportId(const std::string& viewportId); + + const std::set& GetDataProducerSceneIndicesDataFromViewportId(const std::string& viewportId)const; + + bool ModelPanelIsAlreadyRegistered(const std::string& modelPanel)const; + void RemoveAllViewportsInformation(); private: ///Hydra viewport information diff --git a/lib/flowViewport/API/samples/CMakeLists.txt b/lib/flowViewport/API/samples/CMakeLists.txt index d19e5dd25f..2be4c6cedd 100644 --- a/lib/flowViewport/API/samples/CMakeLists.txt +++ b/lib/flowViewport/API/samples/CMakeLists.txt @@ -5,11 +5,13 @@ target_sources(${TARGET_NAME} PRIVATE fvpSelectionClientExample.cpp fvpInformationClientExample.cpp + fvpDataProducerSceneIndexExample.cpp ) set(HEADERS fvpSelectionClientExample.h fvpInformationClientExample.h + fvpDataProducerSceneIndexExample.h ) # ----------------------------------------------------------------------------- diff --git a/lib/flowViewport/API/samples/fvpDataProducerSceneIndexExample.cpp b/lib/flowViewport/API/samples/fvpDataProducerSceneIndexExample.cpp new file mode 100644 index 0000000000..eeb5823f3c --- /dev/null +++ b/lib/flowViewport/API/samples/fvpDataProducerSceneIndexExample.cpp @@ -0,0 +1,564 @@ +// +// Copyright 2023 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. +// + +/* This class is an example on how to inject Hydra prims into an Hydra viewport. We are dataProducer cubes meshes as primitives. + We are using a HdRetainedSceneIndex as it contains helper functions to add/remove/dirty prims. + We could also have done a subclass of HdRetainedSceneIndex as well. +*/ + +//Local headers +#include "fvpDataProducerSceneIndexExample.h" + +//USD headers +#include +#include +#include + +//Hydra headers +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//TBB headers to use multithreading +#include +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace PrototypeInstancing +{ + //Global functions to deal with geometry prototype instancing + + // Returns a typed sampled data source for a small number of VtArray types. + HdSampledDataSourceHandle getRetainedDataSource(VtValue const &val) + { + if (val.IsHolding()) { + return HdRetainedTypedSampledDataSource::New( + val.UncheckedGet()); + } + if (val.IsHolding()) { + return HdRetainedTypedSampledDataSource::New( + val.UncheckedGet()); + } + if (val.IsHolding()) { + return HdRetainedTypedSampledDataSource::New( + val.UncheckedGet()); + } + if (val.IsHolding()) { + return HdRetainedTypedSampledDataSource::New( + val.UncheckedGet()); + } + if (val.IsHolding()) { + return HdRetainedTypedSampledDataSource::New( + val.UncheckedGet()); + } + + TF_WARN("Unsupported primvar type %s", + val.GetTypeName().c_str()); + return HdRetainedTypedSampledDataSource::New(val); + } + + HdContainerDataSourceHandle constructPrimvarDataSource(const VtValue& value, const TfToken& interpolation, const TfToken& role) + { + static auto emptyArray = HdRetainedTypedSampledDataSource::New(VtIntArray()); + + return HdPrimvarSchema::BuildRetained(getRetainedDataSource(value), + HdSampledDataSourceHandle(), emptyArray, //is an indexer on the primVars which we don't use, primVars are not indexed in our case. + HdPrimvarSchema::BuildInterpolationDataSource(interpolation), + HdPrimvarSchema::BuildInterpolationDataSource(role)); + } + + //Create an instancer topology data source for the instancer, and supply the matrices as a by-instance varying primvar. + void createInstancer(const SdfPath& id, + const SdfPath& prototypeId, const VtIntArray& prototypeIndices, + const VtMatrix4dArray& matrices, HdRetainedSceneIndexRefPtr& retainedScene) + { + auto instanceIndices = Fvp::DataProducerSceneIndexExample::_InstanceIndicesDataSource::New(std::move(prototypeIndices)); + + HdDataSourceBaseHandle instancerTopologyData = + HdInstancerTopologySchema::Builder() + .SetPrototypes(HdRetainedTypedSampledDataSource>::New({ prototypeId })) + .SetInstanceIndices(instanceIndices) + .Build(); + + //The matrices are varying per instance + HdDataSourceBaseHandle primvarData = constructPrimvarDataSource(VtValue(matrices), HdPrimvarSchemaTokens->instance, HdInstancerTokens->instanceTransforms); + + HdDataSourceBaseHandle primvarsDs = HdRetainedContainerDataSource::New( + HdInstancerTokens->instanceTransforms, primvarData); + + HdRetainedContainerDataSourceHandle instancerData = + HdRetainedContainerDataSource::New( + HdInstancerTopologySchema::GetSchemaToken(), instancerTopologyData, + HdPrimvarsSchema::GetSchemaToken(), primvarsDs); + + // Add the primitives to the scene index + retainedScene->AddPrims({ { id, HdInstancerTokens->instancer, instancerData } }); + } +} + +namespace FVP_NS_DEF { + +DataProducerSceneIndexExample::DataProducerSceneIndexExample() +{ + //Is the root path for the cubes + _cubeRootPath = SdfPath("/DataProducerSceneIndexExample/cube"); + + //Is the instancer path when using instancing + _instancerPath = SdfPath("/DataProducerSceneIndexExample/instancer"); + + //Create the HdRetainedSceneIndex to be able to easily add primitives + _retainedSceneIndex = HdRetainedSceneIndex::New(); + + //set the container node inverse transform being identity + _containerNodeInvTransform = _containerNodeInvTransform.SetIdentity(); + + //Add all primitives + _AddAllPrims(); +} + +DataProducerSceneIndexExample::~DataProducerSceneIndexExample() +{ + removeDataProducerSceneIndex(); + _hydraInterface = nullptr; +} + +void DataProducerSceneIndexExample::setCubeGridParams(const CubeGridCreationParams& params) +{ + if (params == _currentCubeGridParams){ + return; + } + + _RemoveAllPrims();//Use old params from _currentCubeGridParams + _currentCubeGridParams = params;//Update _currentCubeGridParams + _AddAllPrims();//Use new params from updated _currentCubeGridParams +} + +// Compute the resulting axis aligned bounding box (AABB) of the 3D grid of cube primitives, is used by the DCC node to give its bounding box +void DataProducerSceneIndexExample::getPrimsBoundingBox(float& corner1X, float& corner1Y, float& corner1Z, + float& corner2X, float& corner2Y, float& corner2Z)const +{ + //Compute the initial cube prim AABB + const GfRange3d cubeRange ( + {-_currentCubeGridParams._halfSize, -_currentCubeGridParams._halfSize, -_currentCubeGridParams._halfSize}, + { _currentCubeGridParams._halfSize, _currentCubeGridParams._halfSize, _currentCubeGridParams._halfSize} + ); + const GfBBox3d cubeInitialBBox (cubeRange, _currentCubeGridParams._initalTransform); + + //Init some variables before looping + const GfVec3d initTrans = _currentCubeGridParams._initalTransform.ExtractTranslation(); + const int numLevelsX = _currentCubeGridParams._numLevelsX; + const int numLevelsY = _currentCubeGridParams._numLevelsY; + const int numLevelsZ = _currentCubeGridParams._numLevelsZ; + + //Will hold the combined AABB of all cube prims + GfBBox3d combinedAABBox(cubeInitialBBox); + + //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) { + //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)))); + + const GfBBox3d currentCubeAABB (cubeRange, currentXForm); + combinedAABBox = GfBBox3d::Combine(currentCubeAABB, combinedAABBox); + } + } + } + + //Get resulting AABB + const GfRange3d resultedAABB = combinedAABBox.ComputeAlignedRange(); + const GfVec3d& minAABB = resultedAABB.GetMin(); + const GfVec3d& maxAABB = resultedAABB.GetMax(); + + corner1X = minAABB.data()[0]; + corner1Y = minAABB.data()[1]; + corner1Z = minAABB.data()[2]; + + corner2X = maxAABB.data()[0]; + corner2Y = maxAABB.data()[1]; + corner2Z = maxAABB.data()[2]; +} + +void DataProducerSceneIndexExample::_AddAllPrims() +{ + if (_isEnabled || ! _retainedSceneIndex){ + return; + } + + if (_currentCubeGridParams._useInstancing) { + _AddAllPrimsWithInstancing(); + } else{ + _AddAllPrimsNoInstancing(); + } + + _isEnabled = true; +} + +void DataProducerSceneIndexExample::_AddAllPrimsWithInstancing() +{ + static const bool instancing = true; + + //We need to apply the inverse of the container node transform for instances so that it is not applied twice, once on the original cube and once on the instancer node. + GfMatrix4d transform = _currentCubeGridParams._initalTransform * _containerNodeInvTransform; + + //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, + transform, 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 int numLevelsX = _currentCubeGridParams._numLevelsX; + const int numLevelsY = _currentCubeGridParams._numLevelsY; + const int numLevelsZ = _currentCubeGridParams._numLevelsZ; + + VtIntArray prototypeIndices(totalSize, 0);//Resize and set the value of all elements to 0 which is our prototype index since we only have one prototype + GfMatrix4d identity; + identity = identity.SetIdentity(); + VtMatrix4dArray matrices(totalSize, identity);//Resize and set the initial value + + //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 size_t index = x + (numLevelsX * y) + (numLevelsX * numLevelsY * z); + + //Update prototype index + prototypeIndices[index] = index; + + //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); +} + +void DataProducerSceneIndexExample::_AddAllPrimsNoInstancing() +{ + static const 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; + + //Resize the addedPrims array to the exact size of the number of cube primitives we want in the grid. + addedPrims.resize(totalSize, cubePrimEntry); + + //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; + + //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) { + + //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)))); + + //Update information at the right place in the array + HdRetainedSceneIndex::AddedPrimEntry& currentCubePrimEntry = addedPrims[x + (numLevelsX * y) + (numLevelsX * numLevelsY * z)]; + + //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)); + + //Update the matrix in the data source for this cube prim + currentCubePrimEntry.dataSource = HdContainerDataSourceEditor(currentCubePrimEntry.dataSource) + .Set(HdXformSchema::GetDefaultLocator(), + HdXformSchema::Builder().SetMatrix(HdRetainedTypedSampledDataSource::New(currentXForm)) + .Build()) + .Finish(); + } + } + } + } + ); + + //Add all the cube prims to the retained scene index + _retainedSceneIndex->AddPrims(addedPrims); +} + +//Removes the cube prims from the scene index +void DataProducerSceneIndexExample::_RemoveAllPrims() +{ + if(! _retainedSceneIndex || ! _isEnabled)return; + + if (_currentCubeGridParams._useInstancing) { + _RemoveAllPrimsWithInstancing(); + } + else{ + _RemoveAllPrimsNoInstancing(); + } +} + +void DataProducerSceneIndexExample::_RemoveAllPrimsNoInstancing() +{ + //We delete a 3D grid of cubes + const size_t totalSize = _currentCubeGridParams._numLevelsX * _currentCubeGridParams._numLevelsY * _currentCubeGridParams._numLevelsZ; + + HdSceneIndexObserver::RemovedPrimEntries removedEntries; + HdSceneIndexObserver::RemovedPrimEntry removedEntry(_cubeRootPath); + + //Resize the removedEntries array to the exact size of the number of cube primitives we want to remove from the 3D grid. + removedEntries.resize(totalSize, removedEntry); + + //Init some variables before looping + const std::string cubeRootString = _cubeRootPath.GetString(); + const int numLevelsX = _currentCubeGridParams._numLevelsX; + const int numLevelsY = _currentCubeGridParams._numLevelsY; + const int numLevelsZ = _currentCubeGridParams._numLevelsZ; + + //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) { + + //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)); + + //Store the SdfPath of the cube prim to remove + removedEntries[x + (numLevelsX * y) + (numLevelsX * numLevelsY * z)].primPath = currentCubePath; + } + } + } + } + ); + + //Remove all the cube prims from the retained scene index + _retainedSceneIndex->RemovePrims(removedEntries); + + _isEnabled = false; +} + +void DataProducerSceneIndexExample::_RemoveAllPrimsWithInstancing() +{ + //In the case of instancing, there were only 2 things added, the cube and the instancer + _retainedSceneIndex->RemovePrims({{_cubeRootPath}, {_instancerPath}}); + + _isEnabled = false; +} + +//Create an Hydra cube primitive from these parameters +HdRetainedSceneIndex::AddedPrimEntry DataProducerSceneIndexExample::_CreateCubePrim(const SdfPath& cubePath, float halfSize, const GfVec3f& displayColor, + float opacity, const GfMatrix4d& transform, bool instanced)const +{ + using _PointArrayDs = HdRetainedTypedSampledDataSource>; + using _IntArrayDs = HdRetainedTypedSampledDataSource; + + //Cube hardcoded information + static const VtIntArray faceVertexCounts = {4, 4, 4, 4, 4, 4}; //Using quads + static const VtIntArray faceVertexIndices = {0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 1, 0, 1, 7, 5, 3, 6, 0, 2, 4}; + + const _IntArrayDs::Handle fvcDs = _IntArrayDs::New(faceVertexCounts); + const _IntArrayDs::Handle fviDs = _IntArrayDs::New(faceVertexIndices); + + //Vertices of the cube + const VtArray points = { + {-halfSize, -halfSize, halfSize}, + { halfSize, -halfSize, halfSize}, + {-halfSize, halfSize, halfSize}, + { halfSize, halfSize, halfSize}, + {-halfSize, halfSize, -halfSize}, + { halfSize, halfSize, -halfSize}, + {-halfSize, -halfSize, -halfSize}, + { halfSize, -halfSize, -halfSize} + }; + + const HdContainerDataSourceHandle meshDs = + HdMeshSchema::Builder() + .SetTopology(HdMeshTopologySchema::Builder() + .SetFaceVertexCounts(fvcDs) + .SetFaceVertexIndices(fviDs) + .Build()) + .Build(); + + const HdContainerDataSourceHandle primvarsDs = + HdRetainedContainerDataSource::New( + + //Create the vertices positions + HdPrimvarsSchemaTokens->points, + HdPrimvarSchema::Builder() + .SetPrimvarValue(_PointArrayDs::New(points)) + .SetInterpolation(HdPrimvarSchema:: + BuildInterpolationDataSource( + HdPrimvarSchemaTokens->vertex)) + .SetRole(HdPrimvarSchema:: + BuildRoleDataSource( + HdPrimvarSchemaTokens->point)) + .Build(), + + //Create the vertex colors + HdTokens->displayColor, + HdPrimvarSchema::Builder() + .SetIndexedPrimvarValue( + HdRetainedTypedSampledDataSource::New( + VtVec3fArray{ + displayColor, //Feel free to add more colors if needed, we only have one in this example. + })) + .SetIndices( + HdRetainedTypedSampledDataSource::New( + VtIntArray{ + 0, 0, 0, 0, 0, 0, 0, 0, //Is an index in the per vertex color array above + } + ) + ) + .SetInterpolation( + HdPrimvarSchema::BuildInterpolationDataSource( + HdPrimvarSchemaTokens->varying)) + .SetRole( + HdPrimvarSchema::BuildRoleDataSource( + HdPrimvarSchemaTokens->color))//vertex color + .Build(), + + //Create a face vertex opacity + HdTokens->displayOpacity, + 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 + })) + .SetInterpolation( + HdPrimvarSchema::BuildInterpolationDataSource( + HdPrimvarSchemaTokens->faceVarying)) + .Build() + ); + + //Add the cube primitive + HdRetainedSceneIndex::AddedPrimEntry addedPrim; + addedPrim.primPath = cubePath; + addedPrim.primType = HdPrimTypeTokens->mesh; + if (instanced){ + + const HdDataSourceBaseHandle instancedByData = + HdInstancedBySchema::Builder() + .SetPaths(HdRetainedTypedSampledDataSource>::New(VtArray({ _instancerPath }))) + .Build(); + + addedPrim.dataSource = HdRetainedContainerDataSource::New( + + //Create a matrix + HdXformSchemaTokens->xform, + HdXformSchema::Builder() + .SetMatrix(HdRetainedTypedSampledDataSource::New( + transform)).Build(), + + //create a mesh + HdMeshSchemaTokens->mesh, + meshDs, + HdPrimvarsSchemaTokens->primvars, + primvarsDs, + + HdInstancedBySchema::GetSchemaToken(), //Add the instancer path in the HdInstancedBySchema + instancedByData + ); + }else{ + + addedPrim.dataSource = HdRetainedContainerDataSource::New( + + //Create a matrix + HdXformSchemaTokens->xform, + HdXformSchema::Builder() + .SetMatrix(HdRetainedTypedSampledDataSource::New( + transform)).Build(), + + //create a mesh + HdMeshSchemaTokens->mesh, + meshDs, + HdPrimvarsSchemaTokens->primvars, + primvarsDs + ); + } + + return addedPrim; +} + +void DataProducerSceneIndexExample::addDataProducerSceneIndex() +{ + if (!_dataProducerSceneIndexAdded && _hydraInterface){ + _hydraInterface->addDataProducerSceneIndex(_retainedSceneIndex, _containerNode, DataProducerSceneIndexInterface::allViewports, DataProducerSceneIndexInterface::allRenderers, SdfPath::AbsoluteRootPath()); + _dataProducerSceneIndexAdded = true; + } +} + +void DataProducerSceneIndexExample::removeDataProducerSceneIndex() +{ + if (_dataProducerSceneIndexAdded && _hydraInterface){ + _hydraInterface->removeViewportDataProducerSceneIndex(_retainedSceneIndex, DataProducerSceneIndexInterface::allViewports); + _dataProducerSceneIndexAdded = false; + } +} + +void DataProducerSceneIndexExample::setContainerNodeInverseTransform(const PXR_NS::GfMatrix4d& InvTransform) +{ + _containerNodeInvTransform = InvTransform; + if (_currentCubeGridParams._useInstancing){ + //Update with the current transform + _RemoveAllPrims(); + _AddAllPrims(); + } +} + +} //End of namespace FVP_NS_DEF diff --git a/lib/flowViewport/API/samples/fvpDataProducerSceneIndexExample.h b/lib/flowViewport/API/samples/fvpDataProducerSceneIndexExample.h new file mode 100644 index 0000000000..78e547e757 --- /dev/null +++ b/lib/flowViewport/API/samples/fvpDataProducerSceneIndexExample.h @@ -0,0 +1,187 @@ +// +// Copyright 2023 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef FLOW_VIEWPORT_EXAMPLES_DATA_PRODUCER_SCENE_INDEX_EXAMPLE_H +#define FLOW_VIEWPORT_EXAMPLES_DATA_PRODUCER_SCENE_INDEX_EXAMPLE_H + +//Local headers +#include "flowViewport/api.h" +#include "flowViewport/API/fvpDataProducerSceneIndexInterface.h" + +//USD/Hydra headers +#include +#include +#include +#include +#include +#include + +namespace FVP_NS_DEF { + +/** This class is an example on how to inject Hydra prims into an Hydra viewport. +* An dataProducer scene index is a scene index that adds primitives to the current rendering. +* We are holding a HdRetainedSceneIndex in this class, as it contains helper functions to add/remove/dirty prims. +* Another possibility is that we could also have subclassed HdRetainedSceneIndex. +* We are creating a 3D grid of cubes using Hydra mesh primitives. +*/ +class FVP_API DataProducerSceneIndexExample +{ +public: + DataProducerSceneIndexExample(); + ~DataProducerSceneIndexExample(); + + ///Set the hydra interface + void setHydraInterface(DataProducerSceneIndexInterface* hydraInterface) {_hydraInterface = hydraInterface;} + + /**Is called by the DCC node to set its node pointer. + * Is also our triggering function to add the dataProducer scene index as we want to wait to have the DCC node pointer value initialized before dataProducer the scene index. + */ + void setContainerNode(void* node){_containerNode = node;} + void setContainerNodeInverseTransform(const PXR_NS::GfMatrix4d& InvTransform); + + /// Compute the resulting axis aligned bounding box of the 3D grid of cube primitives, is used by the DCC node to give its bounding box + void getPrimsBoundingBox(float& corner1X, float& corner1Y, float& corner1Z, float& corner2X, float& corner2Y, float& corner2Z)const; + + /// Are the creation parameters for a 3D Grid of Hydra cube primitives + 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; + } + + //Comparison operator + bool operator == (const CubeGridCreationParams& other)const{ + return (_numLevelsX == other._numLevelsX) && + (_numLevelsY == other._numLevelsY) && + (_numLevelsZ == other._numLevelsZ) && + (_halfSize == other._halfSize) && + (_color == other._color) && + (_opacity == other._opacity) && + (_initalTransform == other._initalTransform)&& + (_deltaTrans == other._deltaTrans) && + (_useInstancing == other._useInstancing); + } + + /// Number of X levels for the 3D grid of cube prims + int _numLevelsX; + /// Number of Y levels for the 3D grid of cube prims + int _numLevelsY; + /// Number of Z levels for the 3D grid of cube prims + int _numLevelsZ; + /// Half size of each cube in the 3D grid. + double _halfSize; + /// Color of each cube in the 3D grid. + PXR_NS::GfVec3f _color; + /// Opacity of each cube in the 3D grid. + double _opacity; + /// Initial transform of each cube in the 3D grid. + PXR_NS::GfMatrix4d _initalTransform; + /** _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. + * */ + PXR_NS::GfVec3f _deltaTrans; + /// if _useInstancing is true, then we are using Hydra instancing to create the cube prims, if it is false then we are not using Hydra instancing. + bool _useInstancing; + }; + + ///Set the CubeGridCreationParams + void setCubeGridParams(const CubeGridCreationParams& params); + + ///Call FlowViewport::DataProducerSceneIndexInterface::_AddDataProducerSceneIndex to add our dataProducer scene index to create the 3D grid of cubes + void addDataProducerSceneIndex(); + + ///Call the FlowViewport::DataProducerSceneIndexInterface::RemoveViewportDataProducerSceneIndex to remove our dataProducer scene index from the Hydra viewport + void removeDataProducerSceneIndex(); + + /// This class is about geometry (prototype) instancing and holds the instance indices + class _InstanceIndicesDataSource : public PXR_NS::HdVectorDataSource + { + public: + HD_DECLARE_DATASOURCE(_InstanceIndicesDataSource); + size_t GetNumElements() override { return 1; } + PXR_NS::HdDataSourceBaseHandle GetElement(size_t) override + { + return PXR_NS::HdRetainedTypedSampledDataSource::New(_indices); + } + private: + _InstanceIndicesDataSource(const PXR_NS::VtIntArray& indices) : _indices(indices) {} + PXR_NS::VtIntArray const _indices; + }; + +protected: + + ///Store the Hydra interface + DataProducerSceneIndexInterface* _hydraInterface {nullptr}; + + /// Enabled state of this client to enable/disable the scene indices + bool _isEnabled {false}; + + /// Container node from a DCC (maya or 3dsmax for example) + void* _containerNode {nullptr};//Is a MObject* for Maya or an INode* for 3ds max + + ///Is the container node inverse transform matrix to remove transform matrix being applied twice for instances + PXR_NS::GfMatrix4d _containerNodeInvTransform; + + /// Did we already add this dataProducer scene index to some render index ? + bool _dataProducerSceneIndexAdded {false}; + + ///Create a Hydra cube primitive + PXR_NS::HdRetainedSceneIndex::AddedPrimEntry _CreateCubePrim(const PXR_NS::SdfPath& cubePath, float halfSize, + const PXR_NS::GfVec3f& displayColor, float opacity, const PXR_NS::GfMatrix4d& transform, bool instanced)const; + + ///Create the 3D grid of Hydra cube primitives and add them to the retained scene index + void _AddAllPrims(); + + ///Remove the 3D grid of Hydra cube primitives from the retained scene index + void _RemoveAllPrims(); + + ///Create the 3D grid of Hydra cube primitives and add them to the retained scene index. We are not using Hydra instancing in this function. + void _AddAllPrimsNoInstancing(); + + ///Create the 3D grid of Hydra cube primitives and add them to the retained scene index. We are using Hydra instancing in this function. + void _AddAllPrimsWithInstancing(); + + ///Remove the 3D grid of Hydra cube primitives from the retained scene index. We are not using Hydra instancing in this function. + void _RemoveAllPrimsNoInstancing(); + + ///Remove the 3D grid of Hydra cube primitives from the retained scene index. We are using Hydra instancing in this function. + void _RemoveAllPrimsWithInstancing(); + + ///Aggregation of the retained scene index dataProducer prims into Hydra + PXR_NS::HdRetainedSceneIndexRefPtr _retainedSceneIndex {nullptr}; + + /// 3D grid of cubes primitives parameters + CubeGridCreationParams _currentCubeGridParams; + + /// Is the root path to create unique sdfPath for each prim of the 3D grid of cube prims + PXR_NS::SdfPath _cubeRootPath; + + /// Is the instancer path when using instancing + PXR_NS::SdfPath _instancerPath; +}; + +} //end of namespace FVP_NS_DEF { + +#endif //FLOW_VIEWPORT_EXAMPLES_DATA_PRODUCER_SCENE_INDEX_EXAMPLE_H \ No newline at end of file diff --git a/lib/flowViewport/sceneIndex/CMakeLists.txt b/lib/flowViewport/sceneIndex/CMakeLists.txt index ed1553fc99..7b7b938555 100644 --- a/lib/flowViewport/sceneIndex/CMakeLists.txt +++ b/lib/flowViewport/sceneIndex/CMakeLists.txt @@ -9,6 +9,7 @@ target_sources(${TARGET_NAME} fvpRenderIndexProxy.cpp fvpSelectionSceneIndex.cpp fvpWireframeSelectionHighlightSceneIndex.cpp + fvpParentDataModifierSceneIndex.cpp ) set(HEADERS @@ -19,6 +20,7 @@ set(HEADERS fvpRenderIndexProxyFwd.h fvpSelectionSceneIndex.h fvpWireframeSelectionHighlightSceneIndex.h + fvpParentDataModifierSceneIndex.h ) # ----------------------------------------------------------------------------- diff --git a/lib/flowViewport/sceneIndex/fvpParentDataModifierSceneIndex.cpp b/lib/flowViewport/sceneIndex/fvpParentDataModifierSceneIndex.cpp new file mode 100644 index 0000000000..5c7f3d3f34 --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpParentDataModifierSceneIndex.cpp @@ -0,0 +1,57 @@ +// +// Copyright 2023 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. +// + +//We use a this filtering scene index to update the data from a HdRetainedSceneIndex where we inserted a parent prim to be the parent of all prims from an dataProducer scene index +// which is hosted in a DCC node. +// We only update the data from the parent SdfPath, it has the same transform as the DCC node which contains the data dataProducer scene index. + +//Local headers +#include "fvpParentDataModifierSceneIndex.h" + +//USD/Hydra headers +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +namespace FVP_NS_DEF { + +//This is the function where we filter prims +HdSceneIndexPrim ParentDataModifierSceneIndex::GetPrim(const SdfPath& primPath) const +{ + if (!_GetInputSceneIndex()){ + return HdSceneIndexPrim(); + } + + HdSceneIndexPrim prim = _GetInputSceneIndex()->GetPrim(primPath); + + if (prim.dataSource && (primPath == _parentPath)){ + //Use an HdContainerDataSourceEditor to overwrite the values for the transform and visibility attributes + prim.dataSource = + HdContainerDataSourceEditor(prim.dataSource) + .Set(HdXformSchema::GetDefaultLocator(), HdXformSchema::Builder().SetMatrix(HdRetainedTypedSampledDataSource::New(_transformMatrix)).Build()) + .Set(HdVisibilitySchema::GetDefaultLocator(), HdVisibilitySchema::BuildRetained(HdRetainedTypedSampledDataSource::New(_visible))) + .Finish(); + } + + return prim;//returned the modified prim +} + +}//End of namespace FVP_NS_DEF + +PXR_NAMESPACE_CLOSE_SCOPE \ No newline at end of file diff --git a/lib/flowViewport/sceneIndex/fvpParentDataModifierSceneIndex.h b/lib/flowViewport/sceneIndex/fvpParentDataModifierSceneIndex.h new file mode 100644 index 0000000000..bf83edc27e --- /dev/null +++ b/lib/flowViewport/sceneIndex/fvpParentDataModifierSceneIndex.h @@ -0,0 +1,97 @@ +// +// Copyright 2023 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef FLOW_VIEWPORT_SCENEINDEX_PARENT_DATA_MODIFIER_SCENE_INDEX_H +#define FLOW_VIEWPORT_SCENEINDEX_PARENT_DATA_MODIFIER_SCENE_INDEX_H + +//Local headers +#include "flowViewport/api.h" + +//Hydra headers +#include +#include +#include + +//The Pixar's namespace needs to be at the highest namespace level for TF_DECLARE_WEAK_AND_REF_PTRS to work. +PXR_NAMESPACE_OPEN_SCOPE + +namespace FVP_NS_DEF { + +//We use a filtering scene index to update the data from a HdRetainedSceneIndex where we inserted a parent prim to be the parent of all prims from a data dataProducer scene index +// which is hosted in a DCC node. + +class ParentDataModifierSceneIndex; +TF_DECLARE_WEAK_AND_REF_PTRS(ParentDataModifierSceneIndex); + +class ParentDataModifierSceneIndex : public HdSingleInputFilteringSceneIndexBase +{ +public: + using ParentClass = HdSingleInputFilteringSceneIndexBase; + + static ParentDataModifierSceneIndexRefPtr New(const HdSceneIndexBaseRefPtr& _inputSceneIndex){ + return TfCreateRefPtr(new ParentDataModifierSceneIndex(_inputSceneIndex)); + } + + void SetParentPath (const SdfPath& parentPath) {_parentPath = parentPath;} + void SetParentTransformMatrix (const GfMatrix4d& transformMatrix) {_transformMatrix = transformMatrix;} + void SetParentVisibility (bool visible) {_visible = visible;} + + // From HdSceneIndexBase + HdSceneIndexPrim GetPrim(const SdfPath& primPath) const override; + SdfPathVector GetChildPrimPaths(const SdfPath& primPath) const override { //defined in this header file as we do no modification to it + if (_GetInputSceneIndex()){ + return _GetInputSceneIndex()->GetChildPrimPaths(primPath); + } + + return {}; + } + + ~ParentDataModifierSceneIndex() override = default; + +protected: + void _PrimsAdded( + const HdSceneIndexBase& sender, + const HdSceneIndexObserver::AddedPrimEntries& entries) override final + { + _SendPrimsAdded(entries); + } + + void _PrimsRemoved( + const HdSceneIndexBase& sender, + const HdSceneIndexObserver::RemovedPrimEntries& entries) override final + { + _SendPrimsRemoved(entries); + } + + void _PrimsDirtied( + const HdSceneIndexBase& sender, + const HdSceneIndexObserver::DirtiedPrimEntries& entries) override final + { + _SendPrimsDirtied(entries); + } + + SdfPath _parentPath; + GfMatrix4d _transformMatrix; + bool _visible = true; +private: + ParentDataModifierSceneIndex(const HdSceneIndexBaseRefPtr& _inputSceneIndex) : ParentClass(_inputSceneIndex){} +}; + +} //End of namespace FVP_NS_DEF + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif //FLOW_VIEWPORT_SCENEINDEX_PARENT_DATA_MODIFIER_SCENE_INDEX_H + diff --git a/lib/mayaHydra/CMakeLists.txt b/lib/mayaHydra/CMakeLists.txt index df12f980bd..5b5863a4e6 100644 --- a/lib/mayaHydra/CMakeLists.txt +++ b/lib/mayaHydra/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(mayaPlugin) add_subdirectory(hydraExtensions) add_subdirectory(ufeExtensions) +add_subdirectory(flowViewportAPIExamples) \ No newline at end of file diff --git a/lib/mayaHydra/flowViewportAPIExamples/CMakeLists.txt b/lib/mayaHydra/flowViewportAPIExamples/CMakeLists.txt new file mode 100644 index 0000000000..f05a524afd --- /dev/null +++ b/lib/mayaHydra/flowViewportAPIExamples/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(flowViewportAPIMayaLocator) diff --git a/lib/mayaHydra/flowViewportAPIExamples/flowViewportAPIMayaLocator/CMakeLists.txt b/lib/mayaHydra/flowViewportAPIExamples/flowViewportAPIMayaLocator/CMakeLists.txt new file mode 100644 index 0000000000..b66f690bea --- /dev/null +++ b/lib/mayaHydra/flowViewportAPIExamples/flowViewportAPIMayaLocator/CMakeLists.txt @@ -0,0 +1,102 @@ +set(TARGET_NAME flowViewportAPIMayaLocator) + +add_library(${TARGET_NAME} SHARED) + +# ----------------------------------------------------------------------------- +# sources +# ----------------------------------------------------------------------------- +target_sources(${TARGET_NAME} + PRIVATE + flowViewportAPIMayaLocator.cpp +) + +set(HEADERS + PRIVATE +) + +# ----------------------------------------------------------------------------- +# compiler configuration +# ----------------------------------------------------------------------------- +target_compile_definitions(${TARGET_NAME} + PRIVATE + # M3dView needs this (technically, MNativeWindowHdl.h) + # private atm because M3dView is only used in .cpp right now + # gcc will NOT set this automatically + $<$:LINUX> + # Not sure if msvcc sets this automatically, but won't hurt to redefine + $<$:_WIN32> + MFB_ALT_PACKAGE_NAME="${TARGET_NAME}" # This is used by Pixar HdSceneIndexPluginRegistry::Define to create a unique name +) + +# ----------------------------------------------------------------------------- +# include directories +# ----------------------------------------------------------------------------- +if(DEFINED MAYAUSD_VERSION) + target_compile_definitions(${TARGET_NAME} + PRIVATE + MAYAUSD_VERSION=${MAYAUSD_VERSION} + ) +endif() + +if(DEFINED MAYAHYDRA_VERSION) + target_compile_definitions(${TARGET_NAME} + PRIVATE + MAYAHYDRA_VERSION=${MAYAHYDRA_VERSION} + ) +endif() +if(DEFINED MAYAHYDRA_CUT_ID) + target_compile_definitions(${TARGET_NAME} + PRIVATE + MAYAHYDRA_CUT_ID=${MAYAHYDRA_CUT_ID} + ) +endif() + +mayaHydra_compile_config(${TARGET_NAME}) + +# ----------------------------------------------------------------------------- +# link libraries +# ----------------------------------------------------------------------------- +target_link_libraries(${TARGET_NAME} + PRIVATE + mayaHydraLib +) + +# ----------------------------------------------------------------------------- +# properties +# ----------------------------------------------------------------------------- +maya_set_plugin_properties(${TARGET_NAME}) + +# ----------------------------------------------------------------------------- +# run-time search paths +# ----------------------------------------------------------------------------- +if(IS_MACOSX OR IS_LINUX) + mayaUsd_init_rpath(rpath "lib/maya") + if(DEFINED MAYAUSD_TO_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../${MAYAUSD_TO_USD_RELATIVE_PATH}/lib") + elseif(DEFINED PXR_USD_LOCATION) + mayaUsd_add_rpath(rpath "${PXR_USD_LOCATION}/lib") + endif() + if (IS_LINUX AND DEFINED MAYAUSD_TO_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../${MAYAUSD_TO_USD_RELATIVE_PATH}/lib64") + endif() + if(IS_MACOSX AND DEFINED MAYAUSD_TO_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../../../Maya.app/Contents/MacOS") + endif() + mayaUsd_add_rpath(rpath "../") + mayaUsd_install_rpath(rpath ${TARGET_NAME}) +endif() + +# ----------------------------------------------------------------------------- +# install +# ----------------------------------------------------------------------------- +install(TARGETS ${TARGET_NAME} + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/maya) + +if(IS_WINDOWS) + install(FILES $ + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/maya OPTIONAL) +endif() + +set(LIBFILENAME ${CMAKE_SHARED_LIBRARY_PREFIX}${TARGET_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}) +set(PLUG_INFO_LIBRARY_PATH "../../../${LIBFILENAME}") +set(PLUG_INFO_RESOURCE_PATH "resources") \ No newline at end of file diff --git a/lib/mayaHydra/flowViewportAPIExamples/flowViewportAPIMayaLocator/flowViewportAPIMayaLocator.cpp b/lib/mayaHydra/flowViewportAPIExamples/flowViewportAPIMayaLocator/flowViewportAPIMayaLocator.cpp new file mode 100644 index 0000000000..9e5fea3ca3 --- /dev/null +++ b/lib/mayaHydra/flowViewportAPIExamples/flowViewportAPIMayaLocator/flowViewportAPIMayaLocator.cpp @@ -0,0 +1,607 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. +// + +//Flow viewport headers +#include +#include +#include +#include +#include + +//Maya hydra headers +#include + +//Maya headers +#include +#include +#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. + +/*To create an instance of this node in maya, please use the following MEL command : +createNode("FlowViewportAPIMayaLocator") +*/ + +PXR_NAMESPACE_USING_DIRECTIVE + +///Maya Locator node subclass to create filtering and dataProducer scene indices example, to be used with the flow viewport API. +class FlowViewportAPIMayaLocator : public MPxLocatorNode +{ +public: + ~FlowViewportAPIMayaLocator() override; + + MStatus compute( const MPlug& plug, MDataBlock& data ) override; + + bool isBounded() const override; + MBoundingBox boundingBox() const override; + + MStatus preEvaluation(const MDGContext& context, const MEvaluationNode& evaluationNode) override; + void getCacheSetup(const MEvaluationNode& evalNode, MNodeCacheDisablingInfo& disablingInfo, MNodeCacheSetupInfo& cacheSetupInfo, MObjectArray& monitoredAttributes) const override; + + void SetCubeGridParametersFromAttributes(); + void SetupFlowViewportInterfaces(); + + //static members + static void* creator(); + static MStatus initialize(); + static MTypeId id; + static MString nodeClassification; + + //Maya node attributes that helps creating a 3D grid of Hydra cube mesh primitives to demonstrate how to inject some primitives in an Hydra viewport + static MObject mNumCubeLevelsX; + static MObject mNumCubeLevelsY; + static MObject mNumCubeLevelsZ; + static MObject mCubeHalfSize; + static MObject mCubeInitalTransform; + static MObject mCubeColor; + static MObject mCubeOpacity; + static MObject mCubesUseInstancing; + static MObject mCubesDeltaTrans; + static MObject mDummyInput;//Dummy input to trigger a call to compute + static MObject mDummyOutput;//Dummy output to trigger a call to compute + + ///3D Grid of cube mesh primitives creation parameters for the dataProducer scene index + Fvp::DataProducerSceneIndexExample::CubeGridCreationParams _cubeGridParams; + ///_hydraViewportDataProducerSceneIndexExample is what will inject the 3D grid of Hydra cube mesh primitives into the viewport + Fvp::DataProducerSceneIndexExample _hydraViewportDataProducerSceneIndexExample; + +protected: + /// _hydraViewportInformationClient is the viewport information example for an Hydra viewport. + std::shared_ptr _hydraViewportInformationClient; + ///To be used in hydra viewport API to pass the Maya node's MObject for setting callbacks for filtering and dataProducer scene indices + MObject _thisMObject; + ///To check if the MObject of this node has changed + MObject _oldMObject; + ///To hold the attributeChangedCallback Id to be able to react when the 3D grid creation parameters attributes from this node change. + MCallbackId _cbAttributeChangedId = 0; + ///To hold the afterOpenCallback Id to be able to react when a File Open has happened. + MCallbackId _cbAfterOpenId = 0; + ///To hold the transformAttributeChangedCallback Id to be able to react when the transform matrix has changed. + MCallbackId _cbParentAttributeChangedId; + +private: + // Private Constructor + FlowViewportAPIMayaLocator(); + /// Init flag to be able to do things only once for this node + bool _init = false; +}; + +namespace +{ + //Get node inverse transform matrix (needed by instancing in a dataProducerSceneIndex) + MStatus GetNodeInverseTransform(MObject& mObj, GfMatrix4d& outInvTransform) + { + outInvTransform = outInvTransform.SetIdentity(); + + MStatus stat; + + //Try directly if the mObj is a transform node + MObject nodeToGetTransform = mObj; + if ( ! mObj.hasFn(MFn::kTransform)){ + //Try with the parent dag path from that node + MDagPath dagPath = MDagPath::getAPathTo(mObj, &stat); + CHECK_MSTATUS(stat); + dagPath.pop(); + nodeToGetTransform = dagPath.node(); + if ( ! nodeToGetTransform.hasFn(MFn::kTransform)){ + return MStatus::kInvalidParameter;//Stopping here + } + } + + MFnTransform transform(nodeToGetTransform, &stat); + CHECK_MSTATUS(stat); + if (MStatus::kSuccess != stat){ + return MStatus::kFailure; + } + + MMatrix transformMatrix = transform.transformationMatrix(&stat); + CHECK_MSTATUS(stat); + if (MStatus::kSuccess != stat){ + return MStatus::kFailure; + } + + MMatrix invTransform = transformMatrix.inverse(); + memcpy(outInvTransform.GetArray(), invTransform[0], sizeof(double) * 16);//convert from MMatrix to GfMatrix4d + + return MStatus::kSuccess; + } + + //Callback when an attribute of this Maya node changes + void transformAttributeChangedCallback(MNodeMessage::AttributeMessage msg, MPlug& plug, MPlug & otherPlug, void* dataProducerSceneIndexData) + { + //Dealing with the transform only + MFnAttribute attr (plug.attribute()); + if (MayaHydra::IsAMayaTransformAttributeName(attr.name())){ + + if (! dataProducerSceneIndexData){ + return; + } + FlowViewportAPIMayaLocator* flowViewportAPIMayaLocator = reinterpret_cast(dataProducerSceneIndexData); + + GfMatrix4d nodeInvTransform; + MObject mObj = flowViewportAPIMayaLocator->thisMObject(); + MStatus stat = GetNodeInverseTransform(mObj, nodeInvTransform); + CHECK_MSTATUS(stat); + if (MStatus::kSuccess == stat){ + flowViewportAPIMayaLocator->_hydraViewportDataProducerSceneIndexExample.setContainerNodeInverseTransform(nodeInvTransform); + } + } + } + + //Callback when an attribute of this Maya node changes + void attributeChangedCallback(MNodeMessage::AttributeMessage msg, MPlug& plug, MPlug & otherPlug, void* dataProducerSceneIndexData) + { + if (! dataProducerSceneIndexData){ + return; + } + + FlowViewportAPIMayaLocator* flowViewportAPIMayaLocator = reinterpret_cast(dataProducerSceneIndexData); + + MPlug parentPlug = plug.parent(); + if (plug == flowViewportAPIMayaLocator->mDummyInput || plug == flowViewportAPIMayaLocator->mDummyOutput){ + return; //These attributes are not related to the cubes grid + } + + if (plug == flowViewportAPIMayaLocator->mNumCubeLevelsX){ + flowViewportAPIMayaLocator->_cubeGridParams._numLevelsX = plug.asInt(); + }else + if (plug == flowViewportAPIMayaLocator->mNumCubeLevelsY){ + flowViewportAPIMayaLocator->_cubeGridParams._numLevelsY = plug.asInt(); + }else + if (plug == flowViewportAPIMayaLocator->mNumCubeLevelsZ){ + flowViewportAPIMayaLocator->_cubeGridParams._numLevelsZ = plug.asInt(); + }else + if (plug == flowViewportAPIMayaLocator->mCubeHalfSize){ + flowViewportAPIMayaLocator->_cubeGridParams._halfSize = plug.asDouble(); + }else + if (plug == flowViewportAPIMayaLocator->mCubeInitalTransform){ + auto dataHandle = plug.asMDataHandle(); + const MMatrix mat = dataHandle.asMatrix(); + memcpy(flowViewportAPIMayaLocator->_cubeGridParams._initalTransform.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 + const double3& color = dataHandle.asDouble3(); + flowViewportAPIMayaLocator->_cubeGridParams._color.data()[0] = color[0];//Implicit conversion from double to float + flowViewportAPIMayaLocator->_cubeGridParams._color.data()[1] = color[1]; + flowViewportAPIMayaLocator->_cubeGridParams._color.data()[2] = color[2]; + }else + if (plug == flowViewportAPIMayaLocator->mCubeColor){ + auto dataHandle = plug.asMDataHandle(); + const double3& color = dataHandle.asDouble3(); + flowViewportAPIMayaLocator->_cubeGridParams._color.data()[0] = color[0];//Implicit conversion from double to float + flowViewportAPIMayaLocator->_cubeGridParams._color.data()[1] = color[1]; + flowViewportAPIMayaLocator->_cubeGridParams._color.data()[2] = color[2]; + }else + if (plug == flowViewportAPIMayaLocator->mCubeOpacity){ + flowViewportAPIMayaLocator->_cubeGridParams._opacity = plug.asDouble(); + }else + if (plug == flowViewportAPIMayaLocator->mCubesUseInstancing){ + flowViewportAPIMayaLocator->_cubeGridParams._useInstancing = plug.asBool(); + }else + if (parentPlug == flowViewportAPIMayaLocator->mCubesDeltaTrans){ + auto dataHandle = parentPlug.asMDataHandle();//Using parent plug as this is composed of 3 doubles + const double3& deltaTrans = dataHandle.asDouble3(); + 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->mCubesDeltaTrans){ + auto dataHandle = plug.asMDataHandle(); + const double3& deltaTrans = dataHandle.asDouble3(); + 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{ + return; //Not a grid cube attribute + } + + + flowViewportAPIMayaLocator->_hydraViewportDataProducerSceneIndexExample.setCubeGridParams(flowViewportAPIMayaLocator->_cubeGridParams); + } + + template MStatus GetAttributeValue(T& outVal, const MObject& node, const MObject& attr) + { + MPlug plug(node, attr); + return plug.getValue(outVal); + } + + void GetMatrixAttributeValue(MMatrix& outVal, const MObject& node, const MObject& attr) + { + MPlug plug(node, attr); + MObject oMatrix; + plug.getValue(oMatrix); + + MFnMatrixData fnData(oMatrix); + outVal = fnData.matrix(); + } + + void GetDouble3AttributeValue(double3& outVal, const MObject& node, const MObject& attr) + { + MPlug plug(node, attr); + MObject oDouble3; + plug.getValue(oDouble3); + + MFnNumericData fnData(oDouble3); + fnData.getData( outVal[0], outVal[1], outVal[2] ); + } + + //Callback after a File Open + void afterOpenCallback (void *clientData) + { + if (! clientData){ + return; + } + + FlowViewportAPIMayaLocator* flowViewportAPIMayaLocator = reinterpret_cast(clientData); + flowViewportAPIMayaLocator->SetCubeGridParametersFromAttributes(); + flowViewportAPIMayaLocator->SetupFlowViewportInterfaces(); + } + +}//end of anonymous namespace + +//Initialization of static members +MTypeId FlowViewportAPIMayaLocator::id( 0x90517 ); +MString FlowViewportAPIMayaLocator::nodeClassification("hydraAPIExample/geometry/FlowViewportAPIMayaLocator"); + +MObject FlowViewportAPIMayaLocator::mNumCubeLevelsX; +MObject FlowViewportAPIMayaLocator::mNumCubeLevelsY; +MObject FlowViewportAPIMayaLocator::mNumCubeLevelsZ; +MObject FlowViewportAPIMayaLocator::mCubeHalfSize; +MObject FlowViewportAPIMayaLocator::mCubeInitalTransform; +MObject FlowViewportAPIMayaLocator::mCubeColor; +MObject FlowViewportAPIMayaLocator::mCubeOpacity; +MObject FlowViewportAPIMayaLocator::mCubesUseInstancing; +MObject FlowViewportAPIMayaLocator::mCubesDeltaTrans; +MObject FlowViewportAPIMayaLocator::mDummyInput; +MObject FlowViewportAPIMayaLocator::mDummyOutput; + +//Macro to create input attribute for the maya node +#define MAKE_INPUT(attr) \ + CHECK_MSTATUS(attr.setKeyable(true) ); \ + CHECK_MSTATUS(attr.setStorable(true) ); \ + CHECK_MSTATUS(attr.setReadable(true) ); \ + CHECK_MSTATUS(attr.setWritable(true) ); \ + CHECK_MSTATUS(attr.setAffectsAppearance(true) ); + +//Macro to create output attribute for the maya node +#define MAKE_OUTPUT(attr) \ +CHECK_MSTATUS ( attr.setKeyable(false) ); \ +CHECK_MSTATUS ( attr.setStorable(false) ); \ +CHECK_MSTATUS ( attr.setReadable(true) ); \ +CHECK_MSTATUS ( attr.setWritable(false) ); + + +FlowViewportAPIMayaLocator::FlowViewportAPIMayaLocator() +{ + //Get the flow viewport API hydra interfaces + int majorVersion = 0; + int minorVersion = 0; + int patchLevel = 0; + + //Get the version of the flow viewport APIinterface + Fvp::VersionInterface::Get().GetVersion(majorVersion, minorVersion, patchLevel); + + //DataProducer scene index interface + Fvp::DataProducerSceneIndexInterface& dataProducerSceneIndexInterface = Fvp::DataProducerSceneIndexInterface::get(); + + //Store the interface pointer into our client for later + _hydraViewportDataProducerSceneIndexExample.setHydraInterface(&dataProducerSceneIndexInterface); + + //Viewport Information interface + Fvp::InformationInterface& informationInterface = Fvp::InformationInterface::Get(); + + _hydraViewportInformationClient = std::make_shared(); + + //Register this viewport information client, so it gets called when Hydra viewports scene indices are created/removed + informationInterface.RegisterInformationClient(_hydraViewportInformationClient); + + //Add a callback after a load scene + _cbAfterOpenId = MSceneMessage::addCallback(MSceneMessage::kAfterOpen, afterOpenCallback, ((void*)this)) ; +} + +//This is called only when our node is destroyed and the undo queue flushed. +FlowViewportAPIMayaLocator::~FlowViewportAPIMayaLocator() +{ + //Remove the callbacks + if (_cbAttributeChangedId){ + CHECK_MSTATUS(MMessage::removeCallback(_cbAttributeChangedId)); + _cbAttributeChangedId = 0; + } + + if (_cbAfterOpenId){ + CHECK_MSTATUS(MSceneMessage::removeCallback(_cbAfterOpenId)); + _cbAfterOpenId = 0; + } + + if (_cbParentAttributeChangedId){ + CHECK_MSTATUS(MSceneMessage::removeCallback(_cbParentAttributeChangedId)); + _cbParentAttributeChangedId = 0; + } + + //The DataProducerSceneIndexExample in its destructor removes itself by calling DataProducerSceneIndexExample::RemoveDataProducerSceneIndex() + + //Unregister viewport information client + Fvp::InformationInterface& informationInterface = Fvp::InformationInterface::Get(); + informationInterface.UnregisterInformationClient(_hydraViewportInformationClient); +} + +void FlowViewportAPIMayaLocator::SetCubeGridParametersFromAttributes() +{ + const MObject mObj = thisMObject(); + if (mObj.isNull()){ + return; + } + + GetAttributeValue(_cubeGridParams._numLevelsX, mObj, FlowViewportAPIMayaLocator::mNumCubeLevelsX); + GetAttributeValue(_cubeGridParams._numLevelsY, mObj, FlowViewportAPIMayaLocator::mNumCubeLevelsY); + GetAttributeValue(_cubeGridParams._numLevelsZ, mObj, FlowViewportAPIMayaLocator::mNumCubeLevelsZ); + + GetAttributeValue(_cubeGridParams._halfSize, mObj, FlowViewportAPIMayaLocator::mCubeHalfSize); + + MMatrix mat; + GetMatrixAttributeValue(mat, mObj, FlowViewportAPIMayaLocator::mCubeInitalTransform); + memcpy(_cubeGridParams._initalTransform.GetArray(), mat[0], sizeof(double) * 16);//convert from MMatrix to GfMatrix4d + + double3 color; + GetDouble3AttributeValue(color, mObj, FlowViewportAPIMayaLocator::mCubeColor); + _cubeGridParams._color.data()[0] = color[0];//Implicit conversion from double to float + _cubeGridParams._color.data()[1] = color[1]; + _cubeGridParams._color.data()[2] = color[2]; + + GetAttributeValue(_cubeGridParams._opacity, mObj, FlowViewportAPIMayaLocator::mCubeOpacity); + GetAttributeValue(_cubeGridParams._useInstancing, mObj, FlowViewportAPIMayaLocator::mCubesUseInstancing); + + double3 deltaTrans; + GetDouble3AttributeValue(deltaTrans, mObj, FlowViewportAPIMayaLocator::mCubesDeltaTrans); + _cubeGridParams._deltaTrans.data()[0] = deltaTrans[0];//Implicit conversion from double to float + _cubeGridParams._deltaTrans.data()[1] = deltaTrans[1]; + _cubeGridParams._deltaTrans.data()[2] = deltaTrans[2]; + + _hydraViewportDataProducerSceneIndexExample.setCubeGridParams(_cubeGridParams); +} + +void FlowViewportAPIMayaLocator::SetupFlowViewportInterfaces() +{ + if (_thisMObject.isNull()){ + MObject currentMObj = thisMObject(); + if (_oldMObject.isNull() || _oldMObject != currentMObj){ + _thisMObject = currentMObj; + _oldMObject = _thisMObject; + } + + if(_thisMObject.isNull()){ + return; + } + } + + //Store the MObject* of the maya node in various classes + //Set the maya node as a parent for this data producer scene index so that when the node is hidden/deleted/moved it gets applied to the prims produced + GfMatrix4d nodeInvTransform; + GetNodeInverseTransform(_thisMObject, nodeInvTransform); + _hydraViewportDataProducerSceneIndexExample.setContainerNode(&_thisMObject); + _hydraViewportDataProducerSceneIndexExample.setContainerNodeInverseTransform(nodeInvTransform); + _hydraViewportDataProducerSceneIndexExample.addDataProducerSceneIndex(); +} + +MStatus FlowViewportAPIMayaLocator::compute( const MPlug& plug, MDataBlock& dataBlock) +{ + //Do it only once per call to this function per node + + if( ! _init){ + SetCubeGridParametersFromAttributes(); + + MObject currentMObj = thisMObject(); + //Add the callback when an attribute of this node changes + _cbAttributeChangedId = MNodeMessage::addAttributeChangedCallback(currentMObj, attributeChangedCallback, ((void*)this)); + + //Also monitor parent DAG node to be able to update the scene index if the parent transform is modified + MStatus stat; + MDagPath parentDagPath = MDagPath::getAPathTo(currentMObj, &stat); + CHECK_MSTATUS(stat); + parentDagPath.pop(); + MObject parentObj = parentDagPath.node(); + _cbParentAttributeChangedId = MNodeMessage::addAttributeChangedCallback(parentObj, transformAttributeChangedCallback, this); + + _init = true; + } + + //The MObject can change if the node gets deleted and deletion being undone, so always update it in our records + MObject currentMObj = thisMObject(); + if (_oldMObject.isNull() || _oldMObject != currentMObj){ + + _thisMObject = currentMObj; + _oldMObject = _thisMObject; + + SetupFlowViewportInterfaces(); + } + + + return MS::kSuccess; +} + +bool FlowViewportAPIMayaLocator::isBounded() const +{ + return true; +} + +//We return as a bounding box the bounding box of the dataProducer hydra data +MBoundingBox FlowViewportAPIMayaLocator::boundingBox() const +{ + float corner1X, corner1Y, corner1Z, corner2X, corner2Y, corner2Z; + _hydraViewportDataProducerSceneIndexExample.getPrimsBoundingBox(corner1X, corner1Y, corner1Z, corner2X, corner2Y, corner2Z); + return MBoundingBox( {corner1X, corner1Y, corner1Z}, {corner2X, corner2Y, corner2Z}); +} + +// Called before this node is evaluated by Evaluation Manager +MStatus FlowViewportAPIMayaLocator::preEvaluation( + const MDGContext& context, + const MEvaluationNode& evaluationNode) +{ + return MStatus::kSuccess; +} + +void FlowViewportAPIMayaLocator::getCacheSetup(const MEvaluationNode& evalNode, MNodeCacheDisablingInfo& disablingInfo, MNodeCacheSetupInfo& cacheSetupInfo, MObjectArray& monitoredAttributes) const +{ + MPxLocatorNode::getCacheSetup(evalNode, disablingInfo, cacheSetupInfo, monitoredAttributes); + assert(!disablingInfo.getCacheDisabled()); + cacheSetupInfo.setPreference(MNodeCacheSetupInfo::kWantToCacheByDefault, true); +} + +void* FlowViewportAPIMayaLocator::creator() +{ + return new FlowViewportAPIMayaLocator; +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +// Plugin Registration +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +MStatus FlowViewportAPIMayaLocator::initialize() +{ + MStatus status; + + //Create input attributes for the 3D grid of Hydra cube mesh primitives creation for the dataProducer scene index + MFnNumericAttribute nAttr; + MFnMatrixAttribute mAttr; + + mNumCubeLevelsX = nAttr.create("numCubesX", "nX", MFnNumericData::kInt, 1.0, &status); + MAKE_INPUT(nAttr); + CHECK_MSTATUS ( nAttr.setDefault(10) ); + + mNumCubeLevelsY = nAttr.create("numCubesY", "nY", MFnNumericData::kInt, 1.0, &status); + MAKE_INPUT(nAttr); + CHECK_MSTATUS ( nAttr.setDefault(10) ); + + mNumCubeLevelsZ = nAttr.create("numCubesZ", "nZ", MFnNumericData::kInt, 1.0, &status); + MAKE_INPUT(nAttr); + CHECK_MSTATUS ( nAttr.setDefault(1) ); + + mCubeHalfSize = nAttr.create("cubeHalfSize", "cHS", MFnNumericData::kDouble, 1.0, &status); + MAKE_INPUT(nAttr); + CHECK_MSTATUS ( nAttr.setDefault(2.0) ); + + mCubeInitalTransform = mAttr.create("cubeInitalTransform", "cIT", MFnMatrixAttribute::kDouble, &status); + MAKE_INPUT(mAttr); + + mCubeColor = nAttr.create("cubeColor", "cC", MFnNumericData::k3Double, 1.0, &status); + MAKE_INPUT(nAttr); + CHECK_MSTATUS ( nAttr.setDefault(0.0, 1.0, 0.0) ); + + mCubeOpacity = nAttr.create("cubeOpacity", "cO", MFnNumericData::kDouble, 1.0, &status); + MAKE_INPUT(nAttr); + CHECK_MSTATUS ( nAttr.setDefault(0.8) ); + + mCubesUseInstancing = nAttr.create("cubesUseInstancing", "cUI", MFnNumericData::kBoolean, 1.0, &status); + MAKE_INPUT(nAttr); + CHECK_MSTATUS ( nAttr.setDefault(false) ); + + mCubesDeltaTrans = nAttr.create("cubesDeltaTrans", "cDT", MFnNumericData::k3Double, 1.0, &status); + MAKE_INPUT(nAttr); + CHECK_MSTATUS ( nAttr.setDefault(5.0, 5.0, 5.0) ); + + //Create dummy output attribute to trigger a call to the compute function on demand. as it's in the compute fonction that we add our scene indices + mDummyInput = nAttr.create("dummyInput", "dI", MFnNumericData::kInt, 1.0, &status); + MAKE_INPUT(nAttr); + CHECK_MSTATUS ( nAttr.setDefault(1) ); + + //Create dummy output attribute to trigger a call to the compute function on demand. as it's in the compute fonction that we add our scene indices + mDummyOutput = nAttr.create("dummyOutput", "dO", MFnNumericData::kInt, 1.0, &status); + MAKE_OUTPUT(nAttr); + CHECK_MSTATUS ( nAttr.setDefault(1) ); + + CHECK_MSTATUS ( addAttribute(mNumCubeLevelsX)); + CHECK_MSTATUS ( addAttribute(mNumCubeLevelsY)); + CHECK_MSTATUS ( addAttribute(mNumCubeLevelsZ)); + CHECK_MSTATUS ( addAttribute(mCubeHalfSize)); + CHECK_MSTATUS ( addAttribute(mCubeInitalTransform)); + CHECK_MSTATUS ( addAttribute(mCubeColor)); + CHECK_MSTATUS ( addAttribute(mCubeOpacity)); + CHECK_MSTATUS ( addAttribute(mCubesUseInstancing)); + CHECK_MSTATUS ( addAttribute(mCubesDeltaTrans)); + CHECK_MSTATUS ( addAttribute(mDummyInput)); + CHECK_MSTATUS ( addAttribute(mDummyOutput)); + + CHECK_MSTATUS ( attributeAffects(mDummyInput, mDummyOutput)); + + + return status; +} + +MStatus initializePlugin( MObject obj ) +{ + MStatus status; + static const char * pluginVersion = "1.0"; + MFnPlugin plugin( obj, PLUGIN_COMPANY, pluginVersion, "Any"); + + status = plugin.registerNode( + "FlowViewportAPIMayaLocator", + FlowViewportAPIMayaLocator::id, + &FlowViewportAPIMayaLocator::creator, + &FlowViewportAPIMayaLocator::initialize, + MPxNode::kLocatorNode, + &FlowViewportAPIMayaLocator::nodeClassification); + if (!status) { + status.perror("registerNode"); + return status; + } + + return status; +} + +MStatus uninitializePlugin( MObject obj) +{ + MStatus status; + MFnPlugin plugin( obj ); + + status = plugin.deregisterNode( FlowViewportAPIMayaLocator::id ); + if (!status) { + status.perror("deregisterNode"); + return status; + } + return status; +} diff --git a/lib/mayaHydra/hydraExtensions/mayaUtils.cpp b/lib/mayaHydra/hydraExtensions/mayaUtils.cpp index d5f308e84b..07ddcc8cfc 100644 --- a/lib/mayaHydra/hydraExtensions/mayaUtils.cpp +++ b/lib/mayaHydra/hydraExtensions/mayaUtils.cpp @@ -22,9 +22,45 @@ #include #include #include +#include +#include + +namespace +{ + //To compute the size of an array automatically + template + constexpr std::size_t arraySize(T(&)[N]) noexcept + { + return N; + } + + ///Is an array of strings that are all maya transform attributes names + const char* kMayaTransformAttributesStrings[] = {"translateX", "translateY", "translateZ", + "rotatePivotTranslateX", "rotatePivotTranslateY", "rotatePivotTranslateZ", + "rotatePivotX", "rotatePivotY", "rotatePivotZ", + "rotateX", "rotateY","rotateZ", + "rotateAxisX", "rotateAxisY", "rotateAxisZ", + "scalePivotTranslateX", "scalePivotTranslateY", "scalePivotTranslateZ", + "scalePivotX", "scalePivotY", "scalePivotZ", + "shearXY", "shearXZ", "shearYZ", + "scaleX", "scaleY", "scaleZ", + "worldMatrix", + "localPositionX", "localPositionY", "localPosition", + "translate", "rotate", "scale" + }; + //Convert from const char* [] to MStringArray + const MStringArray transformAttrNames(kMayaTransformAttributesStrings, arraySize(kMayaTransformAttributesStrings)); + + //For visibility attributes + const char* visibilityNames[] = {"visibility"}; + + //For visibility attributes + const MStringArray visibilityAttrNames = MStringArray(visibilityNames, arraySize(visibilityNames)); +} namespace MAYAHYDRA_NS_DEF { + MStatus GetDagPathFromNodeName(const MString& nodeName, MDagPath& outDagPath) { MSelectionList selectionList; @@ -70,4 +106,46 @@ bool IsUfeItemFromMayaUsd(const MObject& obj, MStatus* returnStatus) return IsUfeItemFromMayaUsd(dagPath, returnStatus); } +MStatus GetObjectsFromNodeNames(const MStringArray& nodeNames, MObjectArray & outObjects) +{ + const unsigned int numObjects = outObjects.length() ; + if (nodeNames.length() != numObjects){ + return MStatus::kInvalidParameter; + } + + for (auto& obj : outObjects){ + obj = MObject::kNullObj; + } + + MStatus status; + MSelectionList sList; + for (const auto& nodeName : nodeNames){ + status = sList.add(nodeName); + CHECK_MSTATUS_AND_RETURN_IT(status); + } + + for (unsigned int i=0;i +#include +#include +#include +#include +#include + +namespace +{ + //Callback when an attribute of a maya node changes + void attributeChangedCallback(MNodeMessage::AttributeMessage msg, MPlug& plug, MPlug & otherPlug, void* mayaDataProducerSceneIndexData) + { + if( ! mayaDataProducerSceneIndexData){ + return; + } + + //We care only about transform and visibility attributes + MFnAttribute attr (plug.attribute()); + if (MayaHydra::IsAMayaTransformAttributeName(attr.name())){ + PXR_NS::MayaDataProducerSceneIndexData* MayaDataProducerSceneIndexData = (PXR_NS::MayaDataProducerSceneIndexData*)mayaDataProducerSceneIndexData; + MayaDataProducerSceneIndexData->UpdateTransformFromMayaNode(); + } else{ + bool isVisible = false; + if (MayaHydra::IsAMayaVisibilityAttribute(plug, isVisible)){ + PXR_NS::MayaDataProducerSceneIndexData* MayaDataProducerSceneIndexData = (PXR_NS::MayaDataProducerSceneIndexData*)mayaDataProducerSceneIndexData; + MayaDataProducerSceneIndexData->UpdateVisibilityFromDCCNode(isVisible); + } + } + } + + //Callback when a maya node is added + void onDagNodeAdded(MObject& obj, void* data) + { + //Since when an object is recreated (when doing an undo after a delete of the node), the MObject from the same node are different but their MObjectHandle::hashCode() are identical so + //this is how we identify which object we need to deal with. + PXR_NS::MayaDataProducerSceneIndexData* mayaDataProducerSceneIndexData = (PXR_NS::MayaDataProducerSceneIndexData*)data; + const MObjectHandle objHandle(obj); + if(mayaDataProducerSceneIndexData && mayaDataProducerSceneIndexData->_mObjHandle.isValid() && objHandle.hashCode() == mayaDataProducerSceneIndexData->_mObjHandle.hashCode()){ + mayaDataProducerSceneIndexData->UpdateVisibilityFromDCCNode(true); + } + } + + //Callback when a maya node is removed + void onDagNodeRemoved(MObject& obj, void* data) + { + //Since when an object is recreated (when doing an undo after a delete of the node), the MObject from the same node are different but their MObjectHandle::hashCode() are identical so + //this is how we identify which object we need to deal with. + PXR_NS::MayaDataProducerSceneIndexData* mayaDataProducerSceneIndexData = (PXR_NS::MayaDataProducerSceneIndexData*)data; + const MObjectHandle objHandle(obj); + if(mayaDataProducerSceneIndexData && mayaDataProducerSceneIndexData->_mObjHandle.isValid() && objHandle.hashCode() == mayaDataProducerSceneIndexData->_mObjHandle.hashCode()){ + mayaDataProducerSceneIndexData->UpdateVisibilityFromDCCNode(false); + } + } +} + +PXR_NAMESPACE_USING_DIRECTIVE + +MayaDataProducerSceneIndexData::MayaDataProducerSceneIndexData(const FVP_NS_DEF::DataProducerSceneIndexDataBase::CreationParameters& params) + : FVP_NS_DEF::DataProducerSceneIndexDataBase(params) +{ + //When the user has passed a maya node we are adding callbacks on various changes to be able to act on the dataProducer scene index prims automatically + if (_dccNode){ + MObject* mObj = reinterpret_cast(_dccNode); + _mObjHandle = MObjectHandle(*mObj); + + //The user provided a DCC node, it's a maya MObject in maya hydra + _CreateSceneIndexChainForDataProducerSceneIndex(); + + MCallbackId cbId = MNodeMessage::addAttributeChangedCallback(*mObj, attributeChangedCallback, this); + if (cbId){ + _nodeMessageCallbackIds.append(cbId); + } + + _mayaNodeDagPath = MDagPath::getAPathTo(*mObj); + + //Also monitor parent DAG node to be able to update the scene index if the parent transform is modified or the visibility changed + MDagPath parentDagPath = _mayaNodeDagPath; + parentDagPath.pop(); + MObject parentObj = parentDagPath.node(); + cbId = 0; + cbId = MNodeMessage::addAttributeChangedCallback(parentObj, attributeChangedCallback, this); + if (cbId){ + _nodeMessageCallbackIds.append(cbId); + } + + //Get node type name to filter by node type for callbacks + MFnDependencyNode dep(*mObj); + const MString nodeTypeName = dep.typeName(); + + //Setup node added callback, filter by node type using nodeTypeName + cbId = 0; + cbId = MDGMessage::addNodeAddedCallback(onDagNodeAdded, nodeTypeName, this); + if (cbId) { + _dGMessageCallbackIds.append(cbId); + } + + //Setup node remove callback, filter by node type using nodeTypeName + cbId = 0; + cbId = MDGMessage::addNodeRemovedCallback(onDagNodeRemoved, nodeTypeName, this); + if (cbId) { + _dGMessageCallbackIds.append(cbId); + } + } +} + +MayaDataProducerSceneIndexData::~MayaDataProducerSceneIndexData() +{ + CHECK_MSTATUS(MNodeMessage::removeCallbacks (_nodeMessageCallbackIds)); + CHECK_MSTATUS(MMessage::removeCallbacks (_dGMessageCallbackIds)); +} + +void MayaDataProducerSceneIndexData::UpdateTransformFromMayaNode() +{ + if (_mayaNodeDagPath.isValid()){ + //Get Maya transform value + //Convert from Maya matrix to GfMatrix4d + const MMatrix mayaMat = _mayaNodeDagPath.inclusiveMatrix(); + + //Copy Maya matrix into _parentMatrix member of this struct + memcpy(_parentMatrix.GetArray(), mayaMat[0], sizeof(double) * 16); + + //Update transform in Hydra + UpdateHydraTransformFromParentPath(); + } +} + +std::string MayaDataProducerSceneIndexData::GetDCCNodeName() const +{ + std::string outNodeName; + if (_mObjHandle.isValid() && _mObjHandle.isAlive()){ + MFnDependencyNode dep(_mObjHandle.object()); + outNodeName = std::string(dep.name().asChar()); + MAYAHYDRA_NS_DEF::SanitizeNameForSdfPath(outNodeName); + } + + return outNodeName; +} diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraMayaDataProducerSceneIndexData.h b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraMayaDataProducerSceneIndexData.h new file mode 100644 index 0000000000..1e5c67ec03 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraMayaDataProducerSceneIndexData.h @@ -0,0 +1,72 @@ +// +// Copyright 2023 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 MAYA_HYDRA_HYDRAEXTENSIONS_SCENEINDEX_MAYA_DATA_PRODUCER_SCENE_INDEX_DATA_H +#define MAYA_HYDRA_HYDRAEXTENSIONS_SCENEINDEX_MAYA_DATA_PRODUCER_SCENE_INDEX_DATA_H + +//Flow viewport headers +#include + +//Maya headers +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaDataProducerSceneIndexData; +TF_DECLARE_WEAK_AND_REF_PTRS(MayaDataProducerSceneIndexData);//Be able to use Ref counting pointers on MayaDataProducerSceneIndexData + +/** MayaDataProducerSceneIndexData is storing information about a custom dataProducer scene index. +* Since an instance of the MayaDataProducerSceneIndexData class can be shared between multiple viewports in our records, we need ref counting. +*/ + class MayaDataProducerSceneIndexData : public FVP_NS_DEF::DataProducerSceneIndexDataBase +{ +public: + + ///Is the way to get access to a new instance of MayaDataProducerSceneIndexData using ref counting + static TfRefPtr New(const FVP_NS_DEF::DataProducerSceneIndexDataBase::CreationParameters& params) { + return TfCreateRefPtr(new MayaDataProducerSceneIndexData(params)); + } + + ///Destructor + ~MayaDataProducerSceneIndexData() override; + + //The following members are optional and used only when a dccNode was passed in the constructor of DataProducerSceneIndexDataBase + + /// Is the MObjectHandle of the maya node shape, it may be invalid if no maya node MObject pointer was passed. + MObjectHandle _mObjHandle; + /// Is the dag path of the Maya node passed in AppendSceneIndex. It is used only when a dccNode was passed. + MDagPath _mayaNodeDagPath; + /// Are the callbacks Ids set in maya to forward the changes done in maya on the dataProducer scene index. + MCallbackIdArray _nodeMessageCallbackIds; + /// Are the callbacks Ids set in maya to handle delete and deletion undo/redo + MCallbackIdArray _dGMessageCallbackIds; + + /// Provide the node name from maya + std::string GetDCCNodeName() const override; + + ///Update transform from maya node + void UpdateTransformFromMayaNode(); + +private: + MayaDataProducerSceneIndexData(const FVP_NS_DEF::DataProducerSceneIndexDataBase::CreationParameters& params); +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif //MAYA_HYDRA_HYDRAEXTENSIONS_SCENEINDEX_MAYA_DATA_PRODUCER_SCENE_INDEX_DATA_H + diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraMayaDataProducerSceneIndexDataConcreteFactory.cpp b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraMayaDataProducerSceneIndexDataConcreteFactory.cpp new file mode 100644 index 0000000000..14c144b627 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraMayaDataProducerSceneIndexDataConcreteFactory.cpp @@ -0,0 +1,29 @@ +// +// Copyright 2023 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. +// + +//Local headers +#include "mayaHydraMayaDataProducerSceneIndexDataConcreteFactory.h" +#include "mayaHydraMayaDataProducerSceneIndexData.h" + +namespace MAYAHYDRA_NS_DEF{ + +PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBaseRefPtr +MayaDataProducerSceneIndexDataConcreteFactory::createDataProducerSceneIndexDataBase(const PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBase::CreationParameters& params) +{ + return PXR_NS::MayaDataProducerSceneIndexData::New(params); +} + +}//end of namespace MAYAHYDRA_NS_DEF \ No newline at end of file diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraMayaDataProducerSceneIndexDataConcreteFactory.h b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraMayaDataProducerSceneIndexDataConcreteFactory.h new file mode 100644 index 0000000000..8fade9ffed --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraMayaDataProducerSceneIndexDataConcreteFactory.h @@ -0,0 +1,41 @@ +// +// Copyright 2023 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 MAYA_HYDRA_HYDRA_EXTENSIONS_SCENE_INDEX_MAYA_DATA_PRODUCER_SCENE_INDEX_CONCRETE_FACTORY_H +#define MAYA_HYDRA_HYDRA_EXTENSIONS_SCENE_INDEX_MAYA_DATA_PRODUCER_SCENE_INDEX_CONCRETE_FACTORY_H + +//MayaHydra headers +#include "mayaHydraLib/api.h" +#include "mayaHydraLib/mayaHydra.h" + +//Flow Viewport headers +#include + +namespace MAYAHYDRA_NS_DEF { + +/** Since Flow viewport is DCC agnostic, the DCC implements a concrete factory subclassing that class to provide specific DCC implementation of the classes mentioned. +* */ +class MayaDataProducerSceneIndexDataConcreteFactory : public Fvp::DataProducerSceneIndexDataAbstractFactory +{ +public: + /// CreateDataProducerSceneIndexDataBase creates a subclass of DataProducerSceneIndexDataBaseRefPtr with specific DCC variables that flow viewport cannot manage since it's DCC agnostic + PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBaseRefPtr + createDataProducerSceneIndexDataBase(const PXR_NS::FVP_NS_DEF::DataProducerSceneIndexDataBase::CreationParameters& params) override; +}; + +}//end of namespace MAYAHYDRA_NS_DEF + +#endif //MAYA_HYDRA_HYDRA_EXTENSIONS_SCENE_INDEX_MAYA_DATA_PRODUCER_SCENE_INDEX_CONCRETE_FACTORY_H + diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.h b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.h index 088cb7c7e6..4476b013d3 100644 --- a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.h +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.h @@ -57,7 +57,7 @@ class RenderIndexProxy; PXR_NAMESPACE_OPEN_SCOPE class MayaHydraSceneIndex; -TF_DECLARE_REF_PTRS(MayaHydraSceneIndex); +TF_DECLARE_WEAK_AND_REF_PTRS(MayaHydraSceneIndex); /** * \brief MayaHydraSceneIndex is a scene index to produce the hydra scene from Maya native scene. */ diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndexDataFactoriesSetup.cpp b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndexDataFactoriesSetup.cpp new file mode 100644 index 0000000000..8c0b5efcf3 --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndexDataFactoriesSetup.cpp @@ -0,0 +1,37 @@ +// +// Copyright 2023 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. +// + +/* This class creates the scene index data factories and set them up into the flow viewport library to be able to create DCC +* specific scene index data classes without knowing their content in Flow viewport. +* This is done in the constructor of this class +*/ + +//Local headers +#include "mayaHydraSceneIndexDataFactoriesSetup.h" +#include "mayaHydraMayaDataProducerSceneIndexDataConcreteFactory.h" + +//Flow viewport headers +#include + +namespace MAYAHYDRA_NS_DEF{ + +SceneIndexDataFactoriesSetup::SceneIndexDataFactoriesSetup() +{ + static MayaDataProducerSceneIndexDataConcreteFactory dataProducerFactory; + Fvp::DataProducerSceneIndexInterfaceImp::get().setSceneIndexDataFactory(dataProducerFactory); +} + +}//end of namespace MAYAHYDRA_NS_DEF \ No newline at end of file diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndexDataFactoriesSetup.h b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndexDataFactoriesSetup.h new file mode 100644 index 0000000000..e0fd83293c --- /dev/null +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndexDataFactoriesSetup.h @@ -0,0 +1,38 @@ +// +// Copyright 2023 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 MAYA_HYDRA_SCENE_INDEX_DATA_FACTORIES_SETUP +#define MAYA_HYDRA_SCENE_INDEX_DATA_FACTORIES_SETUP + +//Local headers +#include "mayaHydraLib/api.h" +#include "mayaHydraLib/mayaHydra.h" + +namespace MAYAHYDRA_NS_DEF{ + +/** This class creates the scene index data factories and set them up into the flow viewport library to be able to create DCC +* specific scene index data classes without knowing their content in Flow viewport. +* This is done in the constructor of this class +*/ +class SceneIndexDataFactoriesSetup +{ +public: + MAYAHYDRALIB_API SceneIndexDataFactoriesSetup(); +}; + +}//end of namespace MAYAHYDRA_NS_DEF + +#endif //MAYA_HYDRA_SCENE_INDEX_DATA_FACTORIES_SETUP + diff --git a/lib/mayaHydra/mayaPlugin/renderOverride.cpp b/lib/mayaHydra/mayaPlugin/renderOverride.cpp index 3d412b4eab..8fda7a7a17 100644 --- a/lib/mayaHydra/mayaPlugin/renderOverride.cpp +++ b/lib/mayaHydra/mayaPlugin/renderOverride.cpp @@ -410,14 +410,6 @@ SdfPath MtohRenderOverride::RendererSceneDelegateId(TfToken rendererName, TfToke return SdfPath(); } -void MtohRenderOverride::_UpdateRenderIndexProxyIfRequired(const MHWRender::MDrawContext& drawContext) -{ - //Get model panel name - MString modelPanel; - drawContext.renderingDestination(modelPanel); - Fvp::ViewportInformationAndSceneIndicesPerViewportDataManager::Get().UpdateRenderIndexProxy(modelPanel.asChar(), _renderIndexProxy); -} - void MtohRenderOverride::_DetectMayaDefaultLighting(const MHWRender::MDrawContext& drawContext) { constexpr auto considerAllSceneLights = MHWRender::MDrawContext::kFilteredIgnoreLightLimit; @@ -575,8 +567,6 @@ MStatus MtohRenderOverride::Render( return MStatus::kFailure; } - _UpdateRenderIndexProxyIfRequired(drawContext); - _DetectMayaDefaultLighting(drawContext); if (_needsClear.exchange(false)) { ClearHydraResources(); @@ -590,6 +580,30 @@ MStatus MtohRenderOverride::Render( } } + //This code with strings comparison will go away when doing multi viewports + MString panelName; + auto framecontext = getFrameContext(); + if (framecontext){ + framecontext->renderingDestination(panelName); + auto& manager = Fvp::ViewportInformationAndSceneIndicesPerViewportDataManager::Get(); + if (false == manager.ModelPanelIsAlreadyRegistered(panelName.asChar())){ + //Get information from viewport + std::string cameraName; + + M3dView view; + if (M3dView::getM3dViewFromModelPanel(panelName, view)){ + MDagPath dpath; + view.getCamera(dpath); + MFnCamera viewCamera(dpath); + cameraName = viewCamera.name().asChar(); + } + + //Create an HydraViewportInformation + const Fvp::InformationInterface::ViewportInformation hydraViewportInformation(std::string(panelName.asChar()), cameraName); + manager.AddViewportInformation(hydraViewportInformation, _renderIndexProxy); + } + } + const auto displayStyle = drawContext.getDisplayStyle(); MayaHydraParams delegateParams = _globals.delegateParams; delegateParams.displaySmoothMeshes = !(displayStyle & MHWRender::MFrameContext::kFlatShaded); @@ -824,6 +838,9 @@ void MtohRenderOverride::ClearHydraResources() TF_DEBUG(MAYAHYDRALIB_RENDEROVERRIDE_RESOURCES) .Msg("MtohRenderOverride::ClearHydraResources(%s)\n", _rendererDesc.rendererName.GetText()); + //We don't have any viewport using Hydra any more + Fvp::ViewportInformationAndSceneIndicesPerViewportDataManager::Get().RemoveAllViewportsInformation(); + _mayaHydraSceneProducer.reset(); _selectionSceneIndex.Reset(); _selection.reset(); @@ -1001,21 +1018,6 @@ MStatus MtohRenderOverride::setup(const MString& destination) newCallbacks.append(id); } - //Get information from viewport - std::string cameraName; - - M3dView view; - if (M3dView::getM3dViewFromModelPanel(destination, view)){//destination is a panel name - MDagPath dpath; - view.getCamera(dpath); - MFnCamera viewCamera(dpath); - cameraName = viewCamera.name().asChar(); - } - - //Create an HydraViewportInformation - const Fvp::InformationInterface::ViewportInformation hydraViewportInformation(std::string(destination.asChar()), cameraName); - Fvp::ViewportInformationAndSceneIndicesPerViewportDataManager::Get().AddViewportInformation(hydraViewportInformation); - _renderPanelCallbacks.emplace_back(destination, newCallbacks); } diff --git a/lib/mayaHydra/mayaPlugin/renderOverride.h b/lib/mayaHydra/mayaPlugin/renderOverride.h index 7e3d96a090..5d30e7ce24 100644 --- a/lib/mayaHydra/mayaPlugin/renderOverride.h +++ b/lib/mayaHydra/mayaPlugin/renderOverride.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -144,7 +145,6 @@ class MtohRenderOverride : public MHWRender::MRenderOverride HdRenderDelegate* _GetRenderDelegate(); void _SetRenderPurposeTags(const MayaHydraParams& delegateParams); void _CreateSceneIndicesChainAfterMergingSceneIndex(); - void _UpdateRenderIndexProxyIfRequired(const MHWRender::MDrawContext& drawContext); void _PickByRegion( HdxPickHitVector& outHits, @@ -242,6 +242,12 @@ class MtohRenderOverride : public MHWRender::MRenderOverride std::unique_ptr _mayaHydraSceneProducer; + /** This class creates the scene index data factories and set them up into the flow viewport library to be able to create DCC + * specific scene index data classes without knowing their content in Flow viewport. + * This is done in the constructor of this class + */ + MAYAHYDRA_NS_DEF::SceneIndexDataFactoriesSetup _sceneIndexDataFactoriesSetup; + SdfPath _ID; GfVec4d _viewport; diff --git a/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt b/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt index e614f5a072..cda1513991 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt +++ b/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt @@ -13,6 +13,7 @@ set(TEST_SCRIPT_FILES testStageAddPrim.py testNewSceneWithStage.py testDirectionalLights.py + testFlowViewportAPI.py cpp/testColorPreferences.py cpp/testCppFramework.py cpp/testMayaSceneFlattening.py @@ -21,6 +22,7 @@ set(TEST_SCRIPT_FILES cpp/testSelectionSceneIndex.py cpp/testWireframeSelectionHighlightSceneIndex.py cpp/testFlowViewportAPIViewportInformation.py + cpp/testFlowViewportAPIAddPrims.py ) # Test use of mesh adapter code for mesh support, using environment variable. diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeCreated.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeCreated.png new file mode 100644 index 0000000000000000000000000000000000000000..e0a000f100ae9ac5a66820ea40af74640743b199 GIT binary patch literal 9129 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 literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeDeleted.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeDeleted.png new file mode 100644 index 0000000000000000000000000000000000000000..7763adc644e9a852fe6befe3e6708e09c58ef00d GIT binary patch literal 2105 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~V9glC$suNIKWzyTr{7^Xen z9tos4^WEHv!2%%Sme#g&3=HhsJzX3_D(1Ys+Ua13_t3H1Q>)E85IOasnH-9G^t=xx3@k+ow>TYI=^1bjtw_~U{B@eXAe$I)jl4+ zKCZUp@2{_qmtJSvFU;8ZUe3Pm&x51g;_kb5@1AYAto;2wS)2NQHJ@KzUVirO?(+Wv zLO-4z`uFeOrR;{Iwza>$WZUS)?b#uj{Kny!gyPK468iD`_Wb|&`1ska+1KTC_ohY6 zYS>WaKhNf;VE@~U9_2NKw$Civ3fFvnc)0!S^tl}!8?u-Vt=e1n_t%3bPoA9ryt^;{ zXmVVC$DaECb(^f=RK{$rE!iqN>F1owmoG0~dY#`}NFeO> z)z#tb`S|61+|8m#7fWng9hQ|!A&N$we^v>p)&HVDZdDl0DF&)avZBMY4OP1R|`*^VZ?S*F^ zn#El^GyBDwYrw!d40O0{noQl-&oa~XPfRb&uetc>-o1MtYho-O%x7U~I1(Gdpu)uDprkNLjRwJB fN(G+Ua13_t3H1Q>)E85IOasnH-9G^t=xx3@k+ow>TYI=^1bjtw_~U{B@eXAe$I)jl4+ zKCZUp@2{_qmtJSvFU;8ZUe3Pm&x51g;_kb5@1AYAto;2wS)2NQHJ@KzUVirO?(+Wv zLO-4z`uFeOrR;{Iwza>$WZUS)?b#uj{Kny!gyPK468iD`_Wb|&`1ska+1KTC_ohY6 zYS>WaKhNf;VE@~U9_2NKw$Civ3fFvnc)0!S^tl}!8?u-Vt=e1n_t%3bPoA9ryt^;{ zXmVVC$DaECb(^f=RK{$rE!iqN>F1owmoG0~dY#`}NFeO> z)z#tb`S|61+|8m#7fWng9hQ|!A&N$we^v>p)&HVDZdDl0DF&)avZBMY4OP1R|`*^VZ?S*F^ zn#El^GyBDwYrw!d40O0{noQl-&oa~XPfRb&uetc>-o1MtYho-O%x7U~I1(Gdpu)uDprkNLjRwJB fN(GN&O{6C_i;VqNL9d|t#3KA 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 literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeDeletedUndoAgain.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeDeletedUndoAgain.png new file mode 100644 index 0000000000000000000000000000000000000000..a7cb0b382735a5517509314339aedce1ca1fd28e GIT binary patch literal 5757 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 literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeHidden.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeHidden.png new file mode 100644 index 0000000000000000000000000000000000000000..7763adc644e9a852fe6befe3e6708e09c58ef00d GIT binary patch literal 2105 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~V9glC$suNIKWzyTr{7^Xen z9tos4^WEHv!2%%Sme#g&3=HhsJzX3_D(1Ys+Ua13_t3H1Q>)E85IOasnH-9G^t=xx3@k+ow>TYI=^1bjtw_~U{B@eXAe$I)jl4+ zKCZUp@2{_qmtJSvFU;8ZUe3Pm&x51g;_kb5@1AYAto;2wS)2NQHJ@KzUVirO?(+Wv zLO-4z`uFeOrR;{Iwza>$WZUS)?b#uj{Kny!gyPK468iD`_Wb|&`1ska+1KTC_ohY6 zYS>WaKhNf;VE@~U9_2NKw$Civ3fFvnc)0!S^tl}!8?u-Vt=e1n_t%3bPoA9ryt^;{ zXmVVC$DaECb(^f=RK{$rE!iqN>F1owmoG0~dY#`}NFeO> z)z#tb`S|61+|8m#7fWng9hQ|!A&N$we^v>p)&HVDZdDl0DF&)avZBMY4OP1R|`*^VZ?S*F^ zn#El^GyBDwYrw!d40O0{noQl-&oa~XPfRb&uetc>-o1MtYho-O%x7U~I1(Gdpu)uDprkNLjRwJB fN(GN&O{6C_i;VqNL9d|t#3KA z(sV^L?C`MP-6xXOZhJGG>NI>0cW1oxZ;Q46y|g1QvC}tT`Xue{%X>FT+3KORk(`Umu%ImL(pu8%9Y(*X24%HJB`RUd%zFT{*d z0AjwIaL6t|ly5j1vI}MeK2Vq3mIiu@5QFdALH;G)nP=67UZuTUn~JZtI#7%l;i5Zj z{#kCKQCN=CWntA!pgbY&oZ+<{lKTvc+A`}u*GAT6#(P-lON|;NvwhApqh~o?f-~<1 zI6(jzqTZiw9{=Kk&mjrhf+V?nV;u7ARtgMy<-%De>4jDJ?JRaBbw0!Vpb+10`x;Kq zTZ^N^oE1*+!3AtiqBm6ES#guG`M;im!gQFBF$=)?_THTN(*z5l$`~k%=V)A!x3b#zuF1LU#*8 z?tK0eKd}Dfn)@(ek&rWnuDw*Ce~l;K)=%h^dZJ~yS;@DL!*!>;-`#O(30}U*41f$e zsU=V-JM4<^Fn0B>SntG_&A!ozDc9{a{cw(cX?1~1*tIM(ZqS1Lv(erR`b6>Y-U z+%QO9)@k;?ufbT?o!pvYCymSFDb`&FmMnErq{nJMh3`5E@m;YtrKc>!v|gH!&?wbe zc-7BR6-kg2IW3}HU}{^q$FQaO=;qR%)%!)Z1p;&Z{$XmB1$M1?Exhx9Mpt!sK(pm0 zDh{)%qc5>5PNVHSnQ^{&lKFLMbHYWGS6{C|qKlWZ{^#2jP2B>Q!w4K&>+ViL4+_eY zh`|;-3&D6@=o_$no!xcR_@uJ=tJ6>UiPOHFG*8#ll97onSL1jK4PcCUx~_B?=y8oUqBZgr&I%-bmR(L zx6XR79;CV|)5z#GVqtd05v`UP{s*y|)R)miq?(_{RkaZuQg>C(4s zKwhJuObve6>5>&b$4q#Hc%r~5Z}_Ge2$X{E16yDoCzHAZYF)}@Y=77lybQN+2o2DR z-G4zCLz(*jf=GvNqMRC9eVe)LrV0|llqvXv z%K%gRDjD3RW{Cg9C8i5Pe_%ieiP%L)& z^_)?)pIe9S?Gv1|c&X!tsE}YiF(eES1FWdTf8*2I())Z)F1mDJKZ#`FD`GR(T4D(0 z(VF|7=Mr0!Q7VKgiFvUx&E}Id3D@I znbeq3uN0ujRIEvn*$cW!-GzW3a;N0yu)=wGY=;NVw4+quqm8|@#D1+QcKrBH;LN5! z^WAf|kCUkBqQ$K;Yw&tiv@RNDI5Jw*FU)Zx?4pZKL0)|m{>AgwwpB%6Mqm{yw4=C} z)eM84F&tEGc1A0ryRdJz5Ne6bt-5lbXgzyu#!ICGl=?JhADu5<1&yLcEr5V4mCani zC6N5Jb~Xr#GyZLh7MWF=C!PCjC?jXccG(x}?*<2RCl&ITU*$b4bI47>@oHU*L+D3a zRKg2Y&lAt&Qmc!T4eJ7h-;+8lXLWoUqyK6iqHn{O;4l`EDkB1(2U!7?V=eMGxMx=HNODt@*cTWQ_s})RP zhBcjGxk|D$J8|;(Vu8_)AA#8R^qpugNy|&6W+GU8>+7HRuNcu~RaHuQ)eo=YK?H1A zFnVn7Pp_7hV+PpJiiJIL0E-cW{V{nkNHIVlSIqd>*~Psg0h&c(upRIBI*I{GARn48 zRJ^lmt6Wy7_?iYlELfl3Sv$YZT&W#g;T!~8=YMYdOZ5ZHWo9UEmR5S0IbaZM{xK%IWsr!fQ@+cv>vVBl;Dw$^hCP>ACS^PvjUm2x zTLWe(8jtL+?wm! z`wQU0ioturo8|9CO`VY{kr47+2cKha#mq;h1+e`Lm`3)y(%8|r8Bjq5UasrvMu1izoIDlfid*{J{h2sARneX{f3;n3 z!Myl(*70k(wV=HmOe_&p&*|(L57at#MmoQvU|^Ew!#c%uk(&X`R-Dl3zcdxaiPJc^ zm?G4b$%?<1)#P9zXQ85@S(w7+EU*^3wdL!l!is`3#3zDUz{ci`mQOB|l7r8to5v>3~NiRY-7m_ zAP9p!>V>*uge~bboXfdvl84&jM)ft&s5;g0^1t8c$t88Dwri!opO%?zT9i8jpuKS& zx#>tOl~h*IWcdw=ja^RYR2?x}53e2#ZIr^-;-nteml%QK&Hwd9=r1Z-enMr^?yltb zDoQVPHb!PQXEEys7v=KSIEh_JTaFe<~gy~BU0Hi~nY z{jDhkcW)HgHX2>Zz$~BRg$AN<4T0B>e&}r*A|h{29?Qn=<*3E8lolc0B;OKy%xt=SaNm(SNV6>;m{?0Pko^o<% zzO`llAd$R1!Mdh1hJU`kg#eAx$Z+|K0n~xiYK8nUYA>I#_TwedSgU1P$k>6e@U6EY?w^AmW`#O)~iOxNyW~wV0)_G+3=F#$^kl) z6sc4t2RwD=HT&Py#>l7t#B^+-Aj`B>s5LWoKK?=k_qrxfEw$$>-hFuaFqnAUs*B89 zzd-6LQ|*AeW$hBUZVlPCj=5S)YLb~VBXbP&YH{qZu7kH|mG%$qN6qfPdE(5q%5>WE z+>~LRPBfp+n|rqSF7$rbW63$TCQ|O}_uNs^`+d=yOv`?O3n!M}66cnd`SztW1I#Oa z6G^zcOsa@ku7<;(`zq9S1#^|AaTJiRCF^|nOz*IkZqWcSBE|_TTqd1^SjbBPLsEJf zl{`!WdFbU@DK)6a7@U58LRwP@KDqH>{!uos!%8`qi=$!mJqO0;48X8p5=~3>ciYmT zSi2_RQM&T-Vp?z8^*OUnK)-@Y{wgcjvc@?xARHKssEoiUW}gCe0(Va&R5D&;R;fu0 zPA-shdm<|vFp9_R_+c2mo%N}fqN1V}UIFp1VJm$I_7sc7N)kNU&%H+-z3NWlm_$!X zW9k|mDe_xZ{4kfzPek;Z-u6oWtLo-&ZzNMby;Y%zZgIwU#l^;Vci4+Qe&KbY3jM_b z1rCd6e**7p=Z+{$O|-`aX&#fbc9+`hTbf{OmS-X_MB@}_|9@cRM2=Bdx1)&wgil2DNME_6dk~A(R;p| zDF2E(zSz1sTTxV!Hd}1+h^j^m?4Tfu5*8C0)9Ewg2!^$gtBKq;nLA;Kva`DFNqaJH zWCi;F0KA+jEv68IW4%5aRj|7?^9Z$ju;QtKVnV0v-m^x~YrbJ?!=A6gIUmOFS+(<7 zOc^7K#;oaSPmZ%w@o zPMwu~Wy8?FTD(D!*jb9BBhasTpDNXe$Vb);O2y!2w8tAy9yrw-WIdxtx7e<~?~5Z$ z_l5r*+8?)f*c8Iqj|k4S;+O}kmr`HsOl`BrDJPRlmlSFtY-O)l81!e2+aDX)MH1Uz zsQNoBLf`Lgn}W2iUxF)R9jYfSWe+_z@j;*0x!q-y^hm{Z3{CSEtx#}o6DAa=Zx~Bn zIUI1Z?+kvkzhEpOCxt#^s6Fd#^7SC-9A{)?50`mp@x$C6Y=)U!W(w{GtEXF4MmkK> zGU1S$1E4862UB{4*T8lEVR;!zshiYlxP2bB zt$IL5=1{~FRGEypMm`_)gV+6G?H^4fAqR5Vz+!ND!B41Kl^0r5D3Yzx_(fb^c8#45 zhh&jLgoktjWNSZ4D&@ohbR(*XY@bKrM9()p*@YU=xsF()WfihJhH5VoEa$EO7byt> zUmX12=DN5}9!wJAgLn1x>s-c&3-Lh$6jXNwfrYrzuRGvX%U-Z}fu$S?S#c`kWP!CR z-Aof+Tuy~UY)!*_ks+Wzx!v|934s_Kc;P?JeVU!kL4o<|AV-^< zk`?r+fXnXWDmsq&sof9H>WyQqwv*#1i8F=2T7#(k79&>-5F++7th#IPO%|IZ$QQ!n z`{)n7ulDVaICFvz;-Gd2^8A)0#G?-m`J*rrl4`hzM>6RLC|u+KaP%L8|HR?{IUjZf diWqZyM(cgl!N0-(_rJyvq@lS%)n&H_{{^ZWMt}eS literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeMovedAfterDeletionAndUndo.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeMovedAfterDeletionAndUndo.png new file mode 100644 index 0000000000000000000000000000000000000000..c086a1c9fbe90a32c460f16516972415a4475a9a GIT binary patch literal 12882 zcmb7rby$;c`0oS+ELw3e8bMT07{iewHAFxfX$cuJq((>#m`KBD2?+`55Xpg*bTjEz zYSchU>GSgaopYW4&UKwX*m$3~^K;+t{lr2v)DbtXvRnm$KsTN#D`I@EoPs)*>HSPUI#q(-0hGi-_DW-$`CD~-4`72uMOBo~ktLz)Apay1Fv+h~hx%a%o zH#Q7(AAW7x#Ik%XFue8-bfCKKu+yXR@#O=N_f}C)Mdm#wSM?9I4;kr`7Fkbx>`(tj z`a-HYyGqs5Mto>NAnBd*Nr%t3h`#7E3PnvOwJ{?&9SEdsUQV;PViuVeHp0}lU@r=cp?Bm0f&SXfVyPn=e0Td6w%C2YJfv;({TI=oT>n7RBW2S<`Gz2qq9&x% zZa0vLS6Fkd3N323Hs$mf^i|1`6B_+g-mG`3bz~*1XCje?3hAF|6B$bW=5~FoXjMz| z1V~oRk@JRSEo(x{-{Ps7l!&vbN$4#m#jz}S)QwE{hKL##woyB|HVcp=mfRq5$BX!) z_j$y@D@j%iz5YTi=H9)x=XjPR`i#4^Df6=2Uw!{P+{A=eIG5F(ca5L&AxdS=ZgZ5F ziDT|R<-f034Y=A7I3#Dg!v;e2U|)^MN! z?&Y~16pJ)`?FT>ITOE^$CrY1hJhFI^cY2^-x?|?PWqFX+aJp4@P&O1hw>g{seQd_d z*poHj7g{Vs^UZRbw=pT{R%j-i?%}MR3(R?@!6BSO`aI2I5w^$_)3Y>gzwBs~fKys< z79%?p1MM|oUryhC^JHQ*GO%G{b=|*VdNn;_J!i7|(?A_7Q$NRTPh@VS)KSylfBxOS zemjc_A*>|$NqOpxH7lgPB%X0lshTrToLgj5E!bi1C~h2PB2lU;<1&CPiS|=oReA>-Uekj zG`^Mg+3?s~@1A?k=_jHi$>Jn+;C@uMQ@=yNwNl5FlNrUX1%vh&_VoasqI4j=bZ z&Lq#vUaw77yEo8iGRC@K8?oF`xc;zLp$jIwWdE`no7RkdIm}M*A6c^LT|e?kj}^$k z_OHzM^>>}*rp6vx<)&}3^(D+}s1h?%!&hxQ9id|R>tbIDWIqi&CVFV-Fi}|y#*;s5 zBkFBT1w>Ucj*%<}#NE8OQC%4!%}aI^X0S_Bzq{tN%|XlW$%V%iV8&)TNv2TY5f_>U z7!#WyABw-7rTfecKX+4+Zy=U%IF1svZou^~{FPuCpmkbr)njx#mDc^N2~k#BQN10! zZCve8+7$sAA`jz?G$2aLs&%}hrvc1{QP`%`SmCMRr0BAu4j5=#wE;b!=G*$0VbE+0rj z!=0E)uu*K8eJsbbLrqbK*ddFO0+5kWdWhmEY_HLvx{H>kRnBOBl1Gx|vpDnwUE24d zsB8QtS?eSSV~+Z|1B#u%sxQ~5Y3s~9-j>V%~>*Pl{0D+2tsOW zIEk9?nqD?+q$(f4nLbWF_HpFA_07CW^M27V3^AyAOEHHx0p} zb6!nYt85!xQ%RlNGm6-mTHL73}dg;Nm#$wijCH7v>2D>8#o=6f<<%Aqv_VlDLo2$ zq{r762J^B~qQX3igqVWs{C_7rw81CO^(bnbMbE#ZMH@{V?)X8**I~d!XAphJ!s7I2&{_xoi16>TS;k!@zIwTlKiGx+T2<~ zD0+*bq?*SnKkx(RMX&E;#1kEF3QL{%(l!dOmY0ta6wn6S=IJ^b>?(@Be zp?Z2|q%w`l9eoAX0!2L)^e~*2tQq@`lbPbxox20B$NPeoC0vTbkAdNX(YMulTQz{! zVhZ>SMVKPa!g$MV|1iO$=wDJ@@{{gH&otEQJ_r&Hj1##c(0Vtw){&D@RM=lmv!Y~Q z@9jjdZ&}tbpUl?yFZ1Q*&g82!RunTYk}Byyi4iP#`#I+4A6gQB7@rR&?Kc?ThICR} ztNEA|Yt89nls;tIm`TL{b|`IAef^JLB3w8gt6Fy*Iy6gkapVkHxlvLV^jiz`8D7Wn zZ{qNgn0q)8M?n=6JR{xfBH+g4Z(!6Il0+ApiCI$(D$|D<4@5?+jS!cX-#$bQ*k~}#d~DY-N(;W%g0)hjq%4ogePq}-giGnf z_%MPvAFpXSaz?Kh3XgH$3_k;Dq1sCn%g-g}@CqiM5E3m(FfW(f8m}9@FEyAH)uiEo zP88poTQ!X#`nYc*Y@x<+3o;9cf60Z5(!Tyql2w;!fVH z=mScSnm_p!Ss&T3h())ciZWQ7xa~gG9~bVr<;2!#$mlEH4+ccM@lvIz2sr?Qw0T*w zyh#oda6vo!yCyd#?RCihPT=>z!|z33Kz6qv%A6xgp5^4p23hTHOqK-WZK=Sln$Vxg z<&;pg;AKU^*-a`2Kf9vxfq}G9AC9Ma<`a1(!j1xbp8+`7FQ*BspykyXIa&r|!au3raV+lp&VEu^ zw^0eMyH}RvH1}g)L33Bdi4&_6(RwdLL?=Rekh@KHyp(>&^T&Gd7@6PvFInz<>3QUs za8Tx$)<&|KCBf9NGK+3+(({kAmXq7;2VWL_)VD)+ETFf68Bsv(vm(Y&$lIM&%!OOrTg(D+l0lStD-kEl z!^ZN$StY%&xQzF;qjpm1^0uirsN`g-rA*BV9DJFI^Wag`KtSYW6v+$o{9tfawc@O) zaS#5AmX54F+{)6gdx|``eUGu_V>4EvDIBV4LZc{|Anw8UkZpkI#o>lTSdx?|X}1hR zkr9XjhOP6G08&_HJb-~_Tl7=gtn0e%Byo}Pk#Tb5_JXXNlpPC1nc^o${eEzx3T#L9 z%$xmVbIFMA>+#;l0w(J{x)C+|`wD~rVE&bL&Un1#iIJ`s^H5X*87qj2;TehEcgGgl zG*%=gm9zNeGb;d5UU(FxW?(@Xm6%^r*!%m~en8GlG^K6iNnShomauaHF(?Riq?I~!k(VW6s8>yhHYc^v#p@H&L^&5_G#!M zB~^?vDViffqb}J|J;>r9JG~o0uvLl#dkDmsIg<`CD1j2&*3q5R(}eO|XYtedT>*l* z&6PGRuirHTaCerFwM2Lq@nKhwO6j%2%TzHW0qu@a4LR14d>?)=o01ks9K_RW z{xs9({k*IQCo7t$+z{uxpeh#Luc@g!7myy8Y!2f21$2eQ&8s-_u@241mbtUkE@T>) zEWM-jsH!`RO zm9kgWjOFF_4=`5u@!eJxnmg;M=CB&9S!1L(f`)+SYAazuna2gKN(m&Lls%w8NPvxL ztx!({&dV}PAon&K9vL9x6uRt~U)z){bn(H06ww{g0$W*4hM^RB!E3#qhxC%b0%moE zYGB=3t-x~%jrn5*_Ge!62eMpYq`gfBKxWt*e_0rW=J~Vz7n$k zwl;8P#2p`NOD6(SvG{V5IM&_Sf$hznye{lwZR0+-XxRgs_lEbLNG{dPRa-lE z)N;r^io7%SS2Rd<-5qiMSL71B#;|G~Q&aKCjqOpa1*+`QFInr)q^y&=dtRDRo5-E84mx&GP=_ zt8vL$=e)LeIdtX49(}N253}r8nD#VHHk{Z88;7!s=CJD@KcJ@JWmM}0y&Zwd5wtM; z!OL>;uDux)N}6f~oBs^T5%oSMbukLcR6qT-B@P|<2V5Gt4p~p#WgIzEzrEufg$!$q zMn@sZDp!iD5=iR3Tg%keit`r>(^3p2P2>KB z3W+=QfjlF@m6G{*4gdzL-PnH`@@JjkY3jJl*1wbYT3>%Hbv8;XKHXpCtqM*X!@36pKsFB=uWu z92)+cw(+%mTG*{2)z1q&D4TY~)m(1X>2Q4vyDn7A@^|{K5JZ_$k3Iy(414gn zumvkC5SQ^B(j!da29Cc{e4WWMGMcvywb#T@PITf-%9ANCD0b`1jDbp|5(-)VO|R5e zCSjBiuc<#)FHSQCCR7VZSji>Ze?!)wbJE-8;|nyL z(G2;$Y)m)8+sCEemiS`ALniz-l~z>jT$f*?)7;}YsbAEC)bMI?TIM(Zmaz1PPuxF z(%QcI9qnluS-rT7fW~Wp8K*msm0fx3QhjbJDmeUgG~wQv(V0uW5;}Uu{%R~HXWR2v>+T@Vsb4M?2uTSsc(@QXy;(I?5Rq5 zVyt_^?u84+7S04XC3F{&ZbW>>iOi4^-;cG%K?%`53Ll=D177cxn9ofse5^b0!6>o0 z=$Q#hTmHvc(z@!#!P6Y6T0fwK;9;$L=j#H??r=o+OLL(2QW z!f+>OTl*w}(U&>mr0Z($fq4xf*7N7zim$1IBll>sK@;3%a+>o0R1?1ke7h?zzmIt4 z$fB&6w(^1$R=tP?Q+H7PXDE>6hmx@2@eR33S0W4-v{MF$pvH?X<7qV zwh<1iM9Yt#+EXwLS5aIV)DY!M?+dm^HBz4@JFCy8banUYAr05pi&C>!Q<9A>J&#Ya z7OwPtMi7+SuuK+zm5W{uN4YOQA4SXbo-)+=E-OqV>83e7paaol(^VoVo1tdX@^{v9 z+0<6eVzkPi7nXLH8+RCF@&FPj>|98&jAzKK?zY%U$4^OCNah@}Y#2QPG~hPUoi&8c z#i9S&ex7S%I7T2F)=$zBiFo(TlBAp3O9p}!zDs*J*740eeJ9QU8!?9al(1}Zh7@J5 z#UiF6fhAQ{XRNs z*X{!>>z~v4Nyayvt!OzK6T7VpQ)IYN?Rs=*%$ve zIB969DI@4zQn~gsY0GegZdp}s)WUEacb6vnvQ^gBbZ0`L6R%}!3OZNrk8_^6Y4!}H z0(u35aSltSqWZ@9^S%P4i!hZ%ER?Fg5ABLWZ+hmiFxFSLld3-?CvD9`Q1IBnh0Fj_ z0PY@*6_?x!~aBEQ+X z7^(62N| z!&qCT^Ta7G-g}6H=s_sq`2MWAVBxHpHPaYBaVN7-wS#NSayyj8l~2S`xn3${GENIo zIrk!bUiKEJm%H59#`jnQJxw|5FLt>;U8ygIf0Q{Aw<|^ng;~t*W;2*sBYtg2r(7@l z-^hWPL#}0(u3LrElT&wieXYW#@{tjw&$kO~^N6a~`S0Akr+((Rty-YBTh$&yZs2EE z9-0UqAFmBz!I$zo_bO{6Gqxw|^{H)ACr?#XFbKWja|)_&APdd%@PESK=4HGu-hM33 z)`}A9<+V~7Ee-^#)|+&a=8ZaP*mMzD1EnL2U03g8_q zZ=?Cj=DPYvpHM(L$u+qN_PkOfk!5fqT*b5Ehm2vr!%WIRJAhO?O%A$D&3^| zMiTkg$x7u{%_w&=7 zM$e2_`W70#^oMHO>JjbDC6(?1aTTgy5s(?~`=ZZz*HSX&#+{5n6m)E?)a(h^e#Uz| zh?c|c{PFcC=Z6F7W;5ffMNa6K$2DNRuedB!&>DWQ!EN!)q8|wVu&Ut0sV09+P+cILE%%PZN)F#nJddV(@h)CTWC3i_8xNV+b^CTAGFM5)eRuhb*dalhR=T~ zfhAl|h}Md8{E*Hc!)e0PgHiu>CJUsk_ zdAD@7KeOk4{>T2j<~MW8pS6<_M&izzZIkV|QiV}u0UI8#_i{3+=<5PEjn&iSqKf>$ z8iR42)Xy|1g|6D5nn?5W#&ro}mdI4&5^QiN`Gnc5?`<4Th!sS-;^ZQz<9GQ<*<6qY zgGo@F=h&Uu!r}}Zux}S-2P<>7RaL6XD@`s=buv0eE3QrXzPm5-{qXqlaq~iMEviK#Np1hBNnpm+CI z+Tvs1uGBPRbw1lFj565?IZOT*93G4{nnJb-n!w$U}$p%rrY^B6Sh1kJa zQ74+an)0lj`X6v}uyr=SIXOP{oug$s-%me(>x@RF)Xv1_7#M_z34imD@L{F})LY5{4mgbQcRaZME^w|5PmlKb4}{P*df zvzg=Hvw!=sonoOg>M|B4A^81o@2B6}KRUpV8q@3L*K6Ez;0R1noA2e*1iGXF>L8*(ZZ2xQl7Uini*uDu$tW_QQJ~ z4^?H=8orz3Ir`0hl9GfJr0(OqDsHMjX9sR58FeB{-7?;Xeim*NF#G94#ds!0X+_z>i zXTO^>6uSO?DIZi{P@$!Hy(Q)5-|;81#pe&TPwKe$C!M!8b(xH{tfD2`Slf}s5+DD&di-v$hTyNaJc3SR*?QaQU>4q~vl+EHKXqFlN&9q`&R2ZRoz-oU8 z1AQP(j%tnGU?lT(0;kov^Tx>L?J*~Ku5&n$s3Ipy^}C}*zp@cNEPQrDwkaUrldncC zqY)Ia)`21xXI|fdU3>*&;;af1|a}OH$*u1{hw>0UK*UqN!an$iVvJZwe#9 zS24!#Rb1k)-_E%;XP*z%oFSC>=RFqvdpwi}ZN|FbAdYH8oXs%ZldSF3fidV_B~eQC zEp$|}O0nBL?-4wzzbvb=U?3&OT1U9PlfPlWlGQhTonxtff$qoSz zB`z1R=^mp2V?>-vniW4dP7%Xpn^mHm1up{QeomN;;L{tc&^$r$sN2CSlVZyU0()?3 z5Y;z}+ThV|NR2Ywhol|XN=f;g-UrM@%eDIZWr9GPJ#UV()@xy&DVzejQ?tr`|&f3eKqqvO1(AYHD*8rG@2 zOx$0Oe!xwE={JTVsMUHuv=5uI=HJvEF8}DrtHboNl%?Yu4aIjCqSA1bSgg7e@4fB6 z($TeS+JNu>>xk`=nN9&K_GP<2iEG9wqz{>uqszQz5(9?j?+ zjpT8?K%jIF{Ov=fs8Gf{tB2&KlsRlBeLds5${GMtJ*xRmxH9G7%#F=m~lE(Eq>1kcX z{;`8*-@Q`wn1lkXAfR-hx|{qbGIip0(1&sTtjRNN#q+Xf->kz%qHNjzP9q#5`_f=7e5qNy8{vvq2|_gucJxc$Ypdzje2He#!ZeV zaJsvp{cQQt+M#->PZ-wWEAkAv9aKcktS8S_2fhT3=(DzCo47kKjlJGfzT1F4xSxix z=0e)z)CBdN=V}|~4b9+mFDanz+0PLoOK@-EW(YgWScnf}xsKrX)1^HhWPRk+weR+d zZCKfxwI-~DJT95Y?6#fV;^=QC@&fqNj#~4QnkjA?ih@CtIeH_@qevSQPo?Rsn{(ul04a<1Fv3|kvqNVV&9yM{YiJz@2hzp{EdP4a5_1Rh3-v~+Q%|+Z z9!JivwzZ|ZpF;6zawBu16+B&Kq;?5C8gN=813(`(bHEm&s~hj3)S)rQrzj&5eAPmNN0gl41_DXfDuT&c6Dka(L zDRnbh9Q|?hRAi4@6;WN8-a{w>G~2U$<5)@ga_FjE@60}z=%TVCCrJKXg&rVg?@C4- zc^aV6fFF(qYa$E^VYg@ ziixvz4fiJZhuAM~`fE7X#`yQNVnMuvt*T-{wuqQX$?3FJb?JtE;#*C$OOCNfy26tI zKk`e;*75N^&r{QLBcH*GB^h{n*r~{(I`19f#tkj_RJZ=PAY#_8TSB@{xuiVT3427q z)>g0L?X5Od6Je3w6~9yEp_%q=PxRdmQuRYKW-2m` zsF7Ow=J4?!uEVbd5Zt#>z@0h2_eBL;!Ns1GQvBU2$P$NQB|QNkLD8WVN7zh(IbS&o z!$(z~T6gbzUKQedc*i(Ix99r*ZVuIDTY|oXPBCDVtBUZ@X*Nw)fS^jRE042bk>q2> z*i{@)R}Ta+Kf(N8dzV#0V|1f~MGk90$r-1mVMe3kE^+k@iEku2yvPX%F&5c|s0@Ok zwR@|50og!t*@y5(LHeo@cvdR@7uS2Ze74XF&NpFMurJ2q07iRNf z*QDnZC1Vn(g3N)vtYbtP6&2awZK1~&2UcT|{~dDu+;R}(g3bxpipNwE8xoKCnoKTO z%Gc4e6^~$ExEY}ps74%l_gU1pXj;Em%RFmZ0HP=LXEAwM7r{TKm zoF}yI%&Xn+uO6n}2DC@%D%niralL3Ajbt2n+7QGHp>Dc5p)eP1d@3~jVJGiYoK@Gr zs|6UDazQeIhww{EDf$ux;A!X92xaMFT?8r#$BTI3n9`Bm?G!+MX`F7mZBC)$V-!P~ zVxR|IZ3Y+!E%^_-Yz7hS))iE{jj|7QU&sPTW__(~uW(~wod+IycQG#SE-)-B5zm81 z=FK~*NI87Njg0^BP{;=k5h2u|jjo%;U+Vb9*iP@LD~u{HOVE;R#9}h+-P?iKYhouU zEi0h9=GHiFgQW};g(Y<%T|G(tmXF^VzphAFk^h;Pe5~hBe#wtDyM}6KIM8vsFcxVr zr}Nx~%Ch{Yr-8&;cLN)6yYulNp6BRjxObRcO75)0Xry1hqz>3%SPOrt>3;mL?G@Jn zfQ(m6hZ%nqQ4_>gHN90?@BS7=JB}D7@F&Qxi$1ft=(1*cZLj6;i93l?LD{-9;sU3$ zZ~JAQc!dH2yaEapZ5B)aASrd9SGT-TuS1;JWjxWm?MN9<*IjRS52u5X4vY{DBOpz) z&0~=zDbW$+AYR9vJ4M^FkRM^OhY-KG0J;1tgChu_w;`HJE6y4c-?oNf&TG;7am-#w7#-CujjP3k|y_nod+iQLAG&y^h z)2VFtDEK>4vU_7Nn$R!zc;*fb1?XFOK2QpTGI|_3EvIcLvqK%*!~<~ z!_Rj=x$=mQA%H?;pf;$zSD|{W3V?(LQ~6eKLvgM4OF7qcdIKK(OdgN8j8wW$>QvAH zN?QHGg#|@o2&>RhNLME?ks0#^KzCE-;)>+YFUi2JU&UxtS2TQEdZXhov-E-@+gOns zbTM?C!2+!eNm?#x$owrDG%fwS;l`yxCh0azsFts8xxm% zZ8#|1BLrd7y$6XaR~#+`Ud~@q>dnh4*!%7*!N6Ah8etq!^hs6G>JrlJTP@s5-4Dh;1@Jvqd5z|@->CtNG}H;7PUuv2>?i+ zwn*&El9bB#{s(>Wgw3vl;E0RpmrYo7p=xN(TJ_;Wk0Qvn73Ic&jWcSHOF%zcSDBba z`g(Q#rj&8Ap=FP;HrT@lEj!{*1$a%bBgIB=N2*b6h^CdVHJYNxi`ugO32>hh^w~)3 z7?-#%Ahw;zm!gzs3zW-GM54DcMj*lRRpb{_<&`T&6L+XFxSTScDygJpp^dlDr9Xg% zUs-Z8E8A0olzbk1a=evq3#5P|k3`!nwlwy*i|#9S{K5dpO^SW3T3Ms_y)3<6VlE1T zuPVK&`*cJu(chf+@=<@_z+DcJt35+OK;{8C%K9jO>XtQ&VqNDraGRPB`}-C^B?!)W ztvyo^{{GHLz;$-*!#e<${m6|>&cDtCrPS8eJZ1|4_838Mjj1ccqo<8qsZaikq+?&8 zxB%r$tQ~b!PU>G;XXpEGUL~6>X{E5dapnd)J?sLTAgbB{xk#&ueO*~v2Edw4*cc4L zP^X-wY}$?Fv38I3T7N8X5?d&v625hcGw7tyDqUvElXW6ctu%XLvQvZZe@>_{6fU*y$w8q@EZxZ zJ$dkd&;UJ<4`9)kb z`rTD2@}<6bjE|K(x_TiI{r_qCp8+<`du+lNR7Gw1p5$Bl<-sqR-mpuTUcz!@!NH1qbB zQQi56#Qa43MM6GOpA0f`9^-{o+u%%xUAi)Y-Paug7OLYVr%Z-2Q>=$}XJ` zFCHj($h-d1cAU$nPy<;5L>EyM@yHKQlHoe=6h(0{gtr+)2K9 U3i^Nj6a@54QC*=#?$!JM1sidgQ~&?~ literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeUnhidden.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_NodeUnhidden.png new file mode 100644 index 0000000000000000000000000000000000000000..a7cb0b382735a5517509314339aedce1ca1fd28e GIT binary patch literal 5757 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 literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_VP2AndThenBackToStorm.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/add_VP2AndThenBackToStorm.png new file mode 100644 index 0000000000000000000000000000000000000000..c086a1c9fbe90a32c460f16516972415a4475a9a GIT binary patch literal 12882 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 U3i^Nj6a@54QC*=#?$!JM1sidgQ~&?~ literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/cubeGrid_AfterModifs.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/cubeGrid_AfterModifs.png new file mode 100644 index 0000000000000000000000000000000000000000..b81ba41685ef535720b332021d9375c58aa1de26 GIT binary patch literal 2735 zcmdT`ZCDdm7M_H}4h)7MHB|{=V0BeC!AdbujAX=AL1?if9R;sId)@f=ViW00LzNffx*6f}s1XLR}y{iEw(;5WqaX8o&1~>$QNFoBr_GG{nk4HEN zAOYX9&AI57dEvQZ;?Wz5Nv-cY+y_@TdsEGClC;eVO$Jl$L;>dntP{8U8wbgYUjh)} zVi-JV=#qdBc(DAOf_2M^PL?BZ465wJqhg|83I&XzlYimCQaQ3lgGcj-_>B-@2?ZPu zjDVlvq9S{Iaok68j}zTVP$?Lfw}bl!N&K?LR?^2?Twm2aBkP!l2dW&$dS&j@du}VK zK`kRY^Y3n8w;LtQCM4Uj#_7_Vh@SCZG6kvOv)`esy@pL)&joc%1CP6>?!<&csESVR z-`f~|tyh0m8px3D9Xo%Z<<;8Sea)>R)mQFzP|SX9k@;&w7ib+{mx#utq||k`-?J9UFfGgSfgt`bOjnJeEC7dqano!SeDu1Jbz$*CR8HqmkQ+Osqzx|6D=g&sW^Dk$OiWeoy z&$;j!?X&gbPtvGn^JDwkF{2{NMCC7?u|XgBEyXUpG<&dHf878s#S8_S9lLVcW-^k? zB*9rkzqK=5DucGg^H$b&I(hqbmC-xGsfHtVvn;f+PJ_cPw9rq`;2%n%p>F*$3HVPe zg#5mqZ0B|vS<{L~U-RHId{jAy*~=0B2@1H13%}2X#7pSpf6It|))cT^gD)pQgkKM# z!I?z-)RjMXD++`0eygxB^vlSttfQ9Sn8DlIxiuhb${9HW8l8dRuf|^bY)kCNKgkhs z)~Mwj3*T+Ku`G7~kqj?t^34J-3pzwn?c2fu%N|a$p9U2uQ`W`G09K)Z)%nqoFfBbw zU)mqSgLMUzFI-4$CpMemaQJs>cyhJ4j+ApLTFZ06l0PwWdp&g@OKhdUn81{d+Nz`B zm6(2h?i!NK2j5A%g76Qa$Pak5jEJ{}2%q1hNF>VlFo6QDq?5e`M{UJKJ@`0; z#dq36t5M6IO!Huem34z>BubA$^=?;>GNMP<{X?D@KCFs9FbLde%b}ReSlNTTVim&S zFORGU&n9|=++g*-oVs^&6W{vzS$y?n-Yl2VMH}F3qu^ zk0nB-&&70YrRs2}swwA=Z1kbRY|g7M@Jim!)e0l>oNLDaD@b)LmLH7w$$7GNV0}~E z1~#swWaH|%EY;}v+g~LA=X(EsLtn=C7t%>`;sV-E-HXrr+cB0xu`bO=r^CQtq0u&5Ze0kFTB3lLCgfPWtAw@ct6b)lkb%?vFG%*M=ahfBfS< O6A1Vyh;z}8pZOae%bmXf literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/cubeGrid_BeforeModifs.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/cubeGrid_BeforeModifs.png new file mode 100644 index 0000000000000000000000000000000000000000..00d4f776180e9939adfe9c8e74929ad849591dc1 GIT binary patch literal 8616 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 NX!TpjVpXe0{|5x_a>xJx literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/cubeGrid_VP2AndThenBackToStorm.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/cubeGrid_VP2AndThenBackToStorm.png new file mode 100644 index 0000000000000000000000000000000000000000..5287e1aeeec591f1b1dcec63d4ad59ac6efbdae3 GIT binary patch literal 15761 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 literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/cubeGrid_WithInstancing.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/cubeGrid_WithInstancing.png new file mode 100644 index 0000000000000000000000000000000000000000..b81ba41685ef535720b332021d9375c58aa1de26 GIT binary patch literal 2735 zcmdT`ZCDdm7M_H}4h)7MHB|{=V0BeC!AdbujAX=AL1?if9R;sId)@f=ViW00LzNffx*6f}s1XLR}y{iEw(;5WqaX8o&1~>$QNFoBr_GG{nk4HEN zAOYX9&AI57dEvQZ;?Wz5Nv-cY+y_@TdsEGClC;eVO$Jl$L;>dntP{8U8wbgYUjh)} zVi-JV=#qdBc(DAOf_2M^PL?BZ465wJqhg|83I&XzlYimCQaQ3lgGcj-_>B-@2?ZPu zjDVlvq9S{Iaok68j}zTVP$?Lfw}bl!N&K?LR?^2?Twm2aBkP!l2dW&$dS&j@du}VK zK`kRY^Y3n8w;LtQCM4Uj#_7_Vh@SCZG6kvOv)`esy@pL)&joc%1CP6>?!<&csESVR z-`f~|tyh0m8px3D9Xo%Z<<;8Sea)>R)mQFzP|SX9k@;&w7ib+{mx#utq||k`-?J9UFfGgSfgt`bOjnJeEC7dqano!SeDu1Jbz$*CR8HqmkQ+Osqzx|6D=g&sW^Dk$OiWeoy z&$;j!?X&gbPtvGn^JDwkF{2{NMCC7?u|XgBEyXUpG<&dHf878s#S8_S9lLVcW-^k? zB*9rkzqK=5DucGg^H$b&I(hqbmC-xGsfHtVvn;f+PJ_cPw9rq`;2%n%p>F*$3HVPe zg#5mqZ0B|vS<{L~U-RHId{jAy*~=0B2@1H13%}2X#7pSpf6It|))cT^gD)pQgkKM# z!I?z-)RjMXD++`0eygxB^vlSttfQ9Sn8DlIxiuhb${9HW8l8dRuf|^bY)kCNKgkhs z)~Mwj3*T+Ku`G7~kqj?t^34J-3pzwn?c2fu%N|a$p9U2uQ`W`G09K)Z)%nqoFfBbw zU)mqSgLMUzFI-4$CpMemaQJs>cyhJ4j+ApLTFZ06l0PwWdp&g@OKhdUn81{d+Nz`B zm6(2h?i!NK2j5A%g76Qa$Pak5jEJ{}2%q1hNF>VlFo6QDq?5e`M{UJKJ@`0; z#dq36t5M6IO!Huem34z>BubA$^=?;>GNMP<{X?D@KCFs9FbLde%b}ReSlNTTVim&S zFORGU&n9|=++g*-oVs^&6W{vzS$y?n-Yl2VMH}F3qu^ zk0nB-&&70YrRs2}swwA=Z1kbRY|g7M@Jim!)e0l>oNLDaD@b)LmLH7w$$7GNV0}~E z1~#swWaH|%EY;}v+g~LA=X(EsLtn=C7t%>`;sV-E-HXrr+cB0xu`bO=r^CQtq0u&5Ze0kFTB3lLCgfPWtAw@ct6b)lkb%?vFG%*M=ahfBfS< O6A1Vyh;z}8pZOae%bmXf literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/cubeGrid_WithInstancingModifs.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/cubeGrid_WithInstancingModifs.png new file mode 100644 index 0000000000000000000000000000000000000000..5287e1aeeec591f1b1dcec63d4ad59ac6efbdae3 GIT binary patch literal 15761 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 literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleNodes_AfterModifs.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleNodes_AfterModifs.png new file mode 100644 index 0000000000000000000000000000000000000000..c0372a468d95c16770d69e0bfa6bff388ce4cfc7 GIT binary patch literal 6198 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 GYrg@YQ~JdK literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_VP2AndThenBackToStorm_modPan4.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_VP2AndThenBackToStorm_modPan4.png new file mode 100644 index 0000000000000000000000000000000000000000..485e6e19c03593bd4e33aa803599b5341177ea98 GIT binary patch literal 5821 zcmeG=>0gp**OoJ6rfD^n;Xd`$l$u(G8z3!isnxirB2$)`xMXC2qNZt03tE}Em5z;R zYC>jefZ|vqJ3UDUzrlg7V_b=l3soo)7nR-@kL6bDeXY?LOz+K7G5dJ^_L9JD58BU`9q-j?#&O6X`M7*=iPAd!>7#rZE{j{i{RDwD|Yeu@=5MQ_*WXKwQ^)6h_OUDwe68K$AN#atsxOJC#Y4_X=)>wmbe zzivJ3ca3%C$N#teU!|7Gnc=+}sQGVoVZlRlQ%g%HoX4@Vr0H%t-i0ggTinOk#CN~` z*Vo#9jOb$>vdrqnS2+buoQ#w&E(r8&B5)2SKWbNv6`{wm7r&vdd)b$`2a}4PD9;pL z)`rNJr-`RhH`^Bf1l(skPQ}hIf9)yiXj>7Ua#bjcE<#WG{yA;8qN*+e℘u-l0+o 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 literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_VP2_modPan2.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_VP2_modPan2.png new file mode 100644 index 0000000000000000000000000000000000000000..4856fd5babbe55952957f6644790efd53661cfb8 GIT binary patch literal 2225 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~V9glC$suNIKWzyTr{7^Xen z9tos4^WEHv!2%%Sme#g&Ko$C)E{-7;bKc&4-6edQso|pJ`yW-moDKWcI~F{6`f|^+ z0Lv*~@*bVJC*R)r`$|+miL~`p78UuaezRX6eptZD@c+tQZ+k|De{)$_8eAM0JVvR} zAQ;G0@Z{jq`3!$F@9nKFule%g;?=#?-~EzbKR-Wz`NyxXudfC&;^SXv9+G!;h`9Ir z`+NU~6~Df`)Reh?{rd8ar8aW@j}JDphtIdKx9gi=Q~k}yBVtznhKO6o`{n!n=h^J6 z`tjkRCg1n>_xBg}Bp=)O*zWPZ>2?1KJedw{va9`7a;Gi1uB7z#jkc6Cg)gr&PFGUU z$y_6s$94P%$Ll-)c20=s+@QsnXen=3WAW~=;rZ&E=EB>##XTK8mim8pJy^%d;a&Or z+uOo<=Wnn*jM4a}J)zcR+wq>C$L>t8t1Xyb!`;2qrtZ%Vea0P`KFRep%-O%6@4VY* zXujqMS4--i%FoYs#Jci4-&TBP>M1)dAbswmL^;shpfwH=$1K7N^QLaRpLah0%>w&$^Eyr5B|?>*wAzP(MJ2S{YF3I z?b-g^=HxhiW1ZZ$-^Hbd$A6s2I+pMz|NpyfI}=2i4h4M^yfOd$jE@q}%I*PH&-aGi)aPf z1q{{s`&75u9yk2{egFTB@r`HRnu-1XyHU2EakYTJG{bd|XRL0i%sp=KXk&aWk2!0~ z?ngJ?^&f8t-61qLRfezq^-s0^^`Gsx@A{#B{EX%CGHR( z&-Y`0Z;Wp}Q;@@K^sH@aMB|1j8=WMcYaYA7o*>_PzxVIPV!PG+Vaf_R&!3*2zF)~G z$)I_|{rl{X?`$ldXZU<+y!ahc#zas3xIH`meS3R*y}+FAa>JExhn}p<>6c8tbNy8FRAAIre|>fJtHn>-V-db*EdJ#6BtQEIO+DA! z4?nzgp79^Y`uP2JwO6x#Z{B+N+wAb>$xlDTGch?lQr2M*Vq{bh6d0vOgJ57&!B76G aEz%Zzv**bHTM)Qg8VsJUelF{r5}E*9r)`V? literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_VP2_modPan4.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_VP2_modPan4.png new file mode 100644 index 0000000000000000000000000000000000000000..ac1d96cef3d31ea6ac60063b070e0f5f9846c70a GIT binary patch literal 2155 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~V9glC$suNIKWzyTr{7^Xen z9tos4^WEHv!2%%Sme#g&3=HfqJzX3_D(1Ysdpb|pQ=%czy5;q5MF*#x6OS3S#X3&U z-q^@6Z)HzaNV28MPnDFd&t+Do9G`w7ZK8jDcyhkJo}L*)L3sM{I!1;ylN%T&aBwhm zj#8sRFq#S|&Il)eZj5I*F#p55ySwimZs#}u{_gJWIdkXcZoB>Vs#MwTyH}s~%iEjj z$L+Zx_qlZ4%mxPa=YM~H->vRH?~io<-v*s?=g*tR@2j~P8yA;1&%RzyrCdefz{!fw z&(6O4`1ttj-{0T=SC=bxc-GP5`Q-Zg`0)O}UtV5*_ww>`r6Trk4h@qH9({aqaq;Sv zNi2zR#U9_T@2IzVez2K+_WAkt@B9ARU2_4d*Nc-dVU{2udHl)QXu)5U%D z9@%wo+WF;u6wM798CGxq|KECD^%do=wH6IP%-#MeGlkoLe;+l`{^)p@g zWEbr^D|KFa|MQ!h)0g!}3kW<=>FMZs|24vCll;V=+bZ=_;vVm`yY%-P3yX#1`uP3# z>i+-xtHpBq)_sflhmG!=Kl=Rk_V(oct)YSf50> u7Dh&ejqFSgN(u}rOrz8=34%}JP3ATqt^MzU8V|VJ2n?RCelF{r5}E+gI*&sD literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_sphereFiltered_viewPanel2.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_sphereFiltered_viewPanel2.png new file mode 100644 index 0000000000000000000000000000000000000000..74c9756e90218a3f0ad8b314b3b4dc04c10739de GIT binary patch literal 2404 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~V9glC$suNIKWzyTr{7^Xen z9tos4^WEHv!2%%Sme#g&KoyTXT^vIy=DfXY=r=c9#4WI6h0UrXj}=_qCe2jr?>NLN z>e{mQN7%Fv|5ESS?#ea|3ETJTZFSR^9rZOedzc&IzaC%9&R{UPfnfp%2Sev5Mf)H) zd3Y`Rhr0Rla{vBx{6Ba3_3P=M-oN?1?p<>xOG?Lf{{8iKwZFcGKg z`?kMN{(jKqdw+lb_In4c|NLP1!%$!U_t*9H|D=CU+r+Q-$$CQOzyJ3Q_Wxd9yZt76 z^A3l$8-H1I?>}OCD55tZv#j3nc6qKm&m$(ur0?QeYP-2xf_FG9y9rXVwbr16JEh}p z{Dp72dIEkAnP#SelwALIP(g2kB1q@<9S#<)+$pm_N@8yhHN%gB0^81i{rdQKadCOJ zkC(q`{=CD1!Q&4fOM?rLX2DM#nXbwA>1Xpktv|QL`PshJhJQb3!en%$f4y@3Z-JWM z4ZnKr=g*t}{_XD@wFVROAIm0v-=Cg%|Iz!#0C_g1vYwcH;x!QUHx3vIVCg!0Jj%hQUCw| literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_sphereFiltered_viewPanel4.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_sphereFiltered_viewPanel4.png new file mode 100644 index 0000000000000000000000000000000000000000..3db715e4708f0065145ad2c424c6e821916423c3 GIT binary patch literal 5084 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 a;~q;hQ{Ay~;qS)^AUbkyL~HobqCWw9Tk6;V literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_sphereUnfiltered_viewPanel4.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_sphereUnfiltered_viewPanel4.png new file mode 100644 index 0000000000000000000000000000000000000000..c7c5a9c29ef6565109e2789a8eb7c05ce1b9af83 GIT binary patch literal 5876 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 GYrg@YQ~JdK literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_viewPanel4.png b/test/lib/mayaUsd/render/mayaToHydra/FlowViewportAPITest/multipleViewports_viewPanel4.png new file mode 100644 index 0000000000000000000000000000000000000000..485e6e19c03593bd4e33aa803599b5341177ea98 GIT binary patch literal 5821 zcmeG=>0gp**OoJ6rfD^n;Xd`$l$u(G8z3!isnxirB2$)`xMXC2qNZt03tE}Em5z;R zYC>jefZ|vqJ3UDUzrlg7V_b=l3soo)7nR-@kL6bDeXY?LOz+K7G5dJ^_L9JD58BU`9q-j?#&O6X`M7*=iPAd!>7#rZE{j{i{RDwD|Yeu@=5MQ_*WXKwQ^)6h_OUDwe68K$AN#atsxOJC#Y4_X=)>wmbe zzivJ3ca3%C$N#teU!|7Gnc=+}sQGVoVZlRlQ%g%HoX4@Vr0H%t-i0ggTinOk#CN~` z*Vo#9jOb$>vdrqnS2+buoQ#w&E(r8&B5)2SKWbNv6`{wm7r&vdd)b$`2a}4PD9;pL z)`rNJr-`RhH`^Bf1l(skPQ}hIf9)yiXj>7Ua#bjcE<#WG{yA;8qN*+e℘u-l0+o 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 literal 0 HcmV?d00001 diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt b/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt index f59cd60821..e451e0e480 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt @@ -22,6 +22,7 @@ target_sources(${TARGET_NAME} testWireframeSelectionHighlightSceneIndex.cpp testFvpViewportInformationMultipleViewports.cpp testFvpViewportInformationRendererSwitching.cpp + testFlowViewportAPIAddPrims.cpp ) # ----------------------------------------------------------------------------- @@ -52,6 +53,7 @@ target_link_libraries(${TARGET_NAME} PUBLIC mayaHydraLib ${GTEST_LIBRARIES} + flowViewport ) # ----------------------------------------------------------------------------- diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testFlowViewportAPIAddPrims.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testFlowViewportAPIAddPrims.cpp new file mode 100644 index 0000000000..da774b4617 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testFlowViewportAPIAddPrims.cpp @@ -0,0 +1,132 @@ +// +// Copyright 2023 Autodesk, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. +// + +//Local headers +#include "testUtils.h" + +//maya hydra +#include +#include + +//Flow viewport headers +#include +#include + +//maya headers +#include +#include +#include +#include + +//Hydra headers +#include +#include + +//Google tests +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +TEST(FlowViewportAPI, addPrimitives) +{ + static const MString sphereName("parentSphere"); + static const MString sphereShapeName("parentSphereShape"); + static const std::string firstCubePath ("/DataProducerSceneIndexExample/cube0_0_0"); + + ///3D Grid of cube mesh primitives creation parameters for the dataProducer scene index + Fvp::DataProducerSceneIndexExample::CubeGridCreationParams cubeGridParams; + cubeGridParams._numLevelsX = 5; + cubeGridParams._numLevelsY = 5; + cubeGridParams._numLevelsZ = 1; + cubeGridParams._color = GfVec3f(0.0f, 0.0f, 1.0f); + cubeGridParams._deltaTrans = GfVec3f(6.0f, 8.0f, 10.0f); + cubeGridParams._opacity = 0.8f; + cubeGridParams._useInstancing = false; + cubeGridParams._halfSize = 3.0f; + + //hydraViewportDataProducerSceneIndexExample is what will inject the 3D grid of Hydra cube mesh primitives into the viewport + Fvp::DataProducerSceneIndexExample hydraViewportDataProducerSceneIndexExample; + + //Setup cube grid parameters + hydraViewportDataProducerSceneIndexExample.setCubeGridParams(cubeGridParams); + + //DataProducer scene index interface + Fvp::DataProducerSceneIndexInterface& dataProducerSceneIndexInterface = Fvp::DataProducerSceneIndexInterface::get(); + + //Store the interface pointer into our client for later + hydraViewportDataProducerSceneIndexExample.setHydraInterface(&dataProducerSceneIndexInterface); + + //Set the maya node as a parent, the "parentSphere" maya sphere has been created by the python script matching this cpp file. + const MString names[] = {sphereName, sphereShapeName}; + const MStringArray nameArs (names, 2); + MObjectArray objArray; + objArray.setLength(2); + MStatus stat = MayaHydra::GetObjectsFromNodeNames(nameArs, objArray); + ASSERT_EQ(stat, MS::kSuccess); + + MObject parentSphereMOject = objArray[0]; + ASSERT_FALSE(parentSphereMOject.isNull()); + MObject parentSphereShapeMOject = objArray[1]; + ASSERT_FALSE(parentSphereShapeMOject.isNull()); + ASSERT_TRUE(parentSphereMOject.hasFn(MFn::kTransform)); + + 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(); + + //Check that the parentSphere primitive exists in the scene index + //Setup inspector for the first viewport scene index + const SceneIndicesVector& sceneIndices = GetTerminalSceneIndices(); + ASSERT_GT(sceneIndices.size(), static_cast(0)); + SceneIndexInspector inspector(sceneIndices.front()); + + // Retrieve the sphere prim in the list of primitives, it is added as a HdTokens->transform primitive type from DataProducerSceneIndexDataBase::AddParentPrimToSceneIndex() + FindPrimPredicate findFirstCubePrimPredicate + = [](const HdSceneIndexBasePtr& sceneIndex, const SdfPath& primPath) -> bool { + const std::string primPathString = primPath.GetAsString(); + HdSceneIndexPrim prim = sceneIndex->GetPrim(primPath); + if (primPathString.find(firstCubePath) != std::string::npos) { + //Check if it is visible or not + auto visibilityHandle = HdVisibilitySchema::GetFromParent(prim.dataSource).GetVisibility(); + if (visibilityHandle){ + return visibilityHandle->GetTypedValue(0.0f); //return true if it is visible, false otherwise + } + } + return false; + }; + + PrimEntriesVector foundPrims = inspector.FindPrims(findFirstCubePrimPredicate, 1); + ASSERT_EQ(foundPrims.size(), static_cast(1)); //The cube should be found + + //Hide the shape node + MFnDependencyNode depNode(parentSphereShapeMOject, &stat); + ASSERT_EQ(stat, MS::kSuccess); + MPlug visibilityPlug = depNode.findPlug("visibility"); + ASSERT_FALSE(visibilityPlug.isNull()); + visibilityPlug.setBool(false); + + foundPrims = inspector.FindPrims(findFirstCubePrimPredicate, 1); + ASSERT_EQ(foundPrims.size(), static_cast(0));//The cube should not be found + + //Unhide the shape node + visibilityPlug.setBool(true); + + foundPrims = inspector.FindPrims(findFirstCubePrimPredicate, 1); + ASSERT_EQ(foundPrims.size(), static_cast(1));//The cube should be found + + hydraViewportDataProducerSceneIndexExample.removeDataProducerSceneIndex(); +} diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testFlowViewportAPIAddPrims.py b/test/lib/mayaUsd/render/mayaToHydra/cpp/testFlowViewportAPIAddPrims.py new file mode 100644 index 0000000000..28a22c7da7 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testFlowViewportAPIAddPrims.py @@ -0,0 +1,38 @@ +# +# Copyright 2023 Autodesk, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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 TestFlowViewportAPIAddPrims(mtohUtils.MayaHydraBaseTestCase): + # MayaHydraBaseTestCase.setUpClass requirement. + _file = __file__ + + def setupScene(self): + self.setHdStormRenderer() + #Create a maya sphere named parentSphere + cmds.polySphere(name="parentSphere") + cmds.refresh() + + def test_AddPrimitives(self): + self.setupScene() + with PluginLoaded('mayaHydraCppTests'): + cmds.mayaHydraCppTest(f="FlowViewportAPI.addPrimitives") + +if __name__ == '__main__': + fixturesUtils.runTests(globals()) diff --git a/test/lib/mayaUsd/render/mayaToHydra/testFlowViewportAPI.py b/test/lib/mayaUsd/render/mayaToHydra/testFlowViewportAPI.py new file mode 100644 index 0000000000..f8f24092bc --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/testFlowViewportAPI.py @@ -0,0 +1,346 @@ +# +# Copyright 2023 Autodesk, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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. +# + +from math import* +import maya.cmds as cmds +import maya.api.OpenMaya as om +import fixturesUtils +import mtohUtils +import maya.mel as mel +from testUtils import PluginLoaded + +def setRotateY(matrixAsAList, angle): + ''' Sets the matrix as a list of values to be a Rotate about Y matrix (deg), and returns it''' + angle *= (pi/180); + matrixAsAList[0] = cos(angle) + matrixAsAList[2+4*2] = cos(angle) + matrixAsAList[0+4*2] = -sin(angle) + matrixAsAList[2+4*0] = sin(angle) + return matrixAsAList + +class TestFlowViewportAPI(mtohUtils.MtohTestCase): #Subclassing mtohUtils.MtohTestCase to be able to call self.assertSnapshotClose + # MayaHydraBaseTestCase.setUpClass requirement. + _file = __file__ + + def setupScene(self): + self.setHdStormRenderer() + + #Test adding primitives + def test_AddingPrimitives(self): + self.setupScene() + with PluginLoaded('flowViewportAPIMayaLocator'): + + #Create a maya sphere + sphereNode, sphereShape = cmds.polySphere() + cmds.refresh() + + #Create a FlowViewportAPIMayaLocator node which adds a dataProducerSceneIndex and a Filtering scene index + flowViewportNodeName = cmds.createNode("FlowViewportAPIMayaLocator") + self.assertFalse(flowViewportNodeName == None) + #When the node above is created, its compute method is not called automatically, so work around to trigger a call to compute + cmds.setAttr(flowViewportNodeName + '.dummyInput', 2)#setting this will set dirty the dummyOutput attribute + cmds.getAttr(flowViewportNodeName + '.dummyOutput')#getting this value will trigger a call to compute + cmds.refresh() + #Original images are located for example in maya-hydra\test\lib\mayaUsd\render\mayaToHydra\FlowViewportAPITest + self.assertSnapshotClose("add_NodeCreated.png", None, None) + + #Move the transform node, the added prims (cube grid) should move as well + # Get the transform node of the FlowViewportAPIMayaLocator + transformNode = cmds.listRelatives(flowViewportNodeName, parent=True)[0] + self.assertFalse(transformNode == None) + #Select the transform node + cmds.select(transformNode) + # Move the selected node + cmds.move(10, 5, -5) + cmds.refresh() + self.assertSnapshotClose("add_NodeMoved.png", None, None) + + #Hide the transform node, this should hide the FlowViewportAPIMayaLocator node and the added prims as well. + cmds.hide(transformNode) + self.assertSnapshotClose("add_NodeHidden.png", None, None) + + #Unhide the transform node, this should unhide the FlowViewportAPIMayaLocator node and the added prims as well. + cmds.showHidden(transformNode) + self.assertSnapshotClose("add_NodeUnhidden.png", None, None) + + #Delete the shape node + cmds.delete(flowViewportNodeName) + + self.assertSnapshotClose("add_NodeDeleted.png", None, None) + #Enable undo again + cmds.undoInfo(stateWithoutFlush=True) + + #Undo the delete, the node should be visible again + cmds.undo() + self.assertSnapshotClose("add_NodeDeletedUndo.png", None, None) + + #Redo the delete + cmds.redo() + self.assertSnapshotClose("add_NodeDeletedRedo.png", None, None) + + #Undo the delete again, the node should be visible again + cmds.undo() + self.assertSnapshotClose("add_NodeDeletedUndoAgain.png", None, None) + + #Move transform node again to see if it still updates the added prims transform + cmds.select(transformNode) + # Move the selected node + cmds.move(-20, -5, 0) + cmds.refresh() + self.assertSnapshotClose("add_NodeMovedAfterDeletionAndUndo.png", None, None) + + #Switch to VP2 + self.setViewport2Renderer() + #Switch back to Storm + self.setHdStormRenderer() + self.assertSnapshotClose("add_VP2AndThenBackToStorm.png", None, None) + + #Finish by a File New command + cmds.file(new=True, force=True) + + #Test Cube grids parameters + def test_CubeGrid(self): + self.setupScene() + with PluginLoaded('flowViewportAPIMayaLocator'): + + #Create a FlowViewportAPIMayaLocator node which adds a dataProducerSceneIndex and a Filtering scene index + flowViewportNodeName = cmds.createNode("FlowViewportAPIMayaLocator") + self.assertFalse(flowViewportNodeName == None) + + #When the node above is created, its compute method is not called automatically, so work around to trigger a call to compute + cmds.setAttr(flowViewportNodeName + '.dummyInput', 2)#setting this will set dirty the dummyOutput attribute + cmds.getAttr(flowViewportNodeName + '.dummyOutput')#getting this value will trigger a call to compute + self.assertSnapshotClose("cubeGrid_BeforeModifs.png", None, None) + + #Get the matrix and set a rotation of 70 degress around Y axis. + matrix = cmds.getAttr(flowViewportNodeName + '.cubeInitalTransform') + #Set it to have its rotation be a rotation around Y of 70 deg. + setRotateY(matrix, 70) + + #Modify the cube grid parameters + cmds.setAttr(flowViewportNodeName + '.numCubesX', 3) + cmds.setAttr(flowViewportNodeName + '.numCubesY', 2) + cmds.setAttr(flowViewportNodeName + '.numCubesZ', 3) + cmds.setAttr(flowViewportNodeName + '.cubeHalfSize', 0.5) + cmds.setAttr(flowViewportNodeName + '.cubeInitalTransform', matrix, type="matrix") + cmds.setAttr(flowViewportNodeName + '.cubeColor', 1.0, 1.0, 0.0, type="double3") + cmds.setAttr(flowViewportNodeName + '.cubeOpacity', 0.2) + cmds.setAttr(flowViewportNodeName + '.cubesUseInstancing', False) + cmds.setAttr(flowViewportNodeName + '.cubesDeltaTrans', 15, 15, 15, type="double3") + cmds.refresh() + self.assertSnapshotClose("cubeGrid_AfterModifs.png", None, None) + + #Test instancing + cmds.setAttr(flowViewportNodeName + '.cubesUseInstancing', True) + cmds.refresh() + self.assertSnapshotClose("cubeGrid_WithInstancing.png", None, None) + + #Add more cubes + cmds.setAttr(flowViewportNodeName + '.numCubesX', 30) + cmds.setAttr(flowViewportNodeName + '.numCubesY', 30) + cmds.setAttr(flowViewportNodeName + '.numCubesZ', 30) + cmds.setAttr(flowViewportNodeName + '.cubeColor', 0.0, 0.5, 1.0, type="double3") + cmds.setAttr(flowViewportNodeName + '.cubeOpacity', 0.3) + cmds.setAttr(flowViewportNodeName + '.cubesDeltaTrans', 5, 5, 5, type="double3") + cmds.refresh() + self.assertSnapshotClose("cubeGrid_WithInstancingModifs.png", None, None) + + #Switch to VP2 + self.setViewport2Renderer() + #Switch back to Storm + self.setHdStormRenderer() + self.assertSnapshotClose("cubeGrid_VP2AndThenBackToStorm.png", None, None) + + #Finish by a File New command + cmds.file(new=True, force=True) + + #Test multiple nodes + def test_MultipleNodes(self): + self.setupScene() + with PluginLoaded('flowViewportAPIMayaLocator'): + + #Create a FlowViewportAPIMayaLocator node which adds a dataProducerSceneIndex and a Filtering scene index + flowViewportNodeName1 = cmds.createNode("FlowViewportAPIMayaLocator", n="nodeShape1") + self.assertFalse(flowViewportNodeName1 == None) + + #When the node above is created, its compute method is not called automatically, so work around to trigger a call to compute + cmds.setAttr(flowViewportNodeName1 + '.dummyInput', 2)#setting this will set dirty the dummyOutput attribute + cmds.getAttr(flowViewportNodeName1 + '.dummyOutput')#getting this value will trigger a call to compute + + #Get the matrix and set a rotation of 70 degress around Y axis. + matrix = cmds.getAttr(flowViewportNodeName1 + '.cubeInitalTransform') + #Set it to have its rotation be a rotation around Y of 70 deg. + setRotateY(matrix, 70) + + #Modify the cube grid parameters + cmds.setAttr(flowViewportNodeName1 + '.numCubesX', 3) + cmds.setAttr(flowViewportNodeName1 + '.numCubesY', 3) + cmds.setAttr(flowViewportNodeName1 + '.numCubesZ', 3) + cmds.setAttr(flowViewportNodeName1 + '.cubeHalfSize', 0.5) + cmds.setAttr(flowViewportNodeName1 + '.cubeInitalTransform', matrix, type="matrix") + cmds.setAttr(flowViewportNodeName1 + '.cubeColor', 1.0, 0.0, 0.0, type="double3") + cmds.setAttr(flowViewportNodeName1 + '.cubeOpacity', 0.2) + cmds.setAttr(flowViewportNodeName1 + '.cubesUseInstancing', False) + cmds.setAttr(flowViewportNodeName1 + '.cubesDeltaTrans', 5, 5, 5, type="double3") + cmds.refresh() + + #Move the transform node, the added prims (cube grid) should move as well + # Get the transform node of the FlowViewportAPIMayaLocator + transformNode1 = cmds.listRelatives(flowViewportNodeName1, parent=True)[0] + self.assertFalse(transformNode1 == None) + #Select the transform node + cmds.select(transformNode1) + # Move the selected node + cmds.move(-10, 0, 0) + cmds.refresh() + + #Create a FlowViewportAPIMayaLocator node which adds a dataProducerSceneIndex and a Filtering scene index + flowViewportNodeName2 = cmds.createNode("FlowViewportAPIMayaLocator", n="nodeShape2") + self.assertFalse(flowViewportNodeName2 == None) + + #When the node above is created, its compute method is not called automatically, so work around to trigger a call to compute + cmds.setAttr(flowViewportNodeName2 + '.dummyInput', 3)#setting this will set dirty the dummyOutput attribute + cmds.getAttr(flowViewportNodeName2 + '.dummyOutput')#getting this value will trigger a call to compute + + #Get the matrix and set a rotation of 70 degress around Y axis. + matrix = cmds.getAttr(flowViewportNodeName2 + '.cubeInitalTransform') + #Set it to have its rotation be a rotation around Y of 70 deg. + setRotateY(matrix, 20) + + #Modify the cube grid parameters + cmds.setAttr(flowViewportNodeName2 + '.cubesUseInstancing', True) #Setting instancing to true first make it go faster when changing the number of cubes + cmds.setAttr(flowViewportNodeName2 + '.numCubesX', 10) + cmds.setAttr(flowViewportNodeName2 + '.numCubesY', 10) + cmds.setAttr(flowViewportNodeName2 + '.numCubesZ', 1) + cmds.setAttr(flowViewportNodeName2 + '.cubeHalfSize', 2) + cmds.setAttr(flowViewportNodeName2 + '.cubeInitalTransform', matrix, type="matrix") + cmds.setAttr(flowViewportNodeName2 + '.cubeColor', 0.0, 0.0, 1.0, type="double3") + cmds.setAttr(flowViewportNodeName2 + '.cubeOpacity', 0.8) + cmds.setAttr(flowViewportNodeName2 + '.cubesDeltaTrans', 10, 10, 10, type="double3") + cmds.refresh() + + #Move the transform node, the added prims (cube grid) should move as well + # Get the transform node of the FlowViewportAPIMayaLocator + transformNode2 = cmds.listRelatives(flowViewportNodeName2, parent=True)[0] + self.assertFalse(transformNode2 == None) + #Select the transform node + cmds.select(transformNode2) + # Move the selected node + cmds.move(-30, 0, -30) + cmds.refresh() + + self.assertSnapshotClose("multipleNodes_BeforeModifs.png", None, None) + + #Modify the color of node #2, it shouldn't change node's #1 color + cmds.setAttr(flowViewportNodeName2 + '.cubeColor', 1.0, 1.0, 1.0, type="double3") + cmds.setAttr(flowViewportNodeName2 + '.cubeOpacity', 0.1) + + # Apply transform on node #2 + cmds.select(transformNode2) + cmds.move(-30, 0, 0) + cmds.rotate(-30, 45, 0) + cmds.scale(2, 1, 1) + cmds.refresh() + self.assertSnapshotClose("multipleNodes_AfterModifs.png", None, None) + + #Remove instancing, the cubes should stay at the same place + cmds.setAttr(flowViewportNodeName2 + '.cubesUseInstancing', False) + self.assertSnapshotClose("multipleNodes_AfterModifsRemoveInstancing.png", None, None) + + #Hide node #1 + cmds.hide(transformNode1) + self.assertSnapshotClose("multipleNodes_Node1Hidden.png", None, None) + + #Unhide node #1 + cmds.showHidden(transformNode1) + self.assertSnapshotClose("multipleNodes_Node1Unhidden.png", None, None) + + #Switch to VP2 + self.setViewport2Renderer() + #Switch back to Storm + self.setHdStormRenderer() + self.assertSnapshotClose("multipleNodes_VP2AndThenBackToStorm.png", None, None) + + #Finish by a File New command + cmds.file(new=True, force=True) + + #Test multiple viewports + def test_MultipleViewports(self): + with PluginLoaded('flowViewportAPIMayaLocator'): + #switch to 4 views + mel.eval('FourViewLayout') + #Set focus on persp view + cmds.setFocus ('modelPanel4') #Is the persp view + #Set Storm as the renderer + self.setHdStormRenderer() + + #Set focus on model Panel 2 (it's an orthographic view : right) + cmds.setFocus ('modelPanel2') + #Set Storm as the renderer + self.setHdStormRenderer() + + #Create a maya sphere + sphereNode, sphereShape = cmds.polySphere() + #Select the transform node + cmds.select(sphereNode) + # Move the selected node + cmds.move(15, 0, 0) + cmds.refresh() + + #Create a FlowViewportAPIMayaLocator node which adds a dataProducerSceneIndex and a Filtering scene index + flowViewportNodeName1 = cmds.createNode("FlowViewportAPIMayaLocator", n="nodeShape1") + self.assertFalse(flowViewportNodeName1 == None) + + #When the node above is created, its compute method is not called automatically, so work around to trigger a call to compute + cmds.setAttr(flowViewportNodeName1 + '.dummyInput', 2)#setting this will set dirty the dummyOutput attribute + cmds.getAttr(flowViewportNodeName1 + '.dummyOutput')#getting this value will trigger a call to compute + + #Modify the cube grid parameters + cmds.setAttr(flowViewportNodeName1 + '.numCubesX', 3) + cmds.setAttr(flowViewportNodeName1 + '.numCubesY', 3) + cmds.setAttr(flowViewportNodeName1 + '.numCubesZ', 3) + cmds.setAttr(flowViewportNodeName1 + '.cubeHalfSize', 1.0) + cmds.setAttr(flowViewportNodeName1 + '.cubeColor', 1.0, 0.0, 0.0, type="double3") + cmds.setAttr(flowViewportNodeName1 + '.cubeOpacity', 0.8) + cmds.setAttr(flowViewportNodeName1 + '.cubesUseInstancing', False) + cmds.setAttr(flowViewportNodeName1 + '.cubesDeltaTrans', 3, 3, 3, type="double3") + cmds.refresh() + + cmds.setFocus ('modelPanel4') + self.assertSnapshotClose("multipleViewports_viewPanel4.png", None, None) + cmds.setFocus ('modelPanel2') + self.assertSnapshotClose("multipleViewports_viewPanel2.png", None, None) + + #Switch to VP2 + cmds.setFocus ('modelPanel4') + self.setViewport2Renderer() + self.assertSnapshotClose("multipleViewports_VP2_modPan4.png", None, None) + cmds.setFocus ('modelPanel2') + self.setViewport2Renderer() + self.assertSnapshotClose("multipleViewports_VP2_modPan2.png", None, None) + + #Switch back to Storm + cmds.setFocus ('modelPanel4') + self.setHdStormRenderer() + self.assertSnapshotClose("multipleViewports_VP2AndThenBackToStorm_modPan4.png", None, None) + cmds.setFocus ('modelPanel2') + self.setHdStormRenderer() + self.assertSnapshotClose("multipleViewports_VP2AndThenBackToStorm_modPan2.png", None, None) + + #Finish by a File New command + cmds.file(new=True, force=True) +if __name__ == '__main__': + fixturesUtils.runTests(globals()) diff --git a/test/testUtils/imageUtils.py b/test/testUtils/imageUtils.py index cbdf360d07..c93cf38fb0 100644 --- a/test/testUtils/imageUtils.py +++ b/test/testUtils/imageUtils.py @@ -214,11 +214,14 @@ def assertImagesClose(self, imagePath1, imagePath2, fail, failpercent, hardfail= 3 -- The images were not the same size and could not be compared. 4 -- File error: could not find or open input files, etc. """ - + #Disable undo + cmds.undoInfo(stateWithoutFlush=False) proc = imageDiff(imagePath1, imagePath2, verbose=True, fail=fail, failpercent=failpercent, hardfail=hardfail, warn=warn, warnpercent=warnpercent, hardwarn=hardwarn, perceptual=perceptual) + #Enable undo again + cmds.undoInfo(stateWithoutFlush=True) if proc.returncode not in (0, 1): self.fail(str(proc.stdout)) return proc.returncode @@ -228,11 +231,15 @@ def assertImagesEqual(self, imagePath1, imagePath2): def assertSnapshotClose(self, refImage, fail, failpercent, hardfail=None, warn=None, warnpercent=None, hardwarn=None, perceptual=False): + #Disable undo so that when we call undo it doesn't undo any operation from self.assertSnapshotClose + cmds.undoInfo(stateWithoutFlush=False) snapDir = os.path.join(os.path.abspath('.'), self._testMethodName) if not os.path.isdir(snapDir): os.makedirs(snapDir) snapImage = os.path.join(snapDir, os.path.basename(refImage)) snapshot(snapImage) + #Enable undo again + cmds.undoInfo(stateWithoutFlush=True) return self.assertImagesClose(refImage, snapImage, fail=fail, failpercent=failpercent, hardfail=hardfail,