From 584a73fbf036969a38d31cd6d143bfe1cba46130 Mon Sep 17 00:00:00 2001 From: debloip-adsk <145056365+debloip-adsk@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:30:51 -0400 Subject: [PATCH] HYDRA-843 : Add picking test + Link C++ tests with Qt for UI/viewport interaction (#94) * HYDRA-843 : Very rough working draft for picking + marquee * HYDRA-843 : Lead object inconsistency debugging * HYDRA-843 : Make marquee test based on data sources * HYDRA-843 : Make picking test based on data source * HYDRA-843 : Add tests for picking USD meshes copied from Maya and implicit surfaces * HYDRA-843 : Refactor to pick different prim type * HYDRA-843 : Add test to pick Maya light * HYDRA-843 : Add test to pick USD light * HYDRA-843 : Partial refactor in preparation for marquee test * HYDRA-843 : Add translations to USD objects * HYDRA-843 : Working marquee selection test * HYDRA-843 : Rename variables * HYDRA-843 : Mini-refactor of getting Maya object names * HYDRA-843 : Add comments to marquee cpp test * HYDRA-843 : Move mouse utility methods to testUtils * HYDRA-843 : Avoid force-moving cursor * HYDRA-843 : Remove unused changes * HYDRA-843 : Remove unused includes * HYDRA-843 : Adjust Qt CMake setup * HYDRA-843 : Add comments for Qt mouse & keyboard button vars * HYDRA-843 : Use int for iArg to fix warning on Linux * HYDRA-843 : Attempt to revert QT_NO_KEYWORDS to be on each target * HYDRA-843 : Attempt to fix macro redefinition * Revert "HYDRA-843 : Attempt to revert QT_NO_KEYWORDS to be on each target" This reverts commit 9254150a03cf260d251f0456269ac9590b9f56bd. * Revert "HYDRA-843 : Attempt to fix macro redefinition" This reverts commit 910bcb8f6e650162b84d44fa457c5daa831fd551. * HYDRA_843 : Add _USE_MATH_DEFINES definition in cpp test plugin * HYDRA-843 : Attempt to fix previous commit * HYDRA-843 : Adjust comment * HYDRA-843 : Move _USE_MATH_DEFINES to global compiler config and add comment * HYDRA-843 : Adjust comment * HYDRA-843 : Move BOOST_DEBUG_PYTHON to MSVC_DEFINITIONS * HYDRA-843 : Add extra info on _USE_MATH_DEFINES problem * HYDRA-843 : Set QT_NO_KEYWORDS on a per-target basis --- cmake/compiler_config.cmake | 54 ++++- .../mayaUsd/render/mayaToHydra/CMakeLists.txt | 1 + .../render/mayaToHydra/cpp/CMakeLists.txt | 12 ++ .../render/mayaToHydra/cpp/testPicking.cpp | 196 ++++++++++++++++++ .../render/mayaToHydra/cpp/testPicking.py | 135 ++++++++++++ .../render/mayaToHydra/cpp/testUtils.cpp | 54 +++++ .../render/mayaToHydra/cpp/testUtils.h | 32 +++ 7 files changed, 477 insertions(+), 7 deletions(-) create mode 100644 test/lib/mayaUsd/render/mayaToHydra/cpp/testPicking.cpp create mode 100644 test/lib/mayaUsd/render/mayaToHydra/cpp/testPicking.py diff --git a/cmake/compiler_config.cmake b/cmake/compiler_config.cmake index 51379258b0..14beada8b8 100644 --- a/cmake/compiler_config.cmake +++ b/cmake/compiler_config.cmake @@ -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 + $<$:BOOST_DEBUG_PYTHON> + + # 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 or . + # 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 . + # 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 . + # 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 ) #------------------------------------------------------------------------------ @@ -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 - $<$:BOOST_DEBUG_PYTHON> ) endif() diff --git a/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt b/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt index e7aa16344c..153db72ff1 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt +++ b/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt @@ -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. diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt b/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt index 270b80abb3..a341433981 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt @@ -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) @@ -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} @@ -59,6 +69,8 @@ target_link_libraries(${TARGET_NAME} mayaHydraLib ${GTEST_LIBRARIES} flowViewport + Qt6::Core + Qt6::Widgets ) # ----------------------------------------------------------------------------- diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testPicking.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPicking.cpp new file mode 100644 index 0000000000..9826b2bb74 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPicking.cpp @@ -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 +#include +#include + +#include +#include + +#include + +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> 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)); + } +} diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testPicking.py b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPicking.py new file mode 100644 index 0000000000..32bc3647c6 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPicking.py @@ -0,0 +1,135 @@ +# 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 mayaUtils +import mtohUtils +import unittest + +from testUtils import PluginLoaded + +class TestPicking(mtohUtils.MtohTestCase): + # MayaHydraBaseTestCase.setUpClass requirement. + _file = __file__ + + def setUp(self): + super(TestPicking, self).setUp() + self.setHdStormRenderer() + cmds.refresh() + + def createMayaCube(self): + objectName = cmds.polyCube()[0] + cmds.move(1, 2, 3) + cmds.select(clear=True) + cmds.refresh() + return objectName + + def createMayaDirectionalLight(self): + shapeName = cmds.directionalLight() + objectName = cmds.listRelatives(shapeName, parent=True) + cmds.move(3, -2, 1) + cmds.select(clear=True) + cmds.modelEditor(mayaUtils.activeModelPanel(), edit=True, displayLights='all') + cmds.refresh() + return objectName + + def createUsdCubeFromMaya(self, stagePath): + objectName = cmds.polyCube()[0] + cmds.move(-4, 3, -2) + cmds.mayaUsdDuplicate(cmds.ls(objectName, long=True)[0], stagePath) + cmds.delete(objectName) + cmds.select(clear=True) + cmds.refresh() + return objectName + + def createUsdCube(self, stagePath): + import mayaUsd.lib + from pxr import UsdGeom + objectName = "USDCube" + stage = mayaUsd.lib.GetPrim(stagePath).GetStage() + xform = UsdGeom.Xform.Define(stage, "/" + objectName + "Xform") + xform.AddTranslateOp().Set(value=(6, 5, 4)) + UsdGeom.Cube.Define(stage, str(xform.GetPath()) + "/" + objectName) + cmds.select(clear=True) + cmds.refresh() + return objectName + + def createUsdRectLight(self, stagePath): + import mayaUsd.lib + from pxr import UsdGeom, UsdLux + objectName = "USDRectLight" + stage = mayaUsd.lib.GetPrim(stagePath).GetStage() + xform = UsdGeom.Xform.Define(stage, "/" + objectName + "Xform") + xform.AddTranslateOp().Set(value=(-6, -3.5, -1)) + UsdLux.RectLight.Define(stage, str(xform.GetPath()) + "/" + objectName) + cmds.select(clear=True) + cmds.modelEditor(mayaUtils.activeModelPanel(), edit=True, displayLights='all') + cmds.refresh() + return objectName + + def test_PickMayaMesh(self): + cubeObjectName = self.createMayaCube() + with PluginLoaded('mayaHydraCppTests'): + cmds.mayaHydraCppTest(cubeObjectName, "mesh", f="TestPicking.pickObject") + + def test_PickMayaLight(self): + directionalLightObjectName = self.createMayaDirectionalLight() + with PluginLoaded('mayaHydraCppTests'): + cmds.mayaHydraCppTest(directionalLightObjectName, "simpleLight", f="TestPicking.pickObject") + + @unittest.skipUnless(mtohUtils.checkForMayaUsdPlugin(), "Requires Maya USD Plugin.") + def test_PickUsdMesh(self): + import mayaUsd_createStageWithNewLayer + stagePath = mayaUsd_createStageWithNewLayer.createStageWithNewLayer() + cubeObjectName = self.createUsdCubeFromMaya(stagePath) + with PluginLoaded('mayaHydraCppTests'): + cmds.mayaHydraCppTest(cubeObjectName, "mesh", f="TestPicking.pickObject") + + @unittest.skipUnless(mtohUtils.checkForMayaUsdPlugin(), "Requires Maya USD Plugin.") + def test_PickUsdImplicitSurface(self): + import mayaUsd_createStageWithNewLayer + stagePath = mayaUsd_createStageWithNewLayer.createStageWithNewLayer() + cubeObjectName = self.createUsdCube(stagePath) + with PluginLoaded('mayaHydraCppTests'): + cmds.mayaHydraCppTest(cubeObjectName, "mesh", f="TestPicking.pickObject") + + @unittest.skipUnless(mtohUtils.checkForMayaUsdPlugin(), "Requires Maya USD Plugin.") + def test_PickUsdLight(self): + import mayaUsd_createStageWithNewLayer + stagePath = mayaUsd_createStageWithNewLayer.createStageWithNewLayer() + rectLightObjectName = self.createUsdRectLight(stagePath) + with PluginLoaded('mayaHydraCppTests'): + cmds.mayaHydraCppTest(rectLightObjectName, "rectLight", f="TestPicking.pickObject") + + @unittest.skipUnless(mtohUtils.checkForMayaUsdPlugin(), "Requires Maya USD Plugin.") + def test_MarqueeSelection(self): + import mayaUsd_createStageWithNewLayer + stagePath = mayaUsd_createStageWithNewLayer.createStageWithNewLayer() + mayaCubeName = self.createMayaCube() + mayaDirectionalLightName = self.createMayaDirectionalLight() + usdMayaCubeName = self.createUsdCubeFromMaya(stagePath) + usdCubeName = self.createUsdCube(stagePath) + usdRectLightName = self.createUsdRectLight(stagePath) + with PluginLoaded('mayaHydraCppTests'): + cmds.mayaHydraCppTest( + mayaCubeName, "mesh", + mayaDirectionalLightName, "simpleLight", + usdMayaCubeName, "mesh", + usdCubeName, "mesh", + usdRectLightName, "rectLight", + f="TestPicking.marqueeSelect") + +if __name__ == '__main__': + fixturesUtils.runTests(globals()) diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.cpp index d06cf2ed07..d8ab4998d8 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.cpp +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.cpp @@ -27,10 +27,19 @@ #include #include +#include + #include namespace { std::pair testingArgs{0, nullptr}; + +// Store the ongoing state of the pressed moused & keyboard buttons. +// These are normally kept track of internally by Qt and can be retrieved using +// methods of the same name. But since we are sending artificial events, Qt does +// not get the opportunity to set these, so we keep track of them manually here. +Qt::MouseButtons mouseButtons; +Qt::KeyboardModifiers keyboardModifiers; } PXR_NAMESPACE_OPEN_SCOPE @@ -258,4 +267,49 @@ std::pair getTestingArgs() return testingArgs; } +void mouseMoveTo(QWidget* widget, QPoint localMousePos) +{ + QMouseEvent mouseMoveEvent( + QEvent::Type::MouseMove, + localMousePos, + widget->mapToGlobal(localMousePos), + Qt::MouseButton::NoButton, + mouseButtons, + keyboardModifiers); + + QApplication::sendEvent(widget, &mouseMoveEvent); +} + +void mousePress(Qt::MouseButton mouseButton, QWidget* widget, QPoint localMousePos) +{ + QMouseEvent mousePressEvent( + QEvent::Type::MouseButtonPress, + localMousePos, + widget->mapToGlobal(localMousePos), + mouseButton, + mouseButtons, + keyboardModifiers); + + // Update mouse state + mouseButtons |= mouseButton; + + QApplication::sendEvent(widget, &mousePressEvent); +} + +void mouseRelease(Qt::MouseButton mouseButton, QWidget* widget, QPoint localMousePos) +{ + // Update mouse state + mouseButtons &= ~mouseButton; + + QMouseEvent mouseReleaseEvent( + QEvent::Type::MouseButtonRelease, + localMousePos, + widget->mapToGlobal(localMousePos), + mouseButton, + mouseButtons, + keyboardModifiers); + + QApplication::sendEvent(widget, &mouseReleaseEvent); +} + } diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.h b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.h index f24fe62cab..5cc0bb2f0a 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.h +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.h @@ -26,6 +26,9 @@ #include #include +#include +#include + #include #include #include @@ -311,6 +314,35 @@ void setTestingArgs(int argc, char** argv); */ std::pair getTestingArgs(); +/** + * @brief Send a mouse move event to a widget to move the mouse at a given position. + * + * @param[in] widget The widget to send the event to. + * @param[in] localMousePos The position to move the mouse to, relative to the widget. + * + */ +void mouseMoveTo(QWidget* widget, QPoint localMousePos); + +/** + * @brief Send a mouse press event to a widget to press a mouse button at a given position. + * + * @param[in] mouseButton The mouse button to press. + * @param[in] widget The widget to send the event to. + * @param[in] localMousePos The position of the mouse, relative to the widget. + * + */ +void mousePress(Qt::MouseButton mouseButton, QWidget* widget, QPoint localMousePos); + +/** + * @brief Send a mouse release event to a widget to release a mouse button at a given position. + * + * @param[in] mouseButton The mouse button to release. + * @param[in] widget The widget to send the event to. + * @param[in] localMousePos The position of the mouse, relative to the widget. + * + */ +void mouseRelease(Qt::MouseButton mouseButton, QWidget* widget, QPoint localMousePos); + } #endif // MAYAHYDRA_TEST_UTILS_H