From 94905867b82bea73b9f09a7c80e8458942c3a92e Mon Sep 17 00:00:00 2001 From: ppt-adsk Date: Tue, 26 Mar 2024 14:41:24 -0400 Subject: [PATCH] Native instance picking. (#107) * Native instance picking. * Fixed gcc 11.2 Linux compilation. --- lib/mayaHydra/mayaPlugin/renderOverride.cpp | 20 +++ .../mayaUsd/render/mayaToHydra/CMakeLists.txt | 1 + .../render/mayaToHydra/cpp/CMakeLists.txt | 1 + .../cpp/testUsdNativeInstancePicking.cpp | 116 ++++++++++++++++++ .../cpp/testUsdNativeInstancePicking.py | 49 ++++++++ .../cubesHierarchy.usda | 33 +++++ .../instancedCubeHierarchies.usda | 22 ++++ 7 files changed, 242 insertions(+) create mode 100644 test/lib/mayaUsd/render/mayaToHydra/cpp/testUsdNativeInstancePicking.cpp create mode 100644 test/lib/mayaUsd/render/mayaToHydra/cpp/testUsdNativeInstancePicking.py create mode 100644 test/testSamples/testUsdNativeInstances/cubesHierarchy.usda create mode 100644 test/testSamples/testUsdNativeInstances/instancedCubeHierarchies.usda diff --git a/lib/mayaHydra/mayaPlugin/renderOverride.cpp b/lib/mayaHydra/mayaPlugin/renderOverride.cpp index 78879dacc6..026896bb08 100644 --- a/lib/mayaHydra/mayaPlugin/renderOverride.cpp +++ b/lib/mayaHydra/mayaPlugin/renderOverride.cpp @@ -393,6 +393,26 @@ class UsdPickHandler : public MtohRenderOverride::PickHandlerBase { return {primOrigin.GetFullPath(), -1}; } + // If there is a Hydra instancer, distinguish between native instancing + // (implicit USD prototype created by USD itself) and point instancing + // (explicitly authored USD prototypes). As per HdxInstancerContext + // documentation: + // + // [...] "exactly one of instancePrimOrigin or instancerPrimOrigin will + // contain data depending on whether the instancing at the current + // level was implicit or not, respectively." + const auto& instancerContext = primOrigin.instancerContexts.front(); + + if (instancerContext.instancePrimOrigin) { + // Implicit prototype instancing (i.e. USD native instancing). + auto schema = HdPrimOriginSchema(instancerContext.instancePrimOrigin); + if (!TF_VERIFY(schema, "Cannot build prim origin schema for USD native instance.")) { + return {SdfPath(), -1}; + } + return {schema.GetOriginPath(HdPrimOriginSchemaTokens->scenePath), -1}; + } + + // Explicit prototype instancing (i.e. USD point instancing). std::function pickFn[] = {pickInstancer, pickInstance, pickPrototype}; // Retrieve pick mode from mayaUsd optionVar, to see if we're picking diff --git a/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt b/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt index e78c0aaa02..e8deb837b4 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt +++ b/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt @@ -47,6 +47,7 @@ set(INTERACTIVE_TEST_SCRIPT_FILES cpp/testPrimInstancing.py cpp/testPicking.py cpp/testPointInstancePicking.py + cpp/testUsdNativeInstancePicking.py ) #Add this test only if the MayaUsd_FOUND (so also MAYAUSDAPI_LIBRARY) has been found during compile time. diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt b/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt index 9555bbd61d..e5a1720083 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt @@ -36,6 +36,7 @@ target_sources(${TARGET_NAME} testPrimInstancing.cpp testPicking.cpp testPointInstancePicking.cpp + testUsdNativeInstancePicking.cpp ) # ----------------------------------------------------------------------------- diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUsdNativeInstancePicking.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUsdNativeInstancePicking.cpp new file mode 100644 index 0000000000..ccf1197555 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUsdNativeInstancePicking.cpp @@ -0,0 +1,116 @@ +// Copyright 2024 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "testUtils.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +using namespace MayaHydra; + +TEST(TestUsdNativeInstancePicking, pickInstance) +{ + const auto& sceneIndices = GetTerminalSceneIndices(); + ASSERT_GT(sceneIndices.size(), 0u); + auto siRoot = sceneIndices.front(); + + auto [argc, argv] = getTestingArgs(); + ASSERT_EQ(argc, 1); + const Ufe::Path selected(Ufe::PathString::path(argv[0])); + + // Maya selection API doesn't understand USD data, which can only be + // represented through UFE, so use UFE API to interact with Maya selection. + const auto sn = Ufe::GlobalSelection::get(); + sn->clear(); + + // Translate the application path into a scene index path using the + // selection scene index. + // The Flow Viewport selection scene index is in the scene index tree. + const auto snSi = findSelectionSceneIndexInTree(siRoot); + ASSERT_TRUE(snSi); + + const auto sceneIndexPath = snSi->SceneIndexPath(selected); + + ASSERT_FALSE(sceneIndexPath.IsEmpty()); + + const auto prim = siRoot->GetPrim(sceneIndexPath); + ASSERT_TRUE(prim.dataSource); + + // There is no selections data source on the prim. + auto dataSourceNames = prim.dataSource->GetNames(); + ASSERT_EQ(std::find(dataSourceNames.begin(), dataSourceNames.end(), HdSelectionsSchemaTokens->selections), dataSourceNames.end()); + + // Selection scene index says the prim is not selected. + ASSERT_FALSE(snSi->IsFullySelected(sceneIndexPath)); + + //====================================================================== + // Perform a pick + //====================================================================== + + M3dView active3dView = M3dView::active3dView(); + + const auto primMouseCoords = getPrimMouseCoords(prim, active3dView); + + mouseClick(Qt::MouseButton::LeftButton, active3dView.widget(), primMouseCoords); + active3dView.refresh(); + + //====================================================================== + // Test that the pick changed the Maya selection + //====================================================================== + + // When picking on the boundary of multiple objects, one Hydra pick hit per + // object is returned. Therefore test that the expected selected path is + // in the selection. + ASSERT_GE(sn->size(), 1u); + ASSERT_TRUE(sn->contains(selected)); + + //====================================================================== + // Test that the pick changed the Hydra selection + //====================================================================== + + // On selection, the prim is given a selections data source. + dataSourceNames = prim.dataSource->GetNames(); + ASSERT_NE(std::find(dataSourceNames.begin(), dataSourceNames.end(), HdSelectionsSchemaTokens->selections), dataSourceNames.end()); + + auto snDataSource = prim.dataSource->Get(HdSelectionsSchemaTokens->selections); + ASSERT_TRUE(snDataSource); + auto selectionsSchema = HdSelectionsSchema::GetFromParent(prim.dataSource); + ASSERT_TRUE(selectionsSchema); + + // Only one selection in the selections schema. + ASSERT_EQ(selectionsSchema.GetNumElements(), 1u); + auto selectionSchema = selectionsSchema.GetElement(0); + + // Prim is fully selected. + auto ds = selectionSchema.GetFullySelected(); + ASSERT_TRUE(ds); + ASSERT_TRUE(ds->GetTypedValue(0.0f)); + + // Selection scene index says the prim is selected. + ASSERT_TRUE(snSi->IsFullySelected(sceneIndexPath)); + ASSERT_TRUE(snSi->HasFullySelectedAncestorInclusive(sceneIndexPath)); +} diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUsdNativeInstancePicking.py b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUsdNativeInstancePicking.py new file mode 100644 index 0000000000..aa1e2d1677 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUsdNativeInstancePicking.py @@ -0,0 +1,49 @@ +# Copyright 2024 Autodesk +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import maya.cmds as cmds +import fixturesUtils +import mtohUtils +import unittest +import usdUtils + +import testUtils +from testUtils import PluginLoaded + +class TestUsdNativeInstancePicking(mtohUtils.MtohTestCase): + # MayaHydraBaseTestCase.setUpClass requirement. + _file = __file__ + + PICK_PATH = "|instancedCubeHierarchies|instancedCubeHierarchiesShape,/cubeHierarchies" + + def loadUsdScene(self): + usdScenePath = testUtils.getTestScene('testUsdNativeInstances', 'instancedCubeHierarchies.usda') + usdUtils.createStageFromFile(usdScenePath) + + def setUp(self): + super(TestUsdNativeInstancePicking, self).setUp() + self.loadUsdScene() + cmds.refresh() + + @unittest.skipUnless(mtohUtils.checkForMayaUsdPlugin(), "Requires Maya USD Plugin.") + def test_NativeInstances(self): + with PluginLoaded('mayaHydraCppTests'): + instances = ["/cubes_1", "/cubes_2"] + for instance in instances: + cmds.mayaHydraCppTest( + self.PICK_PATH + instance, + f="TestUsdNativeInstancePicking.pickInstance") + +if __name__ == '__main__': + fixturesUtils.runTests(globals()) diff --git a/test/testSamples/testUsdNativeInstances/cubesHierarchy.usda b/test/testSamples/testUsdNativeInstances/cubesHierarchy.usda new file mode 100644 index 0000000000..cfdf02bf7f --- /dev/null +++ b/test/testSamples/testUsdNativeInstances/cubesHierarchy.usda @@ -0,0 +1,33 @@ +#usda 1.0 +( + defaultPrim = "parent" + metersPerUnit = 0.01 + upAxis = "Y" +) + +def Xform "parent" ( + kind = "component" +) +{ + def Mesh "topCube" + { + uniform bool doubleSided = 1 + float3[] extent = [(-0.25, -0.25, -0.25), (0.25, 0.25, 0.25)] + int[] faceVertexCounts = [4, 4, 4, 4, 4, 4] + int[] 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] + point3f[] points = [(-0.25, -0.25, 0.25), (0.25, -0.25, 0.25), (-0.25, 0.25, 0.25), (0.25, 0.25, 0.25), (-0.25, 0.25, -0.25), (0.25, 0.25, -0.25), (-0.25, -0.25, -0.25), (0.25, -0.25, -0.25)] + double3 xformOp:translate = (0, 1.25, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Mesh "baseCube" + { + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)] + int[] faceVertexCounts = [4, 4, 4, 4, 4, 4] + int[] 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] + point3f[] points = [(-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (-0.5, 0.5, 0.5), (0.5, 0.5, 0.5), (-0.5, 0.5, -0.5), (0.5, 0.5, -0.5), (-0.5, -0.5, -0.5), (0.5, -0.5, -0.5)] + double3 xformOp:translate = (0, 0.5, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } +} diff --git a/test/testSamples/testUsdNativeInstances/instancedCubeHierarchies.usda b/test/testSamples/testUsdNativeInstances/instancedCubeHierarchies.usda new file mode 100644 index 0000000000..a546504771 --- /dev/null +++ b/test/testSamples/testUsdNativeInstances/instancedCubeHierarchies.usda @@ -0,0 +1,22 @@ +#usda 1.0 + +def "cubeHierarchies" +{ + def Xform "cubes_1" ( + instanceable = true + references = @./cubesHierarchy.usda@ + ) + { + double3 xformOp:translate = (-1.0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Xform "cubes_2" ( + instanceable = true + references = @./cubesHierarchy.usda@ + ) + { + double3 xformOp:translate = (1.0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } +} \ No newline at end of file