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

HYDRA-843 : Add picking test + Link C++ tests with Qt for UI/viewport interaction #94

Merged
merged 35 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
be0a738
HYDRA-843 : Very rough working draft for picking + marquee
debloip-adsk Feb 29, 2024
fac4838
HYDRA-843 : Lead object inconsistency debugging
debloip-adsk Feb 29, 2024
c90aa3d
HYDRA-843 : Make marquee test based on data sources
debloip-adsk Mar 1, 2024
5987c93
HYDRA-843 : Make picking test based on data source
debloip-adsk Mar 1, 2024
ee8bb59
HYDRA-843 : Add tests for picking USD meshes copied from Maya and imp…
debloip-adsk Mar 1, 2024
07ab827
HYDRA-843 : Refactor to pick different prim type
debloip-adsk Mar 1, 2024
5b9429a
HYDRA-843 : Add test to pick Maya light
debloip-adsk Mar 1, 2024
6f20975
HYDRA-843 : Add test to pick USD light
debloip-adsk Mar 3, 2024
a54766e
HYDRA-843 : Partial refactor in preparation for marquee test
debloip-adsk Mar 3, 2024
ef42886
HYDRA-843 : Add translations to USD objects
debloip-adsk Mar 3, 2024
61aeea1
HYDRA-843 : Working marquee selection test
debloip-adsk Mar 3, 2024
21bf488
HYDRA-843 : Rename variables
debloip-adsk Mar 3, 2024
b66143f
HYDRA-843 : Mini-refactor of getting Maya object names
debloip-adsk Mar 3, 2024
24dbdfe
HYDRA-843 : Add comments to marquee cpp test
debloip-adsk Mar 3, 2024
5f7a166
HYDRA-843 : Move mouse utility methods to testUtils
debloip-adsk Mar 3, 2024
3fe10ee
HYDRA-843 : Avoid force-moving cursor
debloip-adsk Mar 3, 2024
d25e185
HYDRA-843 : Remove unused changes
debloip-adsk Mar 4, 2024
ab8d687
HYDRA-843 : Remove unused includes
debloip-adsk Mar 4, 2024
185843c
HYDRA-843 : Adjust Qt CMake setup
debloip-adsk Mar 4, 2024
1a5727d
HYDRA-843 : Add comments for Qt mouse & keyboard button vars
debloip-adsk Mar 4, 2024
2dcf9ab
HYDRA-843 : Use int for iArg to fix warning on Linux
debloip-adsk Mar 4, 2024
9254150
HYDRA-843 : Attempt to revert QT_NO_KEYWORDS to be on each target
debloip-adsk Mar 4, 2024
910bcb8
HYDRA-843 : Attempt to fix macro redefinition
debloip-adsk Mar 4, 2024
2fa72ca
Revert "HYDRA-843 : Attempt to revert QT_NO_KEYWORDS to be on each ta…
debloip-adsk Mar 6, 2024
42e4997
Revert "HYDRA-843 : Attempt to fix macro redefinition"
debloip-adsk Mar 6, 2024
1e41ae0
HYDRA_843 : Add _USE_MATH_DEFINES definition in cpp test plugin
debloip-adsk Mar 6, 2024
60157b7
HYDRA-843 : Attempt to fix previous commit
debloip-adsk Mar 6, 2024
110498a
HYDRA-843 : Adjust comment
debloip-adsk Mar 6, 2024
f32fb7e
Merge branch 'dev' into debloip/HYDRA-843/test-picking
debloip-adsk Mar 6, 2024
b724358
Merge branch 'dev' into debloip/HYDRA-843/test-picking
debloip-adsk Mar 7, 2024
eed7e1d
HYDRA-843 : Move _USE_MATH_DEFINES to global compiler config and add …
debloip-adsk Mar 7, 2024
490884e
HYDRA-843 : Adjust comment
debloip-adsk Mar 7, 2024
0d0f6fb
HYDRA-843 : Move BOOST_DEBUG_PYTHON to MSVC_DEFINITIONS
debloip-adsk Mar 7, 2024
1d4edce
HYDRA-843 : Add extra info on _USE_MATH_DEFINES problem
debloip-adsk Mar 8, 2024
49da711
HYDRA-843 : Set QT_NO_KEYWORDS on a per-target basis
debloip-adsk Mar 8, 2024
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
54 changes: 47 additions & 7 deletions cmake/compiler_config.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,53 @@ set(MSVC_DEFINITIONS
# Needed to prevent Python from adding a define for snprintf
# since it was added in Visual Studio 2015.
HAVE_SNPRINTF

# In debug builds, Boost's wrap_python.hpp will #undef _DEBUG when BOOST_DEBUG_PYTHON is not defined,
# and will then include pyconfig.h :
# https://github.com/boostorg/python/blob/boost-1.81.0/include/boost/python/detail/wrap_python.hpp#L23-L57
# pyconfig.h will then autolink the Python lib; however, since _DEBUG was #undef'd, it will try to
# link against the release version of the Python lib, causing a linker error :
# https://github.com/python/cpython/blob/v3.11.4/PC/pyconfig.h#L269-L286
$<$<STREQUAL:${CMAKE_BUILD_TYPE},Debug>:BOOST_DEBUG_PYTHON>
seando-adsk marked this conversation as resolved.
Show resolved Hide resolved

# When using the qmath.h Qt header, we can run into macro redefinition issues.
# Specifically, qmath.h wants to define the math macros listed here :
# https://learn.microsoft.com/en-us/cpp/c-runtime-library/math-constants?view=msvc-170
# To do so, it will first try to include cmath. cmath includes cstdlib, which includes math.h,
# which includes corecrt_math_defines.h, which defines the math macros.
# However, corecrt_math_defines.h is only included by math.h if _USE_MATH_DEFINES is defined.
# If _USE_MATH_DEFINES is not defined, Qt will temporarily define it itself, include cmath
# so that corecrt_math_defines.h ends up defining the math macros, and undefine it afterwards. See here :
# https://github.com/qt/qtbase/blob/85c69f023fe281b7f16e1a93e61be4432f7fef9b/src/corelib/kernel/qmath.h#L18-L28
# If the math macros are still not defined after that, Qt will resort to defining the macros itself :
# https://github.com/qt/qtbase/blob/85c69f023fe281b7f16e1a93e61be4432f7fef9b/src/corelib/kernel/qmath.h#L188-L238
# As such, it is guaranteed that the math macros are defined after including qmath.h.
# So what's the issue? Well, 2 things combined. First, it turns out that while cmath, cstdlib and
# corecrt_math_defines.h have header guards, math.h does not. Second, corecrt_math_defines.h does
# not check that the math macros are not defined before defining them itself.
# This can lead to the following scenario :
# 1. _USE_MATH_DEFINES is not defined.
# 2. We hit an #include <cmath> or <cstdlib>.
# 3. The cmath or cstdlib header guards get defined.
# 4. math.h gets included, but since _USE_MATH_DEFINES is not defined, corecrt_math_defines.h
# is not included, and the math macros do not get defined.
# 5. We hit an #include <qmath.h>.
# 6. Qt temporarily defines _USE_MATH_DEFINES and includes cmath.
# 7. We hit the cmath or cstdlib header guard and stop the inclusion, not reaching math.h
# and corecrt_math_defines.h. The math macros are still not defined.
# 8. Qt sees that the math macros are still not defined, and defines them itself.
# 9. _USE_MATH_DEFINES gets defined.
# 10. We hit an #include <math.h>.
# 11. Since there is no header guard for math.h, it is re-included.
# 12. As _USE_MATH_DEFINES is now defined, corecrt_math_defines.h does get included this time.
# 13. corecrt_math_defines.h defines the macros without checking if they have already been defined :
# macro redefinition -> warning -> warning treated-as-error -> error -> compilation fails.
# In our case, step 9 (_USE_MATH_DEFINES getting defined late) happens in MTypes.h, and step 10
# (math.h re-inclusion) in some other Maya headers, for example MFloatVector.h.
# By defining _USE_MATH_DEFINES from the get-go, we ensure that the math macros get defined by
# corecrt_math_defines.h the first time around. Afterwards, even if math.h does not have a
# header guard, the one in corecrt_math_defines.h avoids redefining the macros.
_USE_MATH_DEFINES
Comment on lines +114 to +151
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the new fix for the error I was getting in preflights. This might only be a problem when using Qt, but I'm putting this in the general compiler_config.cmake so we don't have to re-discover this again. There's also another discussion to be had about why this error only occurred in preflights, I'll follow up on that separately.

)

