From f1c295d83453d08a3bd650d70155b8b1392bfe80 Mon Sep 17 00:00:00 2001 From: ppt-adsk Date: Mon, 25 Mar 2024 09:17:10 -0400 Subject: [PATCH] Point instance picking support (no selection highlighting) (#105) * Point instance picking support (no selection highlighting) * Account for multiple pick hits in point instance pick test. --- .../sceneIndex/mayaHydraSceneIndex.cpp | 2 +- lib/mayaHydra/mayaPlugin/renderOverride.cpp | 383 ++++++++++++++++-- lib/mayaHydra/mayaPlugin/renderOverride.h | 18 + .../mayaUsd/render/mayaToHydra/CMakeLists.txt | 1 + .../render/mayaToHydra/cpp/CMakeLists.txt | 1 + .../render/mayaToHydra/cpp/testPicking.cpp | 34 +- .../cpp/testPointInstancePicking.cpp | 73 ++++ .../cpp/testPointInstancePicking.py | 116 ++++++ .../render/mayaToHydra/cpp/testUtils.cpp | 56 ++- .../render/mayaToHydra/cpp/testUtils.h | 42 +- .../testPointInstances/CubeModel.usda | 191 +++++++++ .../testPointInstances/PyramidModel.usda | 109 +++++ .../nestedPointInstancers.usda | 186 +++++++++ 13 files changed, 1135 insertions(+), 77 deletions(-) create mode 100644 test/lib/mayaUsd/render/mayaToHydra/cpp/testPointInstancePicking.cpp create mode 100644 test/lib/mayaUsd/render/mayaToHydra/cpp/testPointInstancePicking.py create mode 100644 test/testSamples/testPointInstances/CubeModel.usda create mode 100644 test/testSamples/testPointInstances/PyramidModel.usda create mode 100644 test/testSamples/testPointInstances/nestedPointInstancers.usda diff --git a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.cpp b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.cpp index 4cf8789f16..7f8d31c8a1 100644 --- a/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.cpp +++ b/lib/mayaHydra/hydraExtensions/sceneIndex/mayaHydraSceneIndex.cpp @@ -648,7 +648,7 @@ SdfPath MayaHydraSceneIndex::SetCameraViewport(const MDagPath& camPath, const Gf bool MayaHydraSceneIndex::AddPickHitToSelectionList( const HdxPickHit& hit, - const MHWRender::MSelectionInfo& selectInfo, + const MHWRender::MSelectionInfo& /* selectInfo */, MSelectionList& selectionList, MPointArray& worldSpaceHitPts) { diff --git a/lib/mayaHydra/mayaPlugin/renderOverride.cpp b/lib/mayaHydra/mayaPlugin/renderOverride.cpp index b593466db7..78879dacc6 100644 --- a/lib/mayaHydra/mayaPlugin/renderOverride.cpp +++ b/lib/mayaHydra/mayaPlugin/renderOverride.cpp @@ -52,8 +52,11 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -106,6 +109,216 @@ int _profilerCategory = MProfiler::addCategory( "MtohRenderOverride (mayaHydra)", "Events from mayaHydra render override"); +PXR_NAMESPACE_OPEN_SCOPE + +// Copy-pasted and adapted from maya-usd's +// https://github.com/Autodesk/maya-usd/blob/dev/lib/mayaUsd/base/tokens.h +// https://github.com/Autodesk/maya-usd/blob/dev/lib/mayaUsd/base/tokens.cpp + +// Tokens that are used as picking optionVars in MayaUSD +// +// clang-format off +#define MAYAUSD_PICK_OPTIONVAR_TOKENS \ + /* The kind to be selected when viewport picking. */ \ + /* After resolving the picked prim, a search from */ \ + /* that prim up the USD namespace hierarchy will */ \ + /* be performed looking for a prim that matches */ \ + /* the kind in the optionVar. If no prim matches, */ \ + /* or if the selection kind is unspecified or */ \ + /* empty, the exact prim picked in the viewport */ \ + /* is selected. */ \ + ((SelectionKind, "mayaUsd_SelectionKind")) \ + /* The method to use to resolve viewport picking */ \ + /* when the picked object is a point instance. */ \ + /* The default behavior is "PointInstancer" which */ \ + /* will resolve to the PointInstancer prim that */ \ + /* generated the point instance. The optionVar */ \ + /* can also be set to "Instances" which will */ \ + /* resolve to individual point instances, or to */ \ + /* "Prototypes" which will resolve to the prim */ \ + /* that is being instanced by the point instance. */ \ + ((PointInstancesPickMode, "mayaUsd_PointInstancesPickMode")) \ +// clang-format on + +TF_DEFINE_PRIVATE_TOKENS(MayaUsdPickOptionVars, MAYAUSD_PICK_OPTIONVAR_TOKENS); + +// Copy-pasted and adapted from maya-usd's +// https://github.com/Autodesk/maya-usd/blob/dev/lib/mayaUsd/render/vp2RenderDelegate/proxyRenderDelegate.h +// https://github.com/Autodesk/maya-usd/blob/dev/lib/mayaUsd/render/vp2RenderDelegate/proxyRenderDelegate.cpp + +// clang-format off +TF_DEFINE_PRIVATE_TOKENS( + _pointInstancesPickModeTokens, + + (PointInstancer) + (Instances) + (Prototypes) +); +// clang-format on + +PXR_NAMESPACE_CLOSE_SCOPE + +namespace { + +PXR_NAMESPACE_USING_DIRECTIVE + +//! Pick resolution behavior to use when the picked object is a point instance. +enum UsdPointInstancesPickMode +{ + //! The PointInstancer prim that generated the point instance is picked. If + //! multiple nested PointInstancers are involved, the top-level + //! PointInstancer is the one picked. If a selection kind is specified, the + //! traversal up the hierarchy looking for a kind match will begin at that + //! PointInstancer. + PointInstancer = 0, + //! The specific point instance is picked. These are represented as + //! UsdSceneItems with UFE paths to a PointInstancer prim and a non-negative + //! instanceIndex for the specific point instance. In this mode, any setting + //! for selection kind is ignored. + Instances, + //! The prototype being instanced by the point instance is picked. If a + //! selection kind is specified, the traversal up the hierarchy looking for + //! a kind match will begin at the prototype prim. + Prototypes +}; + +//! \brief Query the pick mode to use when picking point instances in the viewport. +//! \return A UsdPointInstancesPickMode enum value indicating the pick mode behavior +//! to employ when the picked object is a point instance. +//! +//! This function retrieves the value for the point instances pick mode optionVar +//! and converts it into a UsdPointInstancesPickMode enum value. If the optionVar +//! has not been set or otherwise has an invalid value, the default pick mode of +//! PointInstancer is returned. +UsdPointInstancesPickMode GetPointInstancesPickMode() +{ + static const MString kOptionVarName(MayaUsdPickOptionVars->PointInstancesPickMode.GetText()); + + auto pickMode = UsdPointInstancesPickMode::PointInstancer; + + if (MGlobal::optionVarExists(kOptionVarName)) { + const TfToken pickModeToken(MGlobal::optionVarStringValue(kOptionVarName).asChar()); + + if (pickModeToken == _pointInstancesPickModeTokens->Instances) { + pickMode = UsdPointInstancesPickMode::Instances; + } else if (pickModeToken == _pointInstancesPickModeTokens->Prototypes) { + pickMode = UsdPointInstancesPickMode::Prototypes; + } + } + + return pickMode; +} + +struct PickInput { + PickInput( + const HdxPickHit& pickHitArg, + const MHWRender::MSelectionInfo& pickInfoArg + ) : pickHit(pickHitArg), pickInfo(pickInfoArg) {} + + const HdxPickHit& pickHit; + const MHWRender::MSelectionInfo& pickInfo; +}; + +// Picking output can go either to the UFE representation of the Maya selection +// (which supports non-Maya objects), or the classic MSelectionList +// representation of the Maya selection (which only supports Maya objects). It +// is up to the implementer of the pick handler to decide which is used. If the +// Maya selection is used, there must be a world space hit point in one to one +// correspondence with each Maya selection item placed into the MSelectionList. +struct PickOutput { + PickOutput( + MSelectionList& mayaSn, + MPointArray& worldSpaceHitPts, + const Ufe::NamedSelection::Ptr& ufeSn + ) : mayaSelection(mayaSn), mayaWorldSpaceHitPts(worldSpaceHitPts), + ufeSelection(ufeSn) {} + + MSelectionList& mayaSelection; + MPointArray& mayaWorldSpaceHitPts; + const Ufe::NamedSelection::Ptr& ufeSelection; +}; + +// The SdfPath is in the original data model scene (USD), not in +// the scene index scene. +using HitPath = std::tuple; + +SdfPath instancerPrimOrigin(const HdxInstancerContext& instancerContext) +{ + // When USD prims are converted to Hydra prims (including point instancers), + // they are given a prim origin data source which provides the information + // as to which prim in the USD data model produced the rprim in the Hydra + // scene index scene. This is what is used here to provide the Hydra scene + // path to USD scene path picking to selection mapping. + auto schema = HdPrimOriginSchema(instancerContext.instancerPrimOrigin); + if (!schema) { + return {}; + } + + return schema.GetOriginPath(HdPrimOriginSchemaTokens->scenePath); +} + +HitPath pickInstance( + const HdxPrimOriginInfo& primOrigin, const HdxPickHit& hit +) +{ + // We match VP2 behavior and return the instance on the top-level instancer. + const auto& instancerContext = primOrigin.instancerContexts.front(); + return { + instancerPrimOrigin(instancerContext), instancerContext.instanceId}; +} + +HitPath pickPrototype( + const HdxPrimOriginInfo& primOrigin, const HdxPickHit& hit +) +{ + // The prototype path is the prim origin path in the USD data model. + return {primOrigin.GetFullPath(), -1}; +} + +HitPath pickInstancer( + const HdxPrimOriginInfo& primOrigin, const HdxPickHit& hit +) +{ + // To return the top-level instancer, we use the first instancer context + // prim origin. To return the innermost instancer, we would use the last + // instancer context prim origin. + return {instancerPrimOrigin(primOrigin.instancerContexts.front()), -1}; +} + +} + +PXR_NAMESPACE_OPEN_SCOPE + +class MtohRenderOverride::PickHandlerBase { +public: + + virtual bool handlePickHit( + const PickInput& pickInput, PickOutput& pickOutput + ) const = 0; + +protected: + + PickHandlerBase(MtohRenderOverride& renderOverride) : + _renderOverride(renderOverride) {} + + MayaHydraSceneIndexRefPtr mayaSceneIndex() const { + return _renderOverride._mayaHydraSceneIndex; + } + + std::shared_ptr + sceneIndexRegistry() const { + return _renderOverride._sceneIndexRegistry; + } + + HdRenderIndex* renderIndex() const { return _renderOverride._renderIndex; } + +private: + + MtohRenderOverride& _renderOverride; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + namespace { // Replace the builtin and fixed colorize selection and selection tasks from @@ -135,6 +348,103 @@ void replaceSelectionTask(PXR_NS::HdTaskSharedPtrVector* tasks) *found = HdTaskSharedPtr(new Fvp::SelectionTask); } +class MayaPickHandler : public MtohRenderOverride::PickHandlerBase { +public: + + MayaPickHandler(MtohRenderOverride& renderOverride) : + PickHandlerBase(renderOverride) {} + + bool handlePickHit( + const PickInput& pickInput, PickOutput& pickOutput + ) const override + { + if (!mayaSceneIndex()) { + TF_FATAL_ERROR("Picking called while no Maya scene index exists"); + return false; + } + + // Maya does not create Hydra instances, so if the pick hit instancer + // ID isn't empty, it's not a Maya pick hit. + if (!pickInput.pickHit.instancerId.IsEmpty()) { + return false; + } + + return mayaSceneIndex()->AddPickHitToSelectionList( + pickInput.pickHit, pickInput.pickInfo, + pickOutput.mayaSelection, pickOutput.mayaWorldSpaceHitPts + ); + } +}; + +class UsdPickHandler : public MtohRenderOverride::PickHandlerBase { +public: + + UsdPickHandler(MtohRenderOverride& renderOverride) : + PickHandlerBase(renderOverride) {} + + // Return the closest path and the instance index in the scene index scene + // that corresponds to the pick hit. If the pick hit is not an instance, + // the instance index will be -1. + HitPath hitPath(const HdxPickHit& hit) const { + auto primOrigin = HdxPrimOriginInfo::FromPickHit( + renderIndex(), hit); + + if (hit.instancerId.IsEmpty()) { + return {primOrigin.GetFullPath(), -1}; + } + + std::function pickFn[] = {pickInstancer, pickInstance, pickPrototype}; + + // Retrieve pick mode from mayaUsd optionVar, to see if we're picking + // instances, the instancer itself, or the prototype instanced by the + // point instance. + return pickFn[GetPointInstancesPickMode()](primOrigin, hit); + } + + bool handlePickHit( + const PickInput& pickInput, PickOutput& pickOutput + ) const override + { + if (!sceneIndexRegistry()) { + TF_FATAL_ERROR("Picking called while no scene index registry exists"); + return false; + } + + if (!renderIndex()) { + TF_FATAL_ERROR("Picking called while no render index exists"); + return false; + } + + auto registration = sceneIndexRegistry()->GetSceneIndexRegistrationForRprim(pickInput.pickHit.objectId); + + if (!registration) { + return false; + } + + // For the USD pick handler pick results are directly returned with USD + // scene paths, so no need to remove scene index plugin path prefix. + const auto& [pickedPath, instanceNdx] = hitPath(pickInput.pickHit); + Ufe::Path interpretedPath(registration->interpretRprimPathFn( + registration->pluginSceneIndex, pickedPath)); + + // Appending a numeric component to the path to identify a point + // instance cannot be done on the picked SdfPath, as numeric path + // components are not allowed by SdfPath. Do so here with Ufe::Path, + // which has no such restriction. + if (instanceNdx >= 0) { + interpretedPath = interpretedPath + std::to_string(instanceNdx); + } + + auto si = Ufe::Hierarchy::createItem(interpretedPath); + if (!si) { + return false; + } + + pickOutput.ufeSelection->append(si); + return true; + } +}; + // We compare the current and previous viewport display style and // return true if we need to recreate the filtering scene indices chain because of a change, false otherwise. bool NeedToRecreateTheSceneIndicesChain(unsigned int currentDisplayStyle, unsigned int previousDisplayStyle) @@ -236,8 +546,16 @@ MtohRenderOverride::MtohRenderOverride(const MtohRendererDescription& desc) , _hgi(Hgi::CreatePlatformDefaultHgi()) , _hgiDriver { HgiTokens->renderDriver, VtValue(_hgi.get()) } , _fvpSelectionTracker(new Fvp::SelectionTracker) + , _ufeSn(Ufe::NamedSelection::get("MayaSelectTool")) , _mayaSelectionObserver(std::make_shared(*this)) , _isUsingHdSt(desc.rendererName == MtohTokens->HdStormRendererPlugin) + // unique_ptr is not copyable, so can't use initializer_list, which copies. + , _pickHandlers([this](){ + std::vector> v; + v.push_back(std::make_unique(*this)); + v.push_back(std::make_unique(*this)); + return v; + }()) { TF_DEBUG(MAYAHYDRALIB_RENDEROVERRIDE_RESOURCES) .Msg( @@ -1177,62 +1495,39 @@ bool MtohRenderOverride::nextRenderOperation() return ++_currentOperation < static_cast(_operations.size()); } -constexpr char kNamedSelection[] = "MayaSelectTool"; - void MtohRenderOverride::_PopulateSelectionList( const HdxPickHitVector& hits, const MHWRender::MSelectionInfo& selectInfo, MSelectionList& selectionList, MPointArray& worldSpaceHitPts) { - if (hits.empty()) + if (hits.empty() || !_mayaHydraSceneIndex || !_ufeSn) { return; + } - if (_mayaHydraSceneIndex) { + PickOutput pickOutput(selectionList, worldSpaceHitPts, _ufeSn); - MStatus status; - if (auto ufeSel = Ufe::NamedSelection::get(kNamedSelection)) { - for (const HdxPickHit& hit : hits) { - if (_mayaHydraSceneIndex->AddPickHitToSelectionList( - hit, selectInfo, selectionList, worldSpaceHitPts)) { - continue; - } - SdfPath pickedPath = hit.objectId; - if (auto registration - = _sceneIndexRegistry->GetSceneIndexRegistrationForRprim(pickedPath)) - // Scene index is incompatible with UFE. Skip - { - // Keep the path after the scene index plugin path prefix to obtain local picked path with - // respect to current scene index. This is because the scene index was inserted - // into the render index using a custom prefix. As a result the scene index - // prefix will be prepended to rprims tied to that scene index automatically. - const SdfPath& sceneIndexPathPrefix = registration->sceneIndexPathPrefix; - if ( ! pickedPath.HasPrefix(sceneIndexPathPrefix)){ - TF_CODING_ERROR("pickedPathAsString.find(sceneIndexPathPrefixAsString) returned std::string::npos !"); - return; - } - const SdfPath relativePath = pickedPath.MakeRelativePath(sceneIndexPathPrefix); - - Ufe::Path interpretedPath(registration->interpretRprimPathFn( - registration->pluginSceneIndex, relativePath)); - - // If this is a maya UFE path, then select using MSelectionList - // This is because the NamedSelection ignores Ufe items made from maya ufe path - if (interpretedPath.runTimeId() == UfeExtensions::getMayaRunTimeId()) { - selectionList.add(UfeExtensions::ufeToDagPath(interpretedPath)); - worldSpaceHitPts.append( - hit.worldSpaceHitPoint[0], - hit.worldSpaceHitPoint[1], - hit.worldSpaceHitPoint[2]); - } else if (auto si = Ufe::Hierarchy::createItem(interpretedPath)) { - ufeSel->append(si); - } - } - } - } + MStatus status; + for (const HdxPickHit& hit : hits) { + PickInput pickInput(hit, selectInfo); + + _PickHandler(hit)->handlePickHit(pickInput, pickOutput); } } +const MtohRenderOverride::PickHandlerBase* +MtohRenderOverride::_PickHandler(const HdxPickHit& pickHit) const +{ + // As of 19-Mar-2024, we only have two kinds of pick handlers, one for Maya + // objects, the other for USD objects. USD objects are generated by + // mayaUsd proxy shape Maya nodes, which add one registration to the + // MayaHydraSceneIndexRegistry per proxy shape node. We use the trivial + // strategy of choosing the USD pick handler if there is a registration + // that matches the pick hit, otherwise the Maya pick handler is used. + // This will need to be revised for extensibility. + return _sceneIndexRegistry->GetSceneIndexRegistrationForRprim(pickHit.objectId) ? _pickHandlers[1].get() : _pickHandlers[0].get(); +} + void MtohRenderOverride::_PickByRegion( HdxPickHitVector& outHits, const MMatrix& viewMatrix, diff --git a/lib/mayaHydra/mayaPlugin/renderOverride.h b/lib/mayaHydra/mayaPlugin/renderOverride.h index 4628a5cd2d..01760f5a52 100644 --- a/lib/mayaHydra/mayaPlugin/renderOverride.h +++ b/lib/mayaHydra/mayaPlugin/renderOverride.h @@ -64,10 +64,12 @@ #include #include #include +#include #include UFE_NS_DEF { class SelectionChanged; +class Selection; } PXR_NAMESPACE_OPEN_SCOPE @@ -85,6 +87,10 @@ using HdxPickHitVector = std::vector; class MtohRenderOverride : public MHWRender::MRenderOverride { public: + // Picking support. + class PickHandlerBase; + friend PickHandlerBase; + MtohRenderOverride(const MtohRendererDescription& desc); ~MtohRenderOverride() override; @@ -187,6 +193,10 @@ class MtohRenderOverride : public MHWRender::MRenderOverride void _AddPluginSelectionHighlighting(); + // Determine the pick handler which should handle a pick hit, to transform + // the pick hit into a selection. + const PickHandlerBase* _PickHandler(const HdxPickHit& hit) const; + // Callbacks static void _ClearHydraCallback(void* data); static void _TimerCallback(float, float, void* data); @@ -233,6 +243,11 @@ class MtohRenderOverride : public MHWRender::MRenderOverride Fvp::SelectionTrackerSharedPtr _fvpSelectionTracker; Fvp::SelectionSceneIndexRefPtr _selectionSceneIndex; Fvp::SelectionPtr _selection; + // Naming this identifier _ufeSelection clashes with UFE's selection.h + // include guard and produces + // "error C2351: obsolete C++ constructor initialization syntax" + // with Visual Studio 2022, in MtohRenderOverride::MtohRenderOverride(). + std::shared_ptr _ufeSn; class SelectionObserver; using SelectionObserverPtr = std::shared_ptr; SelectionObserverPtr _mayaSelectionObserver; @@ -267,6 +282,9 @@ class MtohRenderOverride : public MHWRender::MRenderOverride bool _initializationSucceeded = false; bool _hasDefaultLighting = false; unsigned int _oldDisplayStyle {0}; + + // Picking support. + const std::vector> _pickHandlers; }; PXR_NAMESPACE_CLOSE_SCOPE diff --git a/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt b/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt index 32d13afabb..e78c0aaa02 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt +++ b/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt @@ -46,6 +46,7 @@ set(INTERACTIVE_TEST_SCRIPT_FILES cpp/testSceneCorrectness.py cpp/testPrimInstancing.py cpp/testPicking.py + cpp/testPointInstancePicking.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 a341433981..9555bbd61d 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/CMakeLists.txt @@ -35,6 +35,7 @@ target_sources(${TARGET_NAME} testSceneCorrectness.cpp testPrimInstancing.cpp testPicking.cpp + testPointInstancePicking.cpp ) # ----------------------------------------------------------------------------- diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testPicking.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPicking.cpp index 9826b2bb74..ebc6feb61e 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/testPicking.cpp +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPicking.cpp @@ -39,28 +39,6 @@ FindPrimPredicate findPickPrimPredicate(const std::string& objectName, const TfT }; } -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 @@ -115,11 +93,9 @@ TEST(TestPicking, pickObject) M3dView active3dView = M3dView::active3dView(); - QPoint primMouseCoords; - getPrimMouseCoords(prims.front().prim, active3dView, primMouseCoords); + auto primMouseCoords = getPrimMouseCoords(prims.front().prim, active3dView); - mousePress(Qt::MouseButton::LeftButton, active3dView.widget(), primMouseCoords); - mouseRelease(Qt::MouseButton::LeftButton, active3dView.widget(), primMouseCoords); + mouseClick(Qt::MouseButton::LeftButton, active3dView.widget(), primMouseCoords); active3dView.refresh(); @@ -154,8 +130,7 @@ TEST(TestPicking, marqueeSelect) PrimEntriesVector initialPrimEntries = inspector.FindPrims( findPickPrimPredicate(objectsToSelect.front().first, objectsToSelect.front().second)); ASSERT_EQ(initialPrimEntries.size(), 1u); - QPoint initialMouseCoords; - getPrimMouseCoords(initialPrimEntries.front().prim, active3dView, initialMouseCoords); + auto initialMouseCoords = getPrimMouseCoords(initialPrimEntries.front().prim, active3dView); // Initialize the selection rectangle QPoint topLeftMouseCoords = initialMouseCoords; @@ -166,8 +141,7 @@ TEST(TestPicking, marqueeSelect) PrimEntriesVector objectPrims = inspector.FindPrims( findPickPrimPredicate(objectsToSelect[iObject].first, objectsToSelect[iObject].second)); ASSERT_EQ(objectPrims.size(), 1u); - QPoint objectMouseCoords; - getPrimMouseCoords(objectPrims.front().prim, active3dView, objectMouseCoords); + auto objectMouseCoords = getPrimMouseCoords(objectPrims.front().prim, active3dView); if (objectMouseCoords.x() > bottomRightMouseCoords.x()) { bottomRightMouseCoords.setX(objectMouseCoords.x()); diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testPointInstancePicking.cpp b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPointInstancePicking.cpp new file mode 100644 index 0000000000..6c9a60d824 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPointInstancePicking.cpp @@ -0,0 +1,73 @@ +// 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 + +PXR_NAMESPACE_USING_DIRECTIVE + +using namespace MayaHydra; + +TEST(TestPointInstancePicking, pickPointInstance) +{ + const auto& sceneIndices = GetTerminalSceneIndices(); + ASSERT_GT(sceneIndices.size(), 0u); + auto siRoot = sceneIndices.front(); + + auto [argc, argv] = getTestingArgs(); + ASSERT_EQ(argc, 2); + const Ufe::Path selected(Ufe::PathString::path(argv[0])); + const Ufe::Path marker(Ufe::PathString::path(argv[1])); + + // 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 selectionSi = findSelectionSceneIndexInTree(siRoot); + ASSERT_TRUE(selectionSi); + + const auto sceneIndexPath = selectionSi->SceneIndexPath(marker); + + ASSERT_FALSE(sceneIndexPath.IsEmpty()); + + const auto markerPrim = siRoot->GetPrim(sceneIndexPath); + ASSERT_TRUE(markerPrim.dataSource); + + M3dView active3dView = M3dView::active3dView(); + + const auto primMouseCoords = getPrimMouseCoords(markerPrim, active3dView); + + mouseClick(Qt::MouseButton::LeftButton, active3dView.widget(), primMouseCoords); + active3dView.refresh(); + + // 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)); +} diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testPointInstancePicking.py b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPointInstancePicking.py new file mode 100644 index 0000000000..d7213c8827 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testPointInstancePicking.py @@ -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. +# +import maya.cmds as cmds +import fixturesUtils +import mtohUtils +import unittest +import usdUtils + +import testUtils +from testUtils import PluginLoaded + +class TestPointInstancePicking(mtohUtils.MtohTestCase): + # MayaHydraBaseTestCase.setUpClass requirement. + _file = __file__ + + PICK_PATH = "|nestedPointInstancers|nestedPointInstancersShape,/Root" + + # The test picking framework expects rprims that have a (flattened) + # transform, to determine their projected mouse coordinates. Point + # instancers generate point instance geometry from prototypes, so we cannot + # directly obtain a flattened transform for them. To work around this, we + # add marker objects to the scene that are co-located with point instances + # we want to pick, and use the marker objects to determine the projected + # mouse coordinates. + def loadUsdScene(self): + usdScenePath = testUtils.getTestScene('testPointInstances', 'nestedPointInstancers.usda') + usdUtils.createStageFromFile(usdScenePath) + + def setUp(self): + super(TestPointInstancePicking, self).setUp() + self.loadUsdScene() + cmds.setAttr('persp.translate', 19.3, 13.7, 11.4, type='float3') + cmds.setAttr('persp.rotate', -33.4, 63.0, 0, type='float3') + cmds.refresh() + + @unittest.skipUnless(mtohUtils.checkForMayaUsdPlugin(), "Requires Maya USD Plugin.") + def test_PickPointInstancer(self): + with PluginLoaded('mayaHydraCppTests'): + + cmds.optionVar( + sv=('mayaUsd_PointInstancesPickMode', 'PointInstancer')) + + # In point instancer mode picking an instance will select the + # top-level point instancer, regardless of the picked instance. + # We therefore pass in two markers to pick, and the result should + # be the same top-level point instancer selection. + markers = ["/MarkerRedPyramidInstance3", "/MarkerBlueCubeInstance0"] + for marker in markers: + cmds.mayaHydraCppTest( + self.PICK_PATH + "/ParentPointInstancer", + self.PICK_PATH + marker, + f="TestPointInstancePicking.pickPointInstance") + + @unittest.skipUnless(mtohUtils.checkForMayaUsdPlugin(), "Requires Maya USD Plugin.") + def test_PickInstances(self): + with PluginLoaded('mayaHydraCppTests'): + + cmds.optionVar( + sv=('mayaUsd_PointInstancesPickMode', 'Instances')) + + # In instances mode picking an instance will select the + # top-level instance. + markers = ["/MarkerRedPyramidInstance3", + "/MarkerGreenPyramidInstance1", + "/MarkerBlueCubeInstance0", + "/MarkerYellowCubeInstance2"] + instances = ["/ParentPointInstancer/3", + "/ParentPointInstancer/1", + "/ParentPointInstancer/0", + "/ParentPointInstancer/2"] + + for (marker, instance) in zip(markers, instances): + cmds.mayaHydraCppTest( + self.PICK_PATH + instance, + self.PICK_PATH + marker, + f="TestPointInstancePicking.pickPointInstance") + + @unittest.skipUnless(mtohUtils.checkForMayaUsdPlugin(), "Requires Maya USD Plugin.") + def test_PickPrototypes(self): + with PluginLoaded('mayaHydraCppTests'): + + cmds.optionVar( + sv=('mayaUsd_PointInstancesPickMode', 'Prototypes')) + + # In prototypes mode picking an instance will select the prototype + # of the most nested instance. + markers = ["/MarkerRedPyramidInstance3", + "/MarkerGreenPyramidInstance1", + "/MarkerBlueCubeInstance0", + "/MarkerYellowCubeInstance2"] + prototypes = [ + "/ParentPointInstancer/prototypes/PyramidPointInstancerXform/PyramidPointInstancer/prototypes/RedPyramid/Geom/Pyramid", + "/ParentPointInstancer/prototypes/PyramidPointInstancerXform/PyramidPointInstancer/prototypes/GreenPyramid/Geom/Pyramid", + "/ParentPointInstancer/prototypes/CubePointInstancerXform/CubePointInstancer/prototypes/BlueCube/Geom/Cube", + "/ParentPointInstancer/prototypes/CubePointInstancerXform/CubePointInstancer/prototypes/YellowCube/Geom/Cube"] + + for (marker, prototype) in zip(markers, prototypes): + cmds.mayaHydraCppTest( + self.PICK_PATH + prototype, + self.PICK_PATH + marker, + f="TestPointInstancePicking.pickPointInstance") + +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 d8ab4998d8..a6909be725 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.cpp +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.cpp @@ -25,7 +25,11 @@ #include #include -#include +#include +#include +#include + +#include #include @@ -253,8 +257,21 @@ HdSceneIndexBaseRefPtr findSceneIndexInTree( return {}; } +Fvp::SelectionSceneIndexRefPtr findSelectionSceneIndexInTree( + const HdSceneIndexBaseRefPtr& sceneIndex +) +{ + auto isFvpSelectionSceneIndex = SceneIndexDisplayNamePred( + "Flow Viewport Selection Scene Index"); + auto selectionSiBase = findSceneIndexInTree( + sceneIndex, isFvpSelectionSceneIndex); + return TfDynamic_cast(selectionSiBase); +} + PXR_NAMESPACE_CLOSE_SCOPE +PXR_NAMESPACE_USING_DIRECTIVE + namespace MAYAHYDRA_NS_DEF { void setTestingArgs(int argc, char** argv) @@ -312,4 +329,41 @@ void mouseRelease(Qt::MouseButton mouseButton, QWidget* widget, QPoint localMous QApplication::sendEvent(widget, &mouseReleaseEvent); } +void mouseClick(Qt::MouseButton mouseButton, QWidget* widget, QPoint localMousePos) +{ + mousePress(mouseButton, widget, localMousePos); + mouseRelease(mouseButton, widget, localMousePos); +} + +QPoint getPrimMouseCoords(const HdSceneIndexPrim& prim, M3dView& view) +{ + HdDataSourceBaseHandle xformDataSource = HdContainerDataSource::Get(prim.dataSource, HdXformSchema::GetDefaultLocator()); + if (!xformDataSource) { + ADD_FAILURE() << "Scene index prim has no default locator data source, cannot get mouse coordinates for it."; + return {}; + } + HdContainerDataSourceHandle xformContainerDataSource = HdContainerDataSource::Cast(xformDataSource); + TF_AXIOM(xformContainerDataSource); + HdXformSchema xformSchema(xformContainerDataSource); + TF_AXIOM(xformSchema.GetMatrix()); + 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 + if (!view.worldToView(worldPosition, viewportX, viewportY, &worldToViewStatus)) { + ADD_FAILURE() << "point was clipped by world to view projection, cannot get mouse coordinates for scene index prim."; + return {}; + } + if (worldToViewStatus != MS::kSuccess) { + ADD_FAILURE() << "M3dView::worldToView() failed, cannot get mouse coordinates for scene index prim."; + return {}; + } + + // Qt and M3dView use opposite Y-coordinates + return QPoint(viewportX, view.portHeight() - viewportY); +} + } diff --git a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.h b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.h index 5cc0bb2f0a..5026cfc02b 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.h +++ b/test/lib/mayaUsd/render/mayaToHydra/cpp/testUtils.h @@ -19,12 +19,14 @@ #include +#include + #include #include #include -#include #include +#include #include #include @@ -239,6 +241,20 @@ HdSceneIndexBaseRefPtr findSceneIndexInTree( const std::function& predicate ); +/** + * @brief Find the selection scene index in the scene index tree. + * + * This is a convenience function that calls findSceneIndexInTree() with + * the appropriate predicate. + * + * @param[in] sceneIndex The root of the scene index tree to search. + * + * @return Selection scene index pointer if found, otherwise nullptr. + */ +Fvp::SelectionSceneIndexRefPtr findSelectionSceneIndexInTree( + const HdSceneIndexBaseRefPtr& sceneIndex +); + /** * @class A utility class to accumulate and read SceneIndex notifications sent by a SceneIndex. */ @@ -343,6 +359,30 @@ void mousePress(Qt::MouseButton mouseButton, QWidget* widget, QPoint localMouseP */ void mouseRelease(Qt::MouseButton mouseButton, QWidget* widget, QPoint localMousePos); +/** + * @brief Convenience function to send a mouse press / release event pair to a widget at a given position. + * + * @param[in] mouseButton The mouse button to press and release. + * @param[in] widget The widget to send the event to. + * @param[in] localMousePos The position of the mouse, relative to the widget. + * + */ +void mouseClick(Qt::MouseButton mouseButton, QWidget* widget, QPoint localMousePos); + +/** + * @brief Get the mouse coordinates for a scene index prim. + * + * This function will return the mouse coordinates for the scene index prim's + * local coordinate origin. Note that the view argument is not changed and is + * passed in by non const reference only because its interface is not + * const-correct. + * + * @param[in] prim The scene index prim for which mouse coordinates must be computed. + * @param[in] view The view for which mouse coordinates are returned. + * @return Mouse coordinates. + */ +QPoint getPrimMouseCoords(const PXR_NS::HdSceneIndexPrim& prim, M3dView& view); + } #endif // MAYAHYDRA_TEST_UTILS_H diff --git a/test/testSamples/testPointInstances/CubeModel.usda b/test/testSamples/testPointInstances/CubeModel.usda new file mode 100644 index 0000000000..36289c3015 --- /dev/null +++ b/test/testSamples/testPointInstances/CubeModel.usda @@ -0,0 +1,191 @@ +#usda 1.0 +( + defaultPrim = "CubeModel" + metersPerUnit = 0.01 + upAxis = "Z" +) + +def Xform "CubeModel" ( + assetInfo = { + asset identifier = @./CubeModel.usda@ + string name = "CubeModel" + } + kind = "component" + variants = { + string shadingVariant = "Red" + } + add variantSets = "shadingVariant" +) +{ + def Xform "Geom" + { + def Mesh "Cube" + { + 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)] + uniform token subdivisionScheme = "none" + } + } + variantSet "shadingVariant" = { + "Blue" { + over "Geom" + { + over "Cube" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + rel material:binding = + color3f[] primvars:displayColor = [(0, 0, 1)] + } + } + + } + "Green" { + over "Geom" + { + over "Cube" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + rel material:binding = + color3f[] primvars:displayColor = [(0, 1, 0)] + } + } + + } + "Indigo" { + over "Geom" + { + over "Cube" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + rel material:binding = + color3f[] primvars:displayColor = [(0.294, 0, 0.51)] + } + } + + } + "Orange" { + over "Geom" + { + over "Cube" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + rel material:binding = + color3f[] primvars:displayColor = [(1, 0.498, 0)] + } + } + + } + "Red" { + over "Geom" + { + over "Cube" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + rel material:binding = + color3f[] primvars:displayColor = [(1, 0, 0)] + } + } + + } + "Violet" { + over "Geom" + { + over "Cube" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + rel material:binding = + color3f[] primvars:displayColor = [(0.58, 0, 0.827)] + } + } + + } + "Yellow" { + over "Geom" + { + over "Cube" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + rel material:binding = + color3f[] primvars:displayColor = [(1, 1, 0)] + } + } + + } + } + + def Scope "Materials" + { + def Material "BlueMaterial" ( + prepend specializes = + ) + { + color3f inputs:diffuseColor = (0, 0, 1) + } + + def Material "BaseMaterial" + { + color3f inputs:diffuseColor = (0.18, 0.18, 0.18) + token outputs:surface.connect = + + def Shader "PreviewSurface" + { + uniform token info:id = "UsdPreviewSurface" + color3f inputs:diffuseColor.connect = + float inputs:roughness = 1 + token outputs:surface + } + } + + def Material "GreenMaterial" ( + prepend specializes = + ) + { + color3f inputs:diffuseColor = (0, 1, 0) + } + + def Material "IndigoMaterial" ( + prepend specializes = + ) + { + color3f inputs:diffuseColor = (0.294, 0, 0.51) + } + + def Material "OrangeMaterial" ( + prepend specializes = + ) + { + color3f inputs:diffuseColor = (1, 0.498, 0) + } + + def Material "RedMaterial" ( + prepend specializes = + ) + { + color3f inputs:diffuseColor = (1, 0, 0) + } + + def Material "VioletMaterial" ( + prepend specializes = + ) + { + color3f inputs:diffuseColor = (0.58, 0, 0.827) + } + + def Material "YellowMaterial" ( + prepend specializes = + ) + { + color3f inputs:diffuseColor = (1, 1, 0) + } + } +} + diff --git a/test/testSamples/testPointInstances/PyramidModel.usda b/test/testSamples/testPointInstances/PyramidModel.usda new file mode 100644 index 0000000000..ae32847b8e --- /dev/null +++ b/test/testSamples/testPointInstances/PyramidModel.usda @@ -0,0 +1,109 @@ +#usda 1.0 +( + defaultPrim = "PyramidModel" + metersPerUnit = 0.01 + upAxis = "Z" +) + +def Xform "PyramidModel" ( + assetInfo = { + asset identifier = @./PyramidModel.usda@ + string name = "PyramidModel" + } + kind = "component" + variants = { + string shadingVariant = "Red" + } + add variantSets = "shadingVariant" +) +{ + def Xform "Geom" + { + def Mesh "Pyramid" + { + float3[] extent = [(-0.70710677, -0.35355338, -0.70710677), (0.70710677, 0.35355338, 0.70710677)] + int[] faceVertexCounts = [4, 3, 3, 3, 3] + int[] faceVertexIndices = [0, 3, 2, 1, 0, 1, 4, 1, 2, 4, 2, 3, 4, 3, 0, 4] + point3f[] points = [(9.272585e-8, -0.35355338, -0.70710677), (-0.70710677, -0.35355338, -6.181724e-8), (-3.090862e-8, -0.35355338, 0.70710677), (0.70710677, -0.35355338, 0), (0, 0.35355338, 0)] + } + } + variantSet "shadingVariant" = { + "Blue" { + over "Geom" + { + over "Pyramid" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + rel material:binding = + color3f[] primvars:displayColor = [(0, 0, 1)] + } + } + + } + "Green" { + over "Geom" + { + over "Pyramid" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + rel material:binding = + color3f[] primvars:displayColor = [(0, 1, 0)] + } + } + + } + "Red" { + over "Geom" + { + over "Pyramid" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + rel material:binding = + color3f[] primvars:displayColor = [(1, 0, 0)] + } + } + + } + } + + def Scope "Materials" + { + def Material "BaseMaterial" + { + color3f inputs:diffuseColor = (0.18, 0.18, 0.18) + token outputs:surface.connect = + + def Shader "PreviewSurface" + { + uniform token info:id = "UsdPreviewSurface" + color3f inputs:diffuseColor.connect = + float inputs:roughness = 1 + token outputs:surface + } + } + + def Material "BlueMaterial" ( + prepend specializes = + ) + { + color3f inputs:diffuseColor = (0, 0, 1) + } + + def Material "GreenMaterial" ( + prepend specializes = + ) + { + color3f inputs:diffuseColor = (0, 1, 0) + } + + def Material "RedMaterial" ( + prepend specializes = + ) + { + color3f inputs:diffuseColor = (1, 0, 0) + } + } +} diff --git a/test/testSamples/testPointInstances/nestedPointInstancers.usda b/test/testSamples/testPointInstances/nestedPointInstancers.usda new file mode 100644 index 0000000000..1de4108c13 --- /dev/null +++ b/test/testSamples/testPointInstances/nestedPointInstancers.usda @@ -0,0 +1,186 @@ +#usda 1.0 +( + defaultPrim = "Root" + metersPerUnit = 0.01 + upAxis = "Z" +) + +def Xform "Root" +{ + def PointInstancer "ParentPointInstancer" + { + float3[] extent = [(-10, -10, -10), (10, 10, 10)] + point3f[] positions = [(0, 0, 0), (0, 0, 2), (0, 0, 4), (0, 0, 6)] + int[] protoIndices = [0, 1, 0, 1] + rel prototypes = [ + , + , + ] + + def "prototypes" + { + def Xform "CubePointInstancerXform" + { + def PointInstancer "CubePointInstancer" ( + kind = "subcomponent" + ) + { + float3[] extent = [(-5, -0.5, -0.5), (5, 2, 0.5)] + point3f[] positions = [(-4.5, 0, 0), (-3, 0, 0), (-1.5, 0, 0), (0, 0, 0), (1.5, 0, 0), (3, 0, 0), (4.5, 0, 0), (-4.5, 1.5, 0), (-3, 1.5, 0), (-1.5, 1.5, 0), (0, 1.5, 0), (1.5, 1.5, 0), (3, 1.5, 0), (4.5, 1.5, 0)] + int[] protoIndices = [0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6] + rel prototypes = [ + , + , + , + , + , + , + , + ] + + def "prototypes" ( + kind = "subcomponent" + ) + { + def "BlueCube" ( + prepend references = @./CubeModel.usda@ + variants = { + string shadingVariant = "Blue" + } + ) + { + } + + def "GreenCube" ( + prepend references = @./CubeModel.usda@ + variants = { + string shadingVariant = "Green" + } + ) + { + } + + def "IndigoCube" ( + prepend references = @./CubeModel.usda@ + variants = { + string shadingVariant = "Indigo" + } + ) + { + } + + def "OrangeCube" ( + prepend references = @./CubeModel.usda@ + variants = { + string shadingVariant = "Orange" + } + ) + { + } + + def "RedCube" ( + prepend references = @./CubeModel.usda@ + variants = { + string shadingVariant = "Red" + } + ) + { + } + + def "VioletCube" ( + prepend references = @./CubeModel.usda@ + variants = { + string shadingVariant = "Violet" + } + ) + { + } + + def "YellowCube" ( + prepend references = @./CubeModel.usda@ + variants = { + string shadingVariant = "Yellow" + } + ) + { + } + } + } + } + + def Xform "PyramidPointInstancerXform" + { + def PointInstancer "PyramidPointInstancer" ( + kind = "subcomponent" + ) + { + float3[] extent = [(-2, -0.36, -2), (2, 0.36, 2)] + point3f[] positions = [(-1, 0, 0.5), (-1, 0, -1), (0.5, 0, 0.5), (0.5, 0, -1)] + int[] protoIndices = [0, 1, 2, 0] + rel prototypes = [ + , + , + , + ] + + def "prototypes" ( + kind = "subcomponent" + ) + { + def "BluePyramid" ( + prepend references = @./PyramidModel.usda@ + variants = { + string shadingVariant = "Blue" + } + ) + { + } + + def "GreenPyramid" ( + prepend references = @./PyramidModel.usda@ + variants = { + string shadingVariant = "Green" + } + ) + { + } + + def "RedPyramid" ( + prepend references = @./PyramidModel.usda@ + variants = { + string shadingVariant = "Red" + } + ) + { + } + } + } + } + } + } + + def Xform "MarkerRedPyramidInstance3" + { + double3 xformOp:translate = (0.5, 0, 5) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Xform "MarkerGreenPyramidInstance1" + { + double3 xformOp:translate = (-1, 0, 1) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Xform "MarkerBlueCubeInstance0" + { + double3 xformOp:translate = (1.5, 1.5, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Xform "MarkerYellowCubeInstance2" + { + double3 xformOp:translate = (-1.5, 1.5, 4) + uniform token[] xformOpOrder = ["xformOp:translate"] + } +} +