Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native instance picking. #107

Merged
merged 2 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions lib/mayaHydra/mayaPlugin/renderOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {{}, -1};
}
return {schema.GetOriginPath(HdPrimOriginSchemaTokens->scenePath), -1};
}

// Explicit prototype instancing (i.e. USD point instancing).
std::function<HitPath(const HdxPrimOriginInfo& primOrigin, const HdxPickHit& hit)> pickFn[] = {pickInstancer, pickInstance, pickPrototype};

// Retrieve pick mode from mayaUsd optionVar, to see if we're picking
Expand Down
1 change: 1 addition & 0 deletions test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ target_sources(${TARGET_NAME}
testPrimInstancing.cpp
testPicking.cpp
testPointInstancePicking.cpp
testUsdNativeInstancePicking.cpp
)

# -----------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <maya/M3dView.h>
#include <maya/MPoint.h>

#include <pxr/imaging/hd/selectionSchema.h>
#include <pxr/imaging/hd/selectionsSchema.h>

#include <ufe/path.h>
#include <ufe/pathString.h>
#include <ufe/observableSelection.h>
#include <ufe/globalSelection.h>

#include <gtest/gtest.h>

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));
}
Original file line number Diff line number Diff line change
@@ -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())
33 changes: 33 additions & 0 deletions test/testSamples/testUsdNativeInstances/cubesHierarchy.usda
Original file line number Diff line number Diff line change
@@ -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"]
}
}
Original file line number Diff line number Diff line change
@@ -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"]
}
}
Loading