#------------------------------------------------------------------------------
Expand Down Expand Up @@ -165,13 +212,6 @@ function(mayaHydra_compile_config TARGET)
target_compile_definitions(${TARGET}
PRIVATE
${MSVC_DEFINITIONS}
# In debug builds, Boost's wrap_python.hpp will #undef _DEBUG when BOOST_DEBUG_PYTHON is not defined,
# and will then include pyconfig.h :
# https://github.com/boostorg/python/blob/boost-1.81.0/include/boost/python/detail/wrap_python.hpp#L23-L57
# pyconfig.h will then autolink the Python lib; however, since _DEBUG was #undef'd, it will try to
# link against the release version of the Python lib, causing a linker error :
# https://github.com/python/cpython/blob/v3.11.4/PC/pyconfig.h#L269-L286
$<$<STREQUAL:${CMAKE_BUILD_TYPE},Debug>:BOOST_DEBUG_PYTHON>
)
endif()

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 @@ -45,6 +45,7 @@ set(INTERACTIVE_TEST_SCRIPT_FILES
cpp/testFlowViewportAPIFilterPrims.py
cpp/testSceneCorrectness.py
cpp/testPrimInstancing.py
cpp/testPicking.py
)

#Add this test only if the MayaUsd_FOUND (so also MAYAUSDAPI_LIBRARY) has been found during compile time.
Expand Down
12 changes: 12 additions & 0 deletions test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
# -----------------------------------------------------------------------------
find_package(GTest REQUIRED)

