diff --git a/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.cpp b/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.cpp index 0dbd529cd..43d26be45 100644 --- a/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.cpp +++ b/lib/flowViewport/sceneIndex/fvpSelectionSceneIndex.cpp @@ -76,7 +76,7 @@ class _PrimSource : public HdContainerDataSource TfTokenVector GetNames() override { - TfTokenVector names = _inputSource->GetNames(); + TfTokenVector names = _inputSource ? _inputSource->GetNames() : TfTokenVector(); if (_selection->IsFullySelected(_primPath)) { names.push_back(HdSelectionsSchemaTokens->selections); } @@ -89,7 +89,7 @@ class _PrimSource : public HdContainerDataSource return _selection->GetVectorDataSource(_primPath); } - return _inputSource->Get(name); + return _inputSource ? _inputSource->Get(name) : nullptr; } private: @@ -132,7 +132,12 @@ SelectionSceneIndex::GetPrim(const SdfPath &primPath) const .Msg("SelectionSceneIndex::GetPrim() called.\n"); HdSceneIndexPrim result = GetInputSceneIndex()->GetPrim(primPath); - if (!result.dataSource) { + // An empty data source can be taken to mean an invalid prim, but it can + // also be an ancestor in a path to an actual prim (see + // https://forum.aousd.org/t/representation-of-invalid-hdsceneindexprim/833/2 + // ). Such an ancestor prim can be selected, even if typeless, so add + // a selection data source in such a case. + if (!result.dataSource && !_selection->IsFullySelected(primPath)) { return result; } diff --git a/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt b/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt index 73b082e08..f044f3d98 100644 --- a/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt +++ b/test/lib/mayaUsd/render/mayaToHydra/CMakeLists.txt @@ -15,6 +15,7 @@ set(INTERACTIVE_TEST_SCRIPT_FILES testNamespaces.py testVisibility.py testRendererSwitching.py + testSelectPrimWithoutDataSource.py testStageAddPrim.py testTransforms.py testRefinement.py diff --git a/test/lib/mayaUsd/render/mayaToHydra/testSelectPrimWithoutDataSource.py b/test/lib/mayaUsd/render/mayaToHydra/testSelectPrimWithoutDataSource.py new file mode 100644 index 000000000..b4073d876 --- /dev/null +++ b/test/lib/mayaUsd/render/mayaToHydra/testSelectPrimWithoutDataSource.py @@ -0,0 +1,58 @@ +# 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 testUtils +import usdUtils + +class TestSelectPrimWithoutDataSource(mtohUtils.MayaHydraBaseTestCase): + # MayaHydraBaseTestCase.setUpClass requirement. + _file = __file__ + + def test_selectPrimWithoutDataSource(self): + + # Importing mayaUsd or mayaUsd.lib at module scope produces an "unbound + # local" error on use, so import in local scope. May be tied to + # point at which mayaUsd plugin gets loaded into Maya. + import mayaUsd.lib + + # Prims without a data source are sometimes used to flag illegal prim + # return values, but such prims can also be legitimately used as parent + # prims for children prims that do have data sources. As such a prim + # without a data source must be selectable. + + # Read in a scene that creates a legal Hydra parent prim without a data + # source. + usdScenePath = testUtils.getTestScene('testSelectPrimWithoutDataSource', 'root.usda') + + self.proxyShapePathStr = usdUtils.createStageFromFile(usdScenePath) + + stage = mayaUsd.lib.GetPrim(self.proxyShapePathStr).GetStage() + + self.assertIsNotNone(stage) + + # Initially select the proxy shape. + cmds.select('|root|rootShape') + cmds.refresh() + + # Switch selection to a USD prim that generates a Hydra prim without + # a data source, but with children. This must not crash. + cmds.select('|root|rootShape,/root/refAssetB') + cmds.refresh() + +if __name__ == '__main__': + fixturesUtils.runTests(globals()) diff --git a/test/testSamples/testSelectPrimWithoutDataSource/cube.usda b/test/testSamples/testSelectPrimWithoutDataSource/cube.usda new file mode 100644 index 000000000..a3988272f --- /dev/null +++ b/test/testSamples/testSelectPrimWithoutDataSource/cube.usda @@ -0,0 +1,25 @@ +#usda 1.0 +( + defaultPrim = "cube" +) + +def Xform "cube" ( + prepend apiSchemas = ["GeomModelAPI"] + kind = "component" +) +{ + uniform bool model:applyDrawMode = 1 + asset model:cardTextureXNeg = @./red20x20.png@ + asset model:cardTextureXPos = @./red20x20.png@ + asset model:cardTextureYNeg = @./red20x20.png@ + asset model:cardTextureYPos = @./red20x20.png@ + asset model:cardTextureZNeg = @./red20x20.png@ + asset model:cardTextureZPos = @./red20x20.png@ + + def Mesh "cubeMesh" + { + 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)] + } +} diff --git a/test/testSamples/testSelectPrimWithoutDataSource/red20x20.png b/test/testSamples/testSelectPrimWithoutDataSource/red20x20.png new file mode 100644 index 000000000..3ebbffb97 Binary files /dev/null and b/test/testSamples/testSelectPrimWithoutDataSource/red20x20.png differ diff --git a/test/testSamples/testSelectPrimWithoutDataSource/root.usda b/test/testSamples/testSelectPrimWithoutDataSource/root.usda new file mode 100644 index 000000000..b842679fa --- /dev/null +++ b/test/testSamples/testSelectPrimWithoutDataSource/root.usda @@ -0,0 +1,29 @@ +#usda 1.0 +( + defaultPrim = "root" +) + +def Xform "root" ( + kind = "assembly" +) +{ + def Xform "refAssetA" ( + references = @./cube.usda@ + kind = "group" + ) + { + double3 xformOp:rotateXYZ = (-90, 0, 0) + double3 xformOp:translate = (-5, 0, 0) + uniform token[] xformOpOrder = ["xformOp:rotateXYZ", "xformOp:translate"] + } + def Xform "refAssetB" ( + references = @./cube.usda@ + kind = "group" + ) + { + uniform token model:drawMode = "cards" + double3 xformOp:rotateXYZ = (-90, 0, 0) + double3 xformOp:translate = (5, 0, 0) + uniform token[] xformOpOrder = ["xformOp:rotateXYZ", "xformOp:translate"] + } +}