if (NOT Qt6_FOUND)
message(WARNING "No Qt6 package found. Cannot build ${TARGET_NAME}.")
return()
endif()

set(TARGET_NAME mayaHydraCppTests)
add_library(${TARGET_NAME} SHARED)

Expand All @@ -29,11 +34,16 @@ target_sources(${TARGET_NAME}
testFlowViewportAPIFilterPrims.cpp
testSceneCorrectness.cpp
testPrimInstancing.cpp
testPicking.cpp
)

# -----------------------------------------------------------------------------
# compiler configuration
# -----------------------------------------------------------------------------
# QT_NO_KEYWORDS prevents Qt from defining the foreach, signals, slots and emit macros.
# this avoids overlap between Qt macros and boost, and enforces using Q_ macros.
set_target_properties(Qt6::Core PROPERTIES INTERFACE_COMPILE_DEFINITIONS QT_NO_KEYWORDS)

mayaHydra_compile_config(${TARGET_NAME})

target_compile_definitions(${TARGET_NAME}
Expand All @@ -59,6 +69,8 @@ target_link_libraries(${TARGET_NAME}
mayaHydraLib
${GTEST_LIBRARIES}
flowViewport
Qt6::Core
Qt6::Widgets
)

# -----------------------------------------------------------------------------
Expand Down
196 changes: 196 additions & 0 deletions test/lib/mayaUsd/render/mayaToHydra/cpp/testPicking.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// 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 <pxr/imaging/hd/selectionSchema.h>
#include <pxr/imaging/hd/selectionsSchema.h>
#include <pxr/imaging/hd/xformSchema.h>

#include <maya/M3dView.h>
#include <maya/MPoint.h>

#include <gtest/gtest.h>

PXR_NAMESPACE_USING_DIRECTIVE

using namespace MayaHydra;

namespace {

FindPrimPredicate findPickPrimPredicate(const std::string& objectName, const TfToken& primType)
{
return [objectName,
primType](const HdSceneIndexBaseRefPtr& sceneIndex, const SdfPath& primPath) -> bool {
return primPath.GetAsString().find(objectName) != std::string::npos
&& sceneIndex->GetPrim(primPath).primType == primType;
};
}

void getPrimMouseCoords(const HdSceneIndexPrim& prim, M3dView& view, QPoint& outMouseCoords)
{
HdDataSourceBaseHandle xformDataSource = HdContainerDataSource::Get(prim.dataSource, HdXformSchema::GetDefaultLocator());
ASSERT_NE(xformDataSource, nullptr);
HdContainerDataSourceHandle xformContainerDataSource = HdContainerDataSource::Cast(xformDataSource);
ASSERT_NE(xformContainerDataSource, nullptr);
HdXformSchema xformSchema(xformContainerDataSource);
ASSERT_NE(xformSchema.GetMatrix(), nullptr);
GfMatrix4d xformMatrix = xformSchema.GetMatrix()->GetTypedValue(0);
GfVec3d translation = xformMatrix.ExtractTranslation();

MPoint worldPosition(translation[0], translation[1], translation[2], 1.0);
short viewportX = 0, viewportY = 0;
MStatus worldToViewStatus;
// First assert checks that the point was not clipped, second assert checks the general MStatus
ASSERT_TRUE(view.worldToView(worldPosition, viewportX, viewportY, &worldToViewStatus));
ASSERT_TRUE(worldToViewStatus);

// Qt and M3dView use opposite Y-coordinates
outMouseCoords = QPoint(viewportX, view.portHeight() - viewportY);
}

void ensureSelected(const SceneIndexInspector& inspector, const FindPrimPredicate& primPredicate)
{
// 2024-03-01 : Due to the extra "Lighted" hierarchy, it is possible for an object to be split
// into two prims, only one of which will be selected. We will tolerate this in the test, but
// we'll make sure there are at most two prims for that object. We'll also allow a prim not
// to have any selections, but at least one prim must be selected.
PrimEntriesVector primEntries = inspector.FindPrims(primPredicate);
ASSERT_GE(primEntries.size(), 1u);
ASSERT_LE(primEntries.size(), 2u);

size_t nbSelectedPrims = 0;
for (const auto& primEntry : primEntries) {
HdSelectionsSchema selectionsSchema = HdSelectionsSchema::GetFromParent(primEntry.prim.dataSource);
if (selectionsSchema.GetNumElements() > 0u) {
ASSERT_EQ(selectionsSchema.GetNumElements(), 1u);
HdSelectionSchema selectionSchema = selectionsSchema.GetElement(0);
EXPECT_TRUE(selectionSchema.GetFullySelected());
nbSelectedPrims++;
}
}

ASSERT_GT(nbSelectedPrims, 0u);
}

void ensureUnselected(const SceneIndexInspector& inspector, const FindPrimPredicate& primPredicate)
{
PrimEntriesVector primEntries = inspector.FindPrims(primPredicate);
for (const auto& primEntry : primEntries) {
HdSelectionsSchema selectionsSchema
= HdSelectionsSchema::GetFromParent(primEntry.prim.dataSource);
ASSERT_EQ(selectionsSchema.IsDefined(), false);
}
}

} // namespace

TEST(TestPicking, pickObject)
{
const SceneIndicesVector& sceneIndices = GetTerminalSceneIndices();
ASSERT_GT(sceneIndices.size(), 0u);
SceneIndexInspector inspector(sceneIndices.front());

auto [argc, argv] = getTestingArgs();
ASSERT_EQ(argc, 2);
const std::string objectName(argv[0]);
const TfToken primType(argv[1]);

ensureUnselected(inspector, PrimNamePredicate(objectName));

PrimEntriesVector prims = inspector.FindPrims(findPickPrimPredicate(objectName, primType));
ASSERT_EQ(prims.size(), 1u);

M3dView active3dView = M3dView::active3dView();

QPoint primMouseCoords;
getPrimMouseCoords(prims.front().prim, active3dView, primMouseCoords);

mousePress(Qt::MouseButton::LeftButton, active3dView.widget(), primMouseCoords);
mouseRelease(Qt::MouseButton::LeftButton, active3dView.widget(), primMouseCoords);

active3dView.refresh();

ensureSelected(inspector, PrimNamePredicate(objectName));
}

TEST(TestPicking, marqueeSelect)
{
const SceneIndicesVector& sceneIndices = GetTerminalSceneIndices();
ASSERT_GT(sceneIndices.size(), 0u);
SceneIndexInspector inspector(sceneIndices.front());

auto [argc, argv] = getTestingArgs();
ASSERT_TRUE(argc % 2 == 0); // Each object is identified by both its name and a type
ASSERT_TRUE(argc >= 4); // We need at least two objects to do the marquee selection
std::vector<std::pair<std::string, TfToken>> objectsToSelect;
for (int iArg = 0; iArg < argc; iArg += 2) {
objectsToSelect.push_back(std::make_pair(std::string(argv[iArg]), TfToken(argv[iArg + 1])));
}

for (const auto& object : objectsToSelect) {
ensureUnselected(inspector, PrimNamePredicate(object.first));
}

M3dView active3dView = M3dView::active3dView();

// We get the first prim's mouse coordinates and initialize the selection rectangle
// with them; we then iterate on the other prims and expand the selection rectangle
// to fit them all.

// Get the first prim's mouse coords
PrimEntriesVector initialPrimEntries = inspector.FindPrims(
findPickPrimPredicate(objectsToSelect.front().first, objectsToSelect.front().second));
ASSERT_EQ(initialPrimEntries.size(), 1u);
QPoint initialMouseCoords;
getPrimMouseCoords(initialPrimEntries.front().prim, active3dView, initialMouseCoords);

// Initialize the selection rectangle
QPoint topLeftMouseCoords = initialMouseCoords;
QPoint bottomRightMouseCoords = initialMouseCoords;

// Expand the selection rectangle to fit all prims
for (size_t iObject = 1; iObject < objectsToSelect.size(); iObject++) {
PrimEntriesVector objectPrims = inspector.FindPrims(
findPickPrimPredicate(objectsToSelect[iObject].first, objectsToSelect[iObject].second));
ASSERT_EQ(objectPrims.size(), 1u);
QPoint objectMouseCoords;
getPrimMouseCoords(objectPrims.front().prim, active3dView, objectMouseCoords);

if (objectMouseCoords.x() > bottomRightMouseCoords.x()) {
bottomRightMouseCoords.setX(objectMouseCoords.x());
}
if (objectMouseCoords.x() < topLeftMouseCoords.x()) {
topLeftMouseCoords.setX(objectMouseCoords.x());
}
if (objectMouseCoords.y() > topLeftMouseCoords.y()) {
topLeftMouseCoords.setY(objectMouseCoords.y());
}
if (objectMouseCoords.y() < bottomRightMouseCoords.y()) {
bottomRightMouseCoords.setY(objectMouseCoords.y());
}
}

// Perform the marquee selection
mousePress(Qt::MouseButton::LeftButton, active3dView.widget(), topLeftMouseCoords);
mouseMoveTo(active3dView.widget(), bottomRightMouseCoords);
mouseRelease(Qt::MouseButton::LeftButton, active3dView.widget(), bottomRightMouseCoords);

active3dView.refresh();

for (const auto& object : objectsToSelect) {
ensureSelected(inspector, PrimNamePredicate(object.first));
}
}
Loading