From 6cc0a02b819f174b2f555d07f18677e60180cb2c Mon Sep 17 00:00:00 2001 From: Jerry Gamache Date: Tue, 4 Feb 2025 09:42:16 -0500 Subject: [PATCH 1/5] EMSUSD-1973 - Add LookdevXUsd unit tests - CapabilityHandler - Component connections --- cmake/modules/FindLookdevXUfe.cmake | 7 + test/lib/CMakeLists.txt | 3 + test/lib/ufe/CMakeLists.txt | 7 + test/lib/ufe/testLdxCapabilityHandler.py | 73 +++ test/lib/ufe/testLdxComponentConnections.py | 607 ++++++++++++++++++ .../test_component_connections.usda | 278 ++++++++ .../test_component_connections3.usda | 63 ++ 7 files changed, 1038 insertions(+) create mode 100644 test/lib/ufe/testLdxCapabilityHandler.py create mode 100644 test/lib/ufe/testLdxComponentConnections.py create mode 100644 test/testSamples/lookdevXUsd/test_component_connections.usda create mode 100644 test/testSamples/lookdevXUsd/test_component_connections3.usda diff --git a/cmake/modules/FindLookdevXUfe.cmake b/cmake/modules/FindLookdevXUfe.cmake index 3850c3efb6..c408adedea 100644 --- a/cmake/modules/FindLookdevXUfe.cmake +++ b/cmake/modules/FindLookdevXUfe.cmake @@ -77,3 +77,10 @@ find_package_handle_standard_args(LookdevXUfe VERSION_VAR LookdevXUfe_VERSION ) + +set(LOOKDEVXUFE_HAS_PYTHON_BINDINGS FALSE CACHE INTERNAL "PyLookdevXUfe") +if(LookdevXUfe_VERSION VERSION_GREATER_EQUAL "1.0.1" OR + (LookdevXUfe_VERSION VERSION_LESS "1.0.0" AND LookdevXUfe_VERSION VERSION_GREATER_EQUAL "0.2.0")) + set(LOOKDEVXUFE_HAS_PYTHON_BINDINGS TRUE CACHE INTERNAL "PyLookdevXUfe") + message(STATUS "Maya has LookdevXUfe Python bindings") +endif() diff --git a/test/lib/CMakeLists.txt b/test/lib/CMakeLists.txt index 1c465434d1..1c2cf20385 100644 --- a/test/lib/CMakeLists.txt +++ b/test/lib/CMakeLists.txt @@ -118,3 +118,6 @@ add_subdirectory(usd) if (BUILD_MAYAUSDAPI_LIBRARY) add_subdirectory(mayaUsdAPI) endif() +if (BUILD_LOOKDEVXUSD_LIBRARY) + add_subdirectory(lookdevXUsd) +endif() diff --git a/test/lib/ufe/CMakeLists.txt b/test/lib/ufe/CMakeLists.txt index 150460ca50..21c0e617e3 100644 --- a/test/lib/ufe/CMakeLists.txt +++ b/test/lib/ufe/CMakeLists.txt @@ -152,6 +152,13 @@ else() set(UFE_HAS_CODE_WRAPPER "0") endif() +if(LOOKDEVXUFE_HAS_PYTHON_BINDINGS) + list(APPEND TEST_SCRIPT_FILES + testLdxCapabilityHandler.py + testLdxComponentConnections.py + ) +endif() + foreach(script ${TEST_SCRIPT_FILES}) mayaUsd_get_unittest_target(target ${script}) mayaUsd_add_test(${target} diff --git a/test/lib/ufe/testLdxCapabilityHandler.py b/test/lib/ufe/testLdxCapabilityHandler.py new file mode 100644 index 0000000000..32d68a7b38 --- /dev/null +++ b/test/lib/ufe/testLdxCapabilityHandler.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python + +# +# Copyright 2025 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 fixturesUtils +import mayaUtils +import ufeUtils + +from maya import cmds +from maya import standalone + +import ufe +from ufe.extensions import lookdevXUfe # Not using "as" in this module. +import mayaUsd.ufe + +import os +import unittest + + +class CapabilityHandlerTestCase(unittest.TestCase): + '''Verify the CapabilityHandler USD implementation. + ''' + + pluginsLoaded = False + + @classmethod + def setUpClass(cls): + fixturesUtils.readOnlySetUpClass(__file__, loadPlugin=False) + + if not cls.pluginsLoaded: + cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded() + + @classmethod + def tearDownClass(cls): + standalone.uninitialize() + + def setUp(self): + ''' Called initially to set up the Maya test environment ''' + # Load plugins + self.assertTrue(self.pluginsLoaded) + + # Open ballset.ma scene in testSamples + mayaUtils.openGroupBallsScene() + + # Clear selection to start off + cmds.select(clear=True) + + def testCapabilities(self): + rid = ufe.RunTimeMgr.instance().getId('USD') + ldxCaps = ufe.extensions.lookdevXUfe.CapabilityHandler.get(rid) + self.assertIsNotNone(ldxCaps) + + self.assertTrue(ldxCaps.hasCapability(ufe.extensions.lookdevXUfe.CapabilityHandler.kCanPromoteToMaterial)) + self.assertTrue(ldxCaps.hasCapability(ufe.extensions.lookdevXUfe.CapabilityHandler.kCanPromoteInputAtTopLevel)) + self.assertTrue(ldxCaps.hasCapability(ufe.extensions.lookdevXUfe.CapabilityHandler.kCanHaveNestedNodeGraphs)) + self.assertFalse(ldxCaps.hasCapability(ufe.extensions.lookdevXUfe.CapabilityHandler.kCanUseCreateShaderCommandForComponentConnections)) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/test/lib/ufe/testLdxComponentConnections.py b/test/lib/ufe/testLdxComponentConnections.py new file mode 100644 index 0000000000..ae076fbc41 --- /dev/null +++ b/test/lib/ufe/testLdxComponentConnections.py @@ -0,0 +1,607 @@ +#!/usr/bin/env python + +# +# Copyright 2025 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 fixturesUtils +import mayaUtils +import testUtils +import ufeUtils + +from maya import cmds +from maya import standalone + +import ufe +from ufe.extensions import lookdevXUfe as lxufe +import mayaUsd.ufe + +import os +import unittest + +class TestIsComponentConnection: + def __init__(self, unitTest, srcAttrOrItem, srcComponent, dstAttr, dstComponent): + if isinstance(srcAttrOrItem, ufe.SceneItem): + extendedConnectionHandler = lxufe.ExtendedConnectionHandler.get(srcAttrOrItem.runTimeId()) + attrs = ufe.Attributes.attributes(srcAttrOrItem) + attr = attrs.attribute("outputs:out") + self._cmd = extendedConnectionHandler.createConnectionCmd(attr, "", attr, "") + else: + extendedConnectionHandler = lxufe.ExtendedConnectionHandler.get(srcAttrOrItem.sceneItem().runTimeId()) + self._cmd = extendedConnectionHandler.createConnectionCmd(srcAttrOrItem, srcComponent, dstAttr, dstComponent) + self._args = (srcAttrOrItem, srcComponent, dstAttr, dstComponent) + self._unitTest = unitTest + + def testComponentConnected(self, test): + self._unitTest.assertEqual(test, self._cmd.isComponentConnected(*self._args)) + + def testComponentNames(self, attr, attrComponentNames): + self._unitTest.assertEqual(set(self._cmd.componentNames(attr)), set(attrComponentNames)) + +def verifyTypeToTypeConnection(unitTest, connectionHandler, + srcAttr, srcComponent, + dstSceneItem, dstAttr, dstComponent, + separatePath, combinePath): + # Get the separate and combine nodes. + separate1SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(separatePath)) + unitTest.assertTrue(separate1SceneItem) + combine1SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(combinePath)) + unitTest.assertTrue(combine1SceneItem) + + # Verify the combine to standard surface connection. + dstConnections1 = connectionHandler.sourceConnections(dstSceneItem) + unitTest.assertEqual(len(dstConnections1.allConnections()), 1) + connection = dstConnections1.allConnections()[0] + unitTest.assertTrue(connection) + combine1Attrs = ufe.Attributes.attributes(combine1SceneItem) + unitTest.assertEqual(connection.src.attribute(), combine1Attrs.attribute("outputs:out")) + unitTest.assertEqual(connection.dst.attribute(), dstAttr) + + combineInputMap = { + "r": "inputs:in1", + "g": "inputs:in2", + "b": "inputs:in3", + "a": "inputs:in4", + "x": "inputs:in1", + "y": "inputs:in2", + "z": "inputs:in3", + "w": "inputs:in4" + } + combineInput = combineInputMap.get(dstComponent, None) + unitTest.assertIsNotNone(combineInput) + + separateOutputMap = { + "r": "outputs:outr", + "g": "outputs:outg", + "b": "outputs:outb", + "a": "outputs:outa", + "x": "outputs:outx", + "y": "outputs:outy", + "z": "outputs:outz", + "w": "outputs:outw" + } + separateOutput = separateOutputMap.get(srcComponent, None) + unitTest.assertIsNotNone(separateOutput) + + # Verify the separate to combine connection. + combine1Connections = connectionHandler.sourceConnections(combine1SceneItem) + unitTest.assertEqual(len(combine1Connections.allConnections()), 1) + connection = combine1Connections.allConnections()[0] + unitTest.assertTrue(connection) + separate1Attrs = ufe.Attributes.attributes(separate1SceneItem) + unitTest.assertEqual(connection.src.attribute(), separate1Attrs.attribute(separateOutput)) + unitTest.assertEqual(connection.dst.attribute(), combine1Attrs.attribute(combineInput)) + + # Verify the src to separate connection. + separate1Connections = connectionHandler.sourceConnections(separate1SceneItem) + unitTest.assertEqual(len(separate1Connections.allConnections()), 1) + connection = separate1Connections.allConnections()[0] + unitTest.assertTrue(connection) + unitTest.assertEqual(connection.src.attribute(), srcAttr) + unitTest.assertEqual(connection.dst.attribute(), separate1Attrs.attribute("inputs:in")) + +def verifyDisconnectComponentConnections(unitTest, + connectionHandler, + addColor3SceneItem, + constantFloatAttrs, + addColor3Attrs, + numDisconnected): + separate1SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path( + "|stage|stageShape,/mtl/standard_surface1/separate1")) + if numDisconnected <= 1: + unitTest.assertTrue(separate1SceneItem) + else: + unitTest.assertFalse(separate1SceneItem) + + combine1SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path( + "|stage|stageShape,/mtl/standard_surface1/combine1")) + if numDisconnected <= 2: + unitTest.assertTrue(combine1SceneItem) + else: + unitTest.assertFalse(combine1SceneItem) + + separate1Attrs = ufe.Attributes.attributes(separate1SceneItem) + combine1Attrs = ufe.Attributes.attributes(combine1SceneItem) + if combine1SceneItem: + combine1Connections = connectionHandler.sourceConnections(combine1SceneItem).allConnections() + unitTest.assertEqual(len(combine1Connections), 3 - numDisconnected) + + if numDisconnected <= 2: + unitTest.assertEqual(combine1Connections[0].src.attribute(), constantFloatAttrs.attribute("outputs:out")) + unitTest.assertEqual(combine1Connections[0].dst.attribute(), combine1Attrs.attribute("inputs:in1")) + elif numDisconnected <= 1: + unitTest.assertEqual(combine1Connections[1].src.attribute(), separate1Attrs.attribute("outputs:outg")) + unitTest.assertEqual(combine1Connections[1].dst.attribute(), combine1Attrs.attribute("inputs:in2")) + else: + unitTest.assertEqual(combine1Connections[2].src.attribute(), separate1Attrs.attribute("outputs:outr")) + unitTest.assertEqual(combine1Connections[2].dst.attribute(), combine1Attrs.attribute("inputs:in3")) + + addColor3Connections = connectionHandler.sourceConnections(addColor3SceneItem).allConnections() + if numDisconnected == 3: + unitTest.assertEqual(len(addColor3Connections), 0) + else: + unitTest.assertEqual(len(addColor3Connections), 1) + unitTest.assertEqual(addColor3Connections[0].src.attribute(), combine1Attrs.attribute("outputs:out")) + unitTest.assertEqual(addColor3Connections[0].dst.attribute(), addColor3Attrs.attribute("inputs:in1")) + +def typeToTypeConnectionTest(unitTest, srcNodeName, srcComponent, dstNodeName, dstComponent, + outputName = "outputs:out", + inputName = "inputs:in1", + mtlPath = "|stage|stageShape,/mtl/standard_surface1/"): + srcSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPath + srcNodeName)) + unitTest.assertTrue(srcSceneItem) + + dstSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPath + dstNodeName)) + unitTest.assertTrue(dstSceneItem) + + extendedConnectionHandler = lxufe.ExtendedConnectionHandler.get(srcSceneItem.runTimeId()) + unitTest.assertTrue(extendedConnectionHandler) + + # Verify that there are no connections at the onset. + runTimeMgr = ufe.RunTimeMgr.instance() + connectionHandler = runTimeMgr.connectionHandler(dstSceneItem.runTimeId()) + initialDstConnections = connectionHandler.sourceConnections(dstSceneItem) + unitTest.assertEqual(len(initialDstConnections.allConnections()), 0) + + # Test that the component connection doesn't exist + srcAttrs = ufe.Attributes.attributes(srcSceneItem) + unitTest.assertTrue(srcAttrs) + dstAttrs = ufe.Attributes.attributes(dstSceneItem) + unitTest.assertTrue(dstAttrs) + srcAttr = srcAttrs.attribute(outputName) + unitTest.assertTrue(srcAttr) + dstAttr = dstAttrs.attribute(inputName) + unitTest.assertTrue(dstAttr) + testIsComponentConnection = TestIsComponentConnection(unitTest, srcAttr, srcComponent, dstAttr, dstComponent) + testIsComponentConnection.testComponentConnected(False) + + # Test creating component connection between src channel of srcNode and dst channel of dstNode. + + # Create the component connection. + cmd = extendedConnectionHandler.createConnectionCmd(srcAttr, srcComponent, dstAttr, dstComponent) + cmd.execute() + extendedConnection = cmd.extendedConnection() + unitTest.assertTrue(extendedConnection) + unitTest.assertEqual(extendedConnection.src.component, srcComponent) + unitTest.assertEqual(extendedConnection.src.name, srcAttr.name) + unitTest.assertEqual(extendedConnection.src.attribute(), srcAttr) + unitTest.assertEqual(extendedConnection.dst.component, dstComponent) + unitTest.assertEqual(extendedConnection.dst.name, dstAttr.name) + unitTest.assertEqual(extendedConnection.dst.attribute(), dstAttr) + + separatePath = mtlPath + "separate1" + combinePath = mtlPath + "combine1" + verifyTypeToTypeConnection(unitTest, connectionHandler, srcAttr, srcComponent, dstSceneItem, dstAttr, dstComponent, + separatePath, combinePath) + testIsComponentConnection.testComponentConnected(True) + + # Test undo + cmd.undo() + unitTest.assertEqual(len(connectionHandler.sourceConnections(dstSceneItem).allConnections()), 0) + testIsComponentConnection.testComponentConnected(False) + + # Test redo + cmd.redo() + verifyTypeToTypeConnection(unitTest, connectionHandler, srcAttr, srcComponent, dstSceneItem, dstAttr, dstComponent, + separatePath, combinePath) + testIsComponentConnection.testComponentConnected(True) + + # Undo again so that nothing is left (makes it so we can use separate1 and combine1 again). + cmd.undo() + unitTest.assertEqual(len(connectionHandler.sourceConnections(dstSceneItem).allConnections()), 0) + testIsComponentConnection.testComponentConnected(False) + +#line 927 +def valueAttrComponentNamesCheck(unitTest, constantSceneItem, componentNames): + unitTest.assertTrue(constantSceneItem) + constantSceneItemAttrs = ufe.Attributes.attributes(constantSceneItem) + constantValueAttr = constantSceneItemAttrs.attribute("inputs:value") + unitTest.assertTrue(constantValueAttr) + testIsComponentConnection = TestIsComponentConnection(unitTest, constantSceneItem, "", None, "") + testIsComponentConnection.testComponentNames(constantValueAttr, componentNames) + +#line 1348 +class ComponentConnectionsTestCase(unittest.TestCase): + '''Verify the USD implementation of component connections. + ''' + + pluginsLoaded = False + + @classmethod + def setUpClass(cls): + fixturesUtils.readOnlySetUpClass(__file__, loadPlugin=False) + + if not cls.pluginsLoaded: + cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded() + + @classmethod + def tearDownClass(cls): + standalone.uninitialize() + + def testTypeToTypeComponentConnection(self): + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('lookdevXUsd', 'test_component_connections.usda') + shapeNode, stage = mayaUtils.createProxyFromFile(testFile) + typeToTypeConnectionTest(self, "constant_color3", "r", "add_color3", "g") + typeToTypeConnectionTest(self, "constant_color4", "a", "add_color4", "b") + typeToTypeConnectionTest(self, "constant_color4", "b", "add_color4", "a") + typeToTypeConnectionTest(self, "constant_vector2", "x", "add_vector2", "y") + typeToTypeConnectionTest(self, "constant_vector3", "z", "add_vector3", "z") + typeToTypeConnectionTest(self, "constant_vector4", "w", "add_vector4", "x") + typeToTypeConnectionTest(self, "constant_vector4", "x", "add_vector4", "w") + typeToTypeConnectionTest(self, "constant_vector4", "y", "add_vector4", "y") + + def testMtlxFileComponentConnections(self): + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('lookdevXUsd', 'test_component_connections3.usda') + shapeNode, stage = mayaUtils.createProxyFromFile(testFile) + typeToTypeConnectionTest(self, "image1", "r", "fileTexture1", "b", "outputs:out", "inputs:inColor") + typeToTypeConnectionTest(self, "image1", "b", "fileTexture1", "r", "outputs:out", "inputs:inColor") + typeToTypeConnectionTest(self, "fileTexture1", "r", "standard_surface1", "b", "outputs:outColor", "inputs:base_color") + typeToTypeConnectionTest(self, "fileTexture1", "b", "standard_surface1", "r", "outputs:outColor", "inputs:base_color") + + def testFloatToColor3ComponentConnection(self): + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('lookdevXUsd', 'test_component_connections.usda') + shapeNode, stage = mayaUtils.createProxyFromFile(testFile) + stdSurface1SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path("|stage|stageShape,/mtl/standard_surface1/standard_surface1")) + self.assertTrue(stdSurface1SceneItem) + + constantFloatSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path("|stage|stageShape,/mtl/standard_surface1/constant_float")) + self.assertTrue(constantFloatSceneItem) + + extendedConnectionHandler = lxufe.ExtendedConnectionHandler.get(stdSurface1SceneItem.runTimeId()) + + # Verify that there are no connections at the onset. + runTimeMgr = ufe.RunTimeMgr.instance() + connectionHandler = runTimeMgr.connectionHandler(stdSurface1SceneItem.runTimeId()) + initialStdSurface1Connections = connectionHandler.sourceConnections(stdSurface1SceneItem) + self.assertEqual(len(initialStdSurface1Connections.allConnections()), 0) + + # Test that the component connection doesn't exist + constantFloatAttrs = ufe.Attributes.attributes(constantFloatSceneItem) + stdSurface1Attrs = ufe.Attributes.attributes(stdSurface1SceneItem) + constantFloatAttr = constantFloatAttrs.attribute("outputs:out") + stdSurface1Attr = stdSurface1Attrs.attribute("inputs:base_color") + testIsComponentConnection = TestIsComponentConnection(self, constantFloatAttr, "", stdSurface1Attr, "b") + testIsComponentConnection.testComponentConnected(False) + + # Test creating component connection between constant_float and b channel of standard_surface1. + + # Create the component connection. + cmd = extendedConnectionHandler.createConnectionCmd(constantFloatAttr, "", stdSurface1Attr, "b") + cmd.execute() + + # Get the combine node. + combine1SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path("|stage|stageShape,/mtl/standard_surface1/combine1")) + self.assertTrue(combine1SceneItem) + + # Verify the combine to standard surface connection. + stdSurface1Connections1 = connectionHandler.sourceConnections(stdSurface1SceneItem) + self.assertEqual(len(stdSurface1Connections1.allConnections()), 1) + connection = stdSurface1Connections1.allConnections()[0] + self.assertTrue(connection) + combine1Attrs = ufe.Attributes.attributes(combine1SceneItem) + self.assertEqual(connection.src.attribute(), combine1Attrs.attribute("outputs:out")) + self.assertEqual(connection.dst.attribute(), stdSurface1Attr) + + # Verify the constant_float to combine connection. + combine1Connections = connectionHandler.sourceConnections(combine1SceneItem) + self.assertEqual(len(combine1Connections.allConnections()), 1) + connection = combine1Connections.allConnections()[0] + self.assertTrue(connection) + self.assertEqual(connection.src.attribute(), constantFloatAttr) + self.assertEqual(connection.dst.attribute(), combine1Attrs.attribute("inputs:in3")) + + # Test that the component connection exists + testIsComponentConnection.testComponentConnected(True) + + def testColor3ToFloatComponentConnection(self): + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('lookdevXUsd', 'test_component_connections.usda') + shapeNode, stage = mayaUtils.createProxyFromFile(testFile) + + addSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path("|stage|stageShape,/mtl/standard_surface1/add_float")) + self.assertTrue(addSceneItem) + + constantColor3SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path("|stage|stageShape,/mtl/standard_surface1/constant_color3")) + self.assertTrue(constantColor3SceneItem) + + extendedConnectionHandler = lxufe.ExtendedConnectionHandler.get(constantColor3SceneItem.runTimeId()) + + # Verify that there are no connections at the onset. + runTimeMgr = ufe.RunTimeMgr.instance() + connectionHandler = runTimeMgr.connectionHandler(addSceneItem.runTimeId()) + initialAddConnections = connectionHandler.sourceConnections(addSceneItem) + self.assertEqual(len(initialAddConnections.allConnections()), 0) + + # Test that the component connection doesn't exist + addAttrs = ufe.Attributes.attributes(addSceneItem) + constantColor3Attrs = ufe.Attributes.attributes(constantColor3SceneItem) + addAttr = addAttrs.attribute("inputs:in1") + constantColor3Attr = constantColor3Attrs.attribute("outputs:out") + testIsComponentConnection = TestIsComponentConnection(self, constantColor3Attr, "r", addAttr, "") + testIsComponentConnection.testComponentConnected(False) + + # Test creating component connection between constant_color3 r channel and add_float. + # Create the component connection. + cmd = extendedConnectionHandler.createConnectionCmd(constantColor3Attr, "r", addAttr, "") + cmd.execute() + + # Get the separate node. + separate1SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path("|stage|stageShape,/mtl/standard_surface1/separate1")) + self.assertTrue(separate1SceneItem) + + # Verify the add_float to separate connection. + addConnections1 = connectionHandler.sourceConnections(addSceneItem) + self.assertEqual(len(addConnections1.allConnections()), 1) + connection = addConnections1.allConnections()[0] + self.assertTrue(connection) + separate1Attrs = ufe.Attributes.attributes(separate1SceneItem) + self.assertEqual(connection.src.attribute(), separate1Attrs.attribute("outputs:outr")) + self.assertEqual(connection.dst.attribute(), addAttr) + + # Verify the constant_color3 to separate connection. + separate1Connections = connectionHandler.sourceConnections(separate1SceneItem) + self.assertEqual(len(separate1Connections.allConnections()), 1) + connection = separate1Connections.allConnections()[0] + self.assertTrue(connection) + self.assertEqual(connection.src.attribute(), constantColor3Attr) + self.assertEqual(connection.dst.attribute(), separate1Attrs.attribute("inputs:in")) + + # Test that the component connection exists + testIsComponentConnection.testComponentConnected(True) + + def testColor3ToColor3ReuseSeparateAndCombineComponentConnection(self): + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('lookdevXUsd', 'test_component_connections.usda') + shapeNode, stage = mayaUtils.createProxyFromFile(testFile) + + addColor3SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path("|stage|stageShape,/mtl/standard_surface1/add_color3")) + self.assertTrue(addColor3SceneItem) + + constantColor3SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path("|stage|stageShape,/mtl/standard_surface1/constant_color3")) + self.assertTrue(constantColor3SceneItem) + + extendedConnectionHandler = lxufe.ExtendedConnectionHandler.get(constantColor3SceneItem.runTimeId()) + + # Verify that there are no connections at the onset. + runTimeMgr = ufe.RunTimeMgr.instance() + connectionHandler = runTimeMgr.connectionHandler(addColor3SceneItem.runTimeId()) + initialAddConnections = connectionHandler.sourceConnections(addColor3SceneItem) + self.assertEqual(len(initialAddConnections.allConnections()), 0) + + # Test creating component connection between constant_color3 r channel and add_color3 b channel. + # Create the component connection. + addColor3Attrs = ufe.Attributes.attributes(addColor3SceneItem) + constantColor3Attrs = ufe.Attributes.attributes(constantColor3SceneItem) + cmd = extendedConnectionHandler.createConnectionCmd(constantColor3Attrs.attribute("outputs:out"), "r", + addColor3Attrs.attribute("inputs:in1"), "b") + cmd.execute() + + # Get the separate and combine nodes. + separate1SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path("|stage|stageShape,/mtl/standard_surface1/separate1")) + self.assertTrue(separate1SceneItem) + combine1SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path("|stage|stageShape,/mtl/standard_surface1/combine1")) + self.assertTrue(combine1SceneItem) + + # Verify the combine to add_color3 connection. + dstConnections1 = connectionHandler.sourceConnections(addColor3SceneItem) + self.assertEqual(len(dstConnections1.allConnections()), 1) + connection = dstConnections1.allConnections()[0] + self.assertTrue(connection) + combine1Attrs = ufe.Attributes.attributes(combine1SceneItem) + self.assertEqual(connection.src.attribute(), combine1Attrs.attribute("outputs:out")) + self.assertEqual(connection.dst.attribute(), addColor3Attrs.attribute("inputs:in1")) + + # Verify the separate to combine connection. + combine1Connections = connectionHandler.sourceConnections(combine1SceneItem) + self.assertEqual(len(combine1Connections.allConnections()), 1) + connection = combine1Connections.allConnections()[0] + self.assertTrue(connection) + separate1Attrs = ufe.Attributes.attributes(separate1SceneItem) + self.assertEqual(connection.src.attribute(), separate1Attrs.attribute("outputs:outr")) + self.assertEqual(connection.dst.attribute(), combine1Attrs.attribute("inputs:in3")) + + # Verify the src to separate connection. + separate1Connections = connectionHandler.sourceConnections(separate1SceneItem) + self.assertEqual(len(separate1Connections.allConnections()), 1) + connection = separate1Connections.allConnections()[0] + self.assertTrue(connection) + self.assertEqual(connection.src.attribute(), constantColor3Attrs.attribute("outputs:out")) + self.assertEqual(connection.dst.attribute(), separate1Attrs.attribute("inputs:in")) + + # Create a second component connection between the constant_color3 g channel and add_color3 r channel. + # This connection should reuse the created separate and combine nodes. + cmd2 = extendedConnectionHandler.createConnectionCmd(constantColor3Attrs.attribute("outputs:out"), "g", + addColor3Attrs.attribute("inputs:in1"), "r") + cmd2.execute() + + # Verify the new separate to combine connection. + combine2Connections = connectionHandler.sourceConnections(combine1SceneItem) + self.assertEqual(len(combine2Connections.allConnections()), 2) + connection = combine2Connections.allConnections()[0] + self.assertTrue(connection) + separate1Attrs2 = ufe.Attributes.attributes(separate1SceneItem) + combine1Attrs2 = ufe.Attributes.attributes(combine1SceneItem) + self.assertEqual(connection.src.attribute(), separate1Attrs2.attribute("outputs:outg")) + self.assertEqual(connection.dst.attribute(), combine1Attrs2.attribute("inputs:in1")) + + def testColor3ToColor3ReuseSeparateAndCombineComponentConnection(self): + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('lookdevXUsd', 'test_component_connections.usda') + shapeNode, stage = mayaUtils.createProxyFromFile(testFile) + + addColor3SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path( + "|stage|stageShape,/mtl/standard_surface1/add_color3")) + self.assertTrue(addColor3SceneItem) + + constantFloatSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path( + "|stage|stageShape,/mtl/standard_surface1/constant_float")) + self.assertTrue(constantFloatSceneItem) + + constantColor3SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path( + "|stage|stageShape,/mtl/standard_surface1/constant_color3")) + self.assertTrue(constantColor3SceneItem) + + extendedConnectionHandler = lxufe.ExtendedConnectionHandler.get(constantColor3SceneItem.runTimeId()) + + # Create 3 component connections + addColor3Attrs = ufe.Attributes.attributes(addColor3SceneItem) + constantFloatAttrs = ufe.Attributes.attributes(constantFloatSceneItem) + constantColor3Attrs = ufe.Attributes.attributes(constantColor3SceneItem) + cmd = extendedConnectionHandler.createConnectionCmd(constantColor3Attrs.attribute("outputs:out"), "r", + addColor3Attrs.attribute("inputs:in1"), "b") + cmd.execute() + cmd2 = extendedConnectionHandler.createConnectionCmd(constantColor3Attrs.attribute("outputs:out"), "g", + addColor3Attrs.attribute("inputs:in1"), "g") + cmd2.execute() + cmd3 = extendedConnectionHandler.createConnectionCmd(constantFloatAttrs.attribute("outputs:out"), "", + addColor3Attrs.attribute("inputs:in1"), "r") + cmd3.execute() + + # Verify that the separate / combine scene items exist. + # Verify that there are two connections between the separate and combine scene items + # and one connection between the combine and constant_float scene items. + # Verify that there is a connection to the combine node from the add_color3 scene item. + runTimeMgr = ufe.RunTimeMgr.instance() + connectionHandler = runTimeMgr.connectionHandler(constantColor3SceneItem.runTimeId()) + verifyDisconnectComponentConnections(self, connectionHandler, addColor3SceneItem, constantFloatAttrs, addColor3Attrs, 0) + + # Delete the constant_color3 r to add_color3 b connection. + # Verify that the separate / combine scene items still exist. + # Verify that there is one connection between the separate and combine scene items + # and one connection between the combine and constant_float scene items. + # Verify that there is a connection to the combine node from the add_color3 scene item. + cmd4 = extendedConnectionHandler.deleteConnectionCmd(constantColor3Attrs.attribute("outputs:out"), "r", + addColor3Attrs.attribute("inputs:in1"), "b") + cmd4.execute() + verifyDisconnectComponentConnections(self, connectionHandler, addColor3SceneItem, constantFloatAttrs, addColor3Attrs, 1) + + # Delete the constant_color3 g to add_color3 g connection. + # Verify that the separate node no longer exists and the combine node does still exist. + # Verify that there is a connection between the combine and constant_float scene items. + # Verify that there is a connection to the combine node from the add_color3 scene item. + cmd5 = extendedConnectionHandler.deleteConnectionCmd(constantColor3Attrs.attribute("outputs:out"), "g", + addColor3Attrs.attribute("inputs:in1"), "g") + cmd5.execute() + verifyDisconnectComponentConnections(self, connectionHandler, addColor3SceneItem, constantFloatAttrs, addColor3Attrs, 2) + + # Delete the constant_float to add_color3 r connection. + # Verify that the combine node no longer exists. + # Verify that there is no connection from the add_color3 scene item. + cmd6 = extendedConnectionHandler.deleteConnectionCmd(constantFloatAttrs.attribute("outputs:out"), "", + addColor3Attrs.attribute("inputs:in1"), "r") + cmd6.execute() + verifyDisconnectComponentConnections(self, connectionHandler, addColor3SceneItem, constantFloatAttrs, addColor3Attrs, 3) + + # Test undo/redo. + cmd6.undo() + verifyDisconnectComponentConnections(self, connectionHandler, addColor3SceneItem, constantFloatAttrs, addColor3Attrs, 2) + cmd5.undo() + verifyDisconnectComponentConnections(self, connectionHandler, addColor3SceneItem, constantFloatAttrs, addColor3Attrs, 1) + cmd4.undo() + verifyDisconnectComponentConnections(self, connectionHandler, addColor3SceneItem, constantFloatAttrs, addColor3Attrs, 0) + cmd4.redo() + verifyDisconnectComponentConnections(self, connectionHandler, addColor3SceneItem, constantFloatAttrs, addColor3Attrs, 1) + cmd5.redo() + verifyDisconnectComponentConnections(self, connectionHandler, addColor3SceneItem, constantFloatAttrs, addColor3Attrs, 2) + cmd6.redo() + verifyDisconnectComponentConnections(self, connectionHandler, addColor3SceneItem, constantFloatAttrs, addColor3Attrs, 3) + + def testComponentNames(self): + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('lookdevXUsd', 'test_component_connections.usda') + shapeNode, stage = mayaUtils.createProxyFromFile(testFile) + mtlPath = "|stage|stageShape,/mtl/standard_surface1/" + + constantColor3SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPath + "constant_color3")) + valueAttrComponentNamesCheck(self, constantColor3SceneItem, ("r", "g", "b")) + + constantColor4SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPath + "constant_color4")) + valueAttrComponentNamesCheck(self, constantColor4SceneItem, ("r", "g", "b", "a")) + + constantVec2SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPath + "constant_vector2")) + valueAttrComponentNamesCheck(self, constantVec2SceneItem, ("x", "y")) + + constantVec3SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPath + "constant_vector3")) + valueAttrComponentNamesCheck(self, constantVec3SceneItem, ("x", "y", "z")) + + constantVec4SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPath + "constant_vector4")) + valueAttrComponentNamesCheck(self, constantVec4SceneItem, ("x", "y", "z", "w")) + + constantStringSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPath + "constant_string")) + valueAttrComponentNamesCheck(self, constantStringSceneItem, tuple()) + + def testInvalidComponentConnections(self): + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('lookdevXUsd', 'test_component_connections.usda') + shapeNode, stage = mayaUtils.createProxyFromFile(testFile) + mtlPath = "|stage|stageShape,/mtl/standard_surface1/" + + constantColor3SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path( + "|stage|stageShape,/mtl/standard_surface1/constant_color3")) + self.assertTrue(constantColor3SceneItem) + constantStringSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path( + "|stage|stageShape,/mtl/standard_surface1/constant_string")) + self.assertTrue(constantStringSceneItem) + addColor3SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path( + "|stage|stageShape,/mtl/standard_surface1/add_color3")) + self.assertTrue(addColor3SceneItem) + constantColor3Attrs = ufe.Attributes.attributes(constantColor3SceneItem) + self.assertTrue(constantColor3Attrs) + constantStringAttrs = ufe.Attributes.attributes(constantStringSceneItem) + self.assertTrue(constantStringAttrs) + addColor3Attrs = ufe.Attributes.attributes(addColor3SceneItem) + self.assertTrue(addColor3Attrs) + constantColor3Output = constantColor3Attrs.attribute("outputs:out") + self.assertTrue(constantColor3Output) + constantStringOutput = constantStringAttrs.attribute("outputs:out") + self.assertTrue(constantStringOutput) + addColor3Input = addColor3Attrs.attribute("inputs:in1") + self.assertTrue(addColor3Input) + + extendedConnectionHandler = lxufe.ExtendedConnectionHandler.get(constantColor3SceneItem.runTimeId()) + + with self.assertRaises(RuntimeError): + extendedConnectionHandler.createConnectionCmd(constantColor3Output, "q", addColor3Input, "") + with self.assertRaises(RuntimeError): + extendedConnectionHandler.createConnectionCmd(constantColor3Output, "r", addColor3Input, "m") + with self.assertRaises(RuntimeError): + extendedConnectionHandler.createConnectionCmd(constantStringOutput, "s", addColor3Input, "") + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/test/testSamples/lookdevXUsd/test_component_connections.usda b/test/testSamples/lookdevXUsd/test_component_connections.usda new file mode 100644 index 0000000000..e7bc0d6b99 --- /dev/null +++ b/test/testSamples/lookdevXUsd/test_component_connections.usda @@ -0,0 +1,278 @@ +#usda 1.0 + +def Sphere "Sphere1" ( + prepend apiSchemas = ["MaterialBindingAPI"] +) +{ + rel material:binding = +} + +def Scope "mtl" +{ + def Material "standard_surface1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + customData = { + dictionary Autodesk = { + token ldx_inputPos = "-969 -120.333" + token ldx_outputPos = "617.275 -73.3805" + } + } + ) + { + token outputs:mtlx:surface.connect = + uniform float2 ui:nodegraph:node:pos = (0.055555556, 0.055555556) + + def Shader "standard_surface1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_standard_surface_surfaceshader" + token outputs:out + uniform float2 ui:nodegraph:node:pos = (1.4861945, -0.37486166) + } + + def Shader "constant_color3" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_color3" + uniform float2 ui:nodegraph:node:pos = (-3.8370388, -0.96645) + } + + def Shader "constant_float" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_float" + uniform float2 ui:nodegraph:node:pos = (-3.8081167, -2.13755) + } + + def Shader "add_float" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_float" + float inputs:in1 = 0.5 + uniform float2 ui:nodegraph:node:pos = (-1.3588111, -2.1391444) + } + + def Shader "constant_color4" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_color4" + uniform float2 ui:nodegraph:node:pos = (-3.8583333, 0.15210722) + } + + def Shader "constant_vector2" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_vector2" + uniform float2 ui:nodegraph:node:pos = (-3.886689, 1.3638556) + } + + def Shader "constant_vector3" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_vector3" + uniform float2 ui:nodegraph:node:pos = (-3.9212832, 2.4603057) + } + + def Shader "constant_vector4" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_vector4" + uniform float2 ui:nodegraph:node:pos = (-3.94245, 3.5846388) + } + + def Shader "add_color3" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_color3" + color3f inputs:in1 = (0.1, 0.2, 0.3) + uniform float2 ui:nodegraph:node:pos = (-1.4134389, -0.9946611) + } + + def Shader "add_color4" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_color4" + color4f inputs:in1 = (0.1, 0.2, 0.3, 0.4) + uniform float2 ui:nodegraph:node:pos = (-1.4293056, 0.14815277) + } + + def Shader "add_vector2" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_vector2" + float2 inputs:in1 = (0.1, 0.2) + uniform float2 ui:nodegraph:node:pos = (-1.4394777, 1.3561223) + } + + def Shader "add_vector3" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_vector3" + float3 inputs:in1 = (0.1, 0.2, 0.3) + uniform float2 ui:nodegraph:node:pos = (-1.462061, 2.466189) + } + + def Shader "add_vector4" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_vector4" + float4 inputs:in1 = (0.1, 0.2, 0.3, 0.4) + uniform float2 ui:nodegraph:node:pos = (-1.4772334, 3.6083665) + } + + def Shader "constant_string" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_string" + uniform float2 ui:nodegraph:node:pos = (-3.933311, 4.7703557) + } + + def Shader "constant_matrix33" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_matrix33" + uniform float2 ui:nodegraph:node:pos = (-3.9435499, 5.9079995) + } + + def Shader "constant_matrix44" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_matrix44" + uniform float2 ui:nodegraph:node:pos = (-4.0094333, 7.0698886) + } + + def Shader "add_matrix33" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_matrix33" + uniform float2 ui:nodegraph:node:pos = (-1.5000334, 4.895889) + } + + def Shader "add_matrix44" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_matrix44" + uniform float2 ui:nodegraph:node:pos = (-1.54795, 6.1415553) + } + } + + def Material "standard_surface2" ( + apiSchemas = ["NodeGraphNodeAPI"] + customData = { + dictionary Autodesk = { + token ldx_inputPos = "-969 -120.333" + token ldx_outputPos = "617.275 -73.3805" + } + } + ) + { + uniform float2 ui:nodegraph:node:pos = (0.055555556, 0.055555556) + + def NodeGraph "compound1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + color3f inputs:out ( + sdrMetadata = { + string uiorder = "0" + } + ) + color3f inputs:out.connect = + color3f outputs:value ( + sdrMetadata = { + string uiorder = "0" + } + ) + uniform float2 ui:nodegraph:node:pos = (-1.2314944, -0.35861167) + } + + def Shader "constant1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_color3" + color3f inputs:value.connect = + uniform float2 ui:nodegraph:node:pos = (0.6073056, -0.48191887) + } + + def Shader "constant2" ( + apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_color3" + color3f outputs:out + uniform float2 ui:nodegraph:node:pos = (-2.9181278, -0.11234944) + } + + def Shader "separate1" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_separate3_color3" + color3f inputs:in.connect = + float outputs:outr + } + + def Shader "combine1" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_combine3_color3" + float inputs:in1.connect = + color3f outputs:out + } + + def Shader "separate2" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_separate3_color3" + color3f inputs:in.connect = + float outputs:outr + } + + def Shader "combine2" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_combine3_color3" + float inputs:in1.connect = + color3f outputs:out + } + } +} + diff --git a/test/testSamples/lookdevXUsd/test_component_connections3.usda b/test/testSamples/lookdevXUsd/test_component_connections3.usda new file mode 100644 index 0000000000..a686cd4130 --- /dev/null +++ b/test/testSamples/lookdevXUsd/test_component_connections3.usda @@ -0,0 +1,63 @@ +#usda 1.0 + +def Sphere "Sphere1" ( + prepend apiSchemas = ["MaterialBindingAPI"] +) +{ + rel material:binding = +} + +def Scope "mtl" +{ + def Material "standard_surface1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + customData = { + dictionary Autodesk = { + token ldx_inputPos = "-450 -61" + token ldx_outputPos = "414.333 -82.3333" + } + } + ) + { + token outputs:mtlx:surface.connect = + uniform float2 ui:nodegraph:node:pos = (0.055555556, 0.055555556) + + def Shader "standard_surface1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_standard_surface_surfaceshader" + token outputs:out + uniform float2 ui:nodegraph:node:pos = (0.91481674, -1.2018499) + } + + def Shader "image1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_image_color3" + float2 inputs:texcoord.connect = + uniform float2 ui:nodegraph:node:pos = (-2.195372, -1.362961) + } + + def Shader "geompropvalue1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_geompropvalue_vector2" + string inputs:geomprop = "st" + float2 outputs:out + uniform float2 ui:nodegraph:node:pos = (-3.5564833, -1.362961) + } + + def Shader "fileTexture1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "MayaND_fileTexture_color3" + string inputs:colorSpace = "sRGB" + uniform float2 ui:nodegraph:node:pos = (-0.5620389, -1.2555555) + } + } +} + From 88ba3adf434c924a5a0385e5aa1626c9e31dbef1 Mon Sep 17 00:00:00 2001 From: Jerry Gamache Date: Wed, 5 Feb 2025 17:03:32 -0500 Subject: [PATCH 2/5] Moved testConnection unit test from LookdevX --- test/lib/CMakeLists.txt | 3 - test/lib/ufe/CMakeLists.txt | 1 + test/lib/ufe/testLdxConnection.py | 214 +++++++++++ .../lookdevXUsd/extendedConnections.usda | 357 ++++++++++++++++++ .../lookdevXUsd/isInternalConnection.usda | 264 +++++++++++++ .../lookdevXUsd/test_component.usda | 89 +++++ 6 files changed, 925 insertions(+), 3 deletions(-) create mode 100644 test/lib/ufe/testLdxConnection.py create mode 100644 test/testSamples/lookdevXUsd/extendedConnections.usda create mode 100644 test/testSamples/lookdevXUsd/isInternalConnection.usda create mode 100644 test/testSamples/lookdevXUsd/test_component.usda diff --git a/test/lib/CMakeLists.txt b/test/lib/CMakeLists.txt index 1c2cf20385..1c465434d1 100644 --- a/test/lib/CMakeLists.txt +++ b/test/lib/CMakeLists.txt @@ -118,6 +118,3 @@ add_subdirectory(usd) if (BUILD_MAYAUSDAPI_LIBRARY) add_subdirectory(mayaUsdAPI) endif() -if (BUILD_LOOKDEVXUSD_LIBRARY) - add_subdirectory(lookdevXUsd) -endif() diff --git a/test/lib/ufe/CMakeLists.txt b/test/lib/ufe/CMakeLists.txt index 21c0e617e3..4ad2c71658 100644 --- a/test/lib/ufe/CMakeLists.txt +++ b/test/lib/ufe/CMakeLists.txt @@ -156,6 +156,7 @@ if(LOOKDEVXUFE_HAS_PYTHON_BINDINGS) list(APPEND TEST_SCRIPT_FILES testLdxCapabilityHandler.py testLdxComponentConnections.py + testLdxConnection.py ) endif() diff --git a/test/lib/ufe/testLdxConnection.py b/test/lib/ufe/testLdxConnection.py new file mode 100644 index 0000000000..812744fb4f --- /dev/null +++ b/test/lib/ufe/testLdxConnection.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python + +# +# Copyright 2025 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 fixturesUtils +import mayaUtils +import testUtils +import ufeUtils + +from maya import cmds +from maya import standalone + +import ufe +from ufe.extensions import lookdevXUfe as lxufe +import mayaUsd.ufe + +import os +import unittest + +def verifyConnections(testHarness, connections, numRegularConnections, numComponentConnections, numConverterConnections): + numRegular = 0 + numComponent = 0 + numConverter = 0 + for connection in connections: + if connection.src.component or connection.dst.component: + numComponent += 1 + elif connection.src.attribute().type == connection.dst.attribute().type: + numRegular += 1 + else: + numConverter += 1 + + testHarness.assertEqual(numRegular, numRegularConnections) + testHarness.assertEqual(numComponent, numComponentConnections) + testHarness.assertEqual(numConverter, numConverterConnections) + +class ComponentConnectionsTestCase(unittest.TestCase): + '''Verify the USD implementation of LookdevXUfe connections. + ''' + pluginsLoaded = False + + @classmethod + def setUpClass(cls): + fixturesUtils.readOnlySetUpClass(__file__, loadPlugin=False) + + if not cls.pluginsLoaded: + cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded() + + @classmethod + def tearDownClass(cls): + standalone.uninitialize() + + @staticmethod + def loadUsdFile(fileName): + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('lookdevXUsd', fileName) + mayaUtils.createProxyFromFile(testFile) + + def test_isConnectionHidden(self): + self.loadUsdFile("test_component.usda") + + mtlPathStr = "|stage|stageShape,/mtl/standard_surface1" + + stdSurfaceSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPathStr+"/standard_surface1")) + self.assertTrue(stdSurfaceSceneItem) + combineSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPathStr+"/combine1")) + self.assertTrue(combineSceneItem) + separateSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPathStr+"/separate1")) + self.assertTrue(separateSceneItem) + addSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPathStr+"/add1")) + self.assertTrue(addSceneItem) + + connectionHandler = ufe.RunTimeMgr.instance().connectionHandler(stdSurfaceSceneItem.runTimeId()) + + stdSurfaceSrcConnections = connectionHandler.sourceConnections(stdSurfaceSceneItem) + for stdSurfaceSrcConnection in stdSurfaceSrcConnections.allConnections(): + self.assertTrue(lxufe.UfeUtils.isConnectionHidden(stdSurfaceSrcConnection)) + + combineSrcConnections = connectionHandler.sourceConnections(combineSceneItem) + for combineSrcConnection in combineSrcConnections.allConnections(): + self.assertTrue(lxufe.UfeUtils.isConnectionHidden(combineSrcConnection)) + + separateSrcConnections = connectionHandler.sourceConnections(separateSceneItem) + for separateSrcConnection in separateSrcConnections.allConnections(): + self.assertTrue(lxufe.UfeUtils.isConnectionHidden(separateSrcConnection)) + + addSrcConnections = connectionHandler.sourceConnections(addSceneItem) + for addSrcConnection in addSrcConnections.allConnections(): + self.assertFalse(lxufe.UfeUtils.isConnectionHidden(addSrcConnection)) + + def test_GetAllExtendedConnections(self): + self.loadUsdFile("extendedConnections.usda") + + mtlPathStr = "|stage|stageShape,/mtl/standard_surface1" + + # Get the standard_surface1 node and scene item. + stdSurf1Item = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPathStr+"/standard_surface1")) + self.assertTrue(stdSurf1Item) + + # Get the add1 node and scene item. + add1Item = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPathStr+"/add1")) + self.assertTrue(add1Item) + + # Get the add2 node and scene item. + add2Item = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPathStr+"/add2")) + self.assertTrue(add2Item) + + # Verify the getAllExtendedConnections function with the standard_surface1 node. + allStdSurfExtendedConnections = lxufe.UfeUtils.getAllExtendedConnections(stdSurf1Item) + verifyConnections(self, allStdSurfExtendedConnections, 2, 2, 2) + + stdSurfRegularConnections = lxufe.UfeUtils.getAllExtendedConnections( + stdSurf1Item, lxufe.UfeUtils.kExtendedRegularConnections) + verifyConnections(self, stdSurfRegularConnections, 2, 0, 0) + + stdSurfSrcRegularConnections = lxufe.UfeUtils.getSourceExtendedConnections( + stdSurf1Item, lxufe.UfeUtils.kExtendedRegularConnections) + verifyConnections(self, stdSurfSrcRegularConnections, 1, 0, 0) + + stdSurfComponentConnections = lxufe.UfeUtils.getAllExtendedConnections( + stdSurf1Item, lxufe.UfeUtils.kExtendedComponentConnections) + verifyConnections(self, stdSurfComponentConnections, 0, 2, 0) + + stdSurfSrcComponentConnections = lxufe.UfeUtils.getSourceExtendedConnections( + stdSurf1Item, lxufe.UfeUtils.kExtendedComponentConnections) + verifyConnections(self, stdSurfSrcComponentConnections, 0, 2, 0) + + stdSurfConverterConnections = lxufe.UfeUtils.getAllExtendedConnections( + stdSurf1Item, lxufe.UfeUtils.kExtendedConverterConnections) + verifyConnections(self, stdSurfConverterConnections, 0, 0, 2) + + stdSurfSrcConverterConnections = lxufe.UfeUtils.getSourceExtendedConnections( + stdSurf1Item, lxufe.UfeUtils.kExtendedConverterConnections) + verifyConnections(self, stdSurfSrcConverterConnections, 0, 0, 2) + + stdSurfNonRegularConnections = lxufe.UfeUtils.getAllExtendedConnections( + stdSurf1Item, + lxufe.UfeUtils.kExtendedComponentConnections | lxufe.UfeUtils.kExtendedConverterConnections) + verifyConnections(self, stdSurfNonRegularConnections, 0, 2, 2) + + stdSurfSrcNonRegularConnections = lxufe.UfeUtils.getSourceExtendedConnections( + stdSurf1Item, + lxufe.UfeUtils.kExtendedComponentConnections | lxufe.UfeUtils.kExtendedConverterConnections) + verifyConnections(self, stdSurfSrcNonRegularConnections, 0, 2, 2) + + stdSurfRegularOrComponentConnections = lxufe.UfeUtils.getAllExtendedConnections( + stdSurf1Item, + lxufe.UfeUtils.kExtendedRegularConnections | lxufe.UfeUtils.kExtendedComponentConnections) + verifyConnections(self, stdSurfRegularOrComponentConnections, 2, 2, 0) + + stdSurfSrcRegularOrComponentConnections = lxufe.UfeUtils.getSourceExtendedConnections( + stdSurf1Item, + lxufe.UfeUtils.kExtendedRegularConnections | lxufe.UfeUtils.kExtendedComponentConnections) + verifyConnections(self, stdSurfSrcRegularOrComponentConnections, 1, 2, 0) + + stdSurfRegularOrConverterConnections = lxufe.UfeUtils.getAllExtendedConnections( + stdSurf1Item, + lxufe.UfeUtils.kExtendedRegularConnections | lxufe.UfeUtils.kExtendedConverterConnections) + verifyConnections(self, stdSurfRegularOrConverterConnections, 2, 0, 2) + + stdSurfSrcRegularOrConverterConnections = lxufe.UfeUtils.getSourceExtendedConnections( + stdSurf1Item, + lxufe.UfeUtils.kExtendedRegularConnections | lxufe.UfeUtils.kExtendedConverterConnections) + verifyConnections(self, stdSurfSrcRegularOrConverterConnections, 1, 0, 2) + + # Verify the getAllExtendedConnections function with the add1 node. + allAdd1ExtendedConnections = lxufe.UfeUtils.getAllExtendedConnections(add1Item) + verifyConnections(self, allAdd1ExtendedConnections, 0, 6, 0) + + add1SrcExtendedConnections = lxufe.UfeUtils.getSourceExtendedConnections(add1Item) + verifyConnections(self, add1SrcExtendedConnections, 0, 3, 0) + + # Verify the getAllExtendedConnections function with the add2 node. + allAdd2ExtendedConnections = lxufe.UfeUtils.getAllExtendedConnections(add2Item) + verifyConnections(self, allAdd2ExtendedConnections, 0, 0, 4) + + add2SrcExtendedConnections = lxufe.UfeUtils.getSourceExtendedConnections(add2Item) + verifyConnections(self, add2SrcExtendedConnections, 0, 0, 2) + + def test_IsInternalConnection(self): + self.loadUsdFile("isInternalConnection.usda") + + mtlPathStr = "|stage|stageShape,/mtl/surface1" + + # Get compound1. + # Get the add1 node and scene item. + compound1SceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPathStr+"/compound1")) + self.assertTrue(compound1SceneItem) + + connections = lxufe.UfeUtils.getAllExtendedConnections(compound1SceneItem) + self.assertEqual(len(connections), 12) + + numInternalConnections = 0 + for connection in connections: + if lxufe.UfeUtils.isInternalConnection(connection): + numInternalConnections += 1 + + self.assertEqual(numInternalConnections, 6) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/test/testSamples/lookdevXUsd/extendedConnections.usda b/test/testSamples/lookdevXUsd/extendedConnections.usda new file mode 100644 index 0000000000..04de2bfa52 --- /dev/null +++ b/test/testSamples/lookdevXUsd/extendedConnections.usda @@ -0,0 +1,357 @@ +#usda 1.0 + +def Scope "mtl" +{ + def Material "standard_surface1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + customData = { + dictionary Autodesk = { + string ldx_inputPos = "-669.5 -72.5" + string ldx_outputPos = "226 -61" + } + } + ) + { + token outputs:mtlx:surface.connect = + uniform float2 ui:nodegraph:node:pos = (-1.0916667, -0.84444445) + + def Shader "standard_surface1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_standard_surface_surfaceshader" + float inputs:base.connect = + color3f inputs:base_color.connect = + color3f inputs:specular_color.connect = + color3f inputs:subsurface_color.connect = + color3f inputs:transmission_color.connect = + token outputs:out + uniform float2 ui:nodegraph:node:pos = (-0.43888888, -0.20555556) + } + + def Shader "converterNode1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_float" + float outputs:out + uniform float2 ui:nodegraph:node:pos = (-2.2027779, -0.08888889) + } + + def Shader "adsk_converter1" ( + customData = { + dictionary Autodesk = { + string hiddenInternalConverter = "true" + string internalConverter = "true" + } + } + ) + { + uniform token info:id = "ND_adsk_converter_float_color3" + float inputs:in.connect = + color3f outputs:out + } + + def Shader "regularNode1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_color3" + color3f outputs:out + uniform float2 ui:nodegraph:node:pos = (-2.225, 1.1722223) + } + + def Shader "componentNode1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_color3" + color3f outputs:out + uniform float2 ui:nodegraph:node:pos = (-2.2416666, 2.3666666) + } + + def Shader "separate1" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_separate3_color3" + color3f inputs:in.connect = + float outputs:outb + float outputs:outr + } + + def Shader "combine1" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_combine3_color3" + float inputs:in1 = 1 + float inputs:in2 = 1 + float inputs:in3 = 1 + float inputs:in3.connect = + color3f outputs:out + } + + def Shader "combine2" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_combine3_color3" + float inputs:in1 = 1 + float inputs:in2 = 1 + float inputs:in3 = 1 + float inputs:in3.connect = + color3f outputs:out + } + + def Shader "converterNode2" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_color3" + color3f outputs:out + uniform float2 ui:nodegraph:node:pos = (-2.2305555, 3.9277778) + } + + def Shader "adsk_converter2" ( + customData = { + dictionary Autodesk = { + string hiddenInternalConverter = "true" + string internalConverter = "true" + } + } + ) + { + uniform token info:id = "ND_adsk_converter_color3_float" + color3f inputs:in.connect = + float outputs:out + } + + def Shader "add1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_color3" + color3f inputs:in1.connect = + color3f inputs:in2.connect = + color3f outputs:out + uniform float2 ui:nodegraph:node:pos = (-0.5744389, 6.488055) + } + + def Shader "add2" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + customData = { + dictionary ClipboardMetadata = { + string materialName = "standard_surface1" + string shaderName = "add1" + string stagePath = "|world|stage1|stageShape1" + } + } + ) + { + uniform token info:id = "ND_add_color3" + color3f inputs:in1.connect = + color3f inputs:in2.connect = + color3f outputs:out + uniform float2 ui:nodegraph:node:pos = (-0.43286943, 9.2105) + } + + def Shader "componentNode2" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_color3" + color3f outputs:out + uniform float2 ui:nodegraph:node:pos = (-2.774139, 7.4027777) + } + + def Shader "constant2" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_float" + float outputs:out + uniform float2 ui:nodegraph:node:pos = (-2.7305777, 9.243167) + } + + def Shader "componentNode3" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + customData = { + dictionary ClipboardMetadata = { + string materialName = "standard_surface1" + string shaderName = "add1" + string stagePath = "|world|stage1|stageShape1" + } + } + ) + { + uniform token info:id = "ND_add_color3" + color3f inputs:in1.connect = + color3f inputs:in2.connect = + uniform float2 ui:nodegraph:node:pos = (1.6797223, 6.4989996) + } + + def Shader "add4" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_float" + float inputs:in1.connect = + float inputs:in2.connect = + uniform float2 ui:nodegraph:node:pos = (1.8212833, 9.068945) + } + + def Shader "separate2" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_separate3_color3" + color3f inputs:in.connect = + float outputs:outb + float outputs:outg + float outputs:outr + } + + def Shader "combine3" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_combine3_color3" + float inputs:in1.connect = + color3f outputs:out + } + + def Shader "combine4" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_combine3_color3" + float inputs:in2.connect = + float inputs:in3.connect = + color3f outputs:out + } + + def Shader "separate3" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_separate3_color3" + color3f inputs:in.connect = + float outputs:outb + float outputs:outg + float outputs:outr + } + + def Shader "combine5" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_combine3_color3" + float inputs:in1.connect = + float inputs:in2.connect = + color3f outputs:out + } + + def Shader "combine6" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_combine3_color3" + float inputs:in3.connect = + color3f outputs:out + } + + def Shader "adsk_converter3" ( + customData = { + dictionary Autodesk = { + string hiddenInternalConverter = "true" + string internalConverter = "true" + } + } + ) + { + uniform token info:id = "ND_adsk_converter_float_color3" + float inputs:in.connect = + color3f outputs:out + } + + def Shader "adsk_converter4" ( + customData = { + dictionary Autodesk = { + string hiddenInternalConverter = "true" + string internalConverter = "true" + } + } + ) + { + uniform token info:id = "ND_adsk_converter_float_color3" + float inputs:in.connect = + color3f outputs:out + } + + def Shader "adsk_converter5" ( + customData = { + dictionary Autodesk = { + string hiddenInternalConverter = "true" + string internalConverter = "true" + } + } + ) + { + uniform token info:id = "ND_adsk_converter_color3_float" + color3f inputs:in.connect = + float outputs:out + } + + def Shader "adsk_converter6" ( + customData = { + dictionary Autodesk = { + string hiddenInternalConverter = "true" + string internalConverter = "true" + } + } + ) + { + uniform token info:id = "ND_adsk_converter_color3_float" + color3f inputs:in.connect = + float outputs:out + } + } +} + diff --git a/test/testSamples/lookdevXUsd/isInternalConnection.usda b/test/testSamples/lookdevXUsd/isInternalConnection.usda new file mode 100644 index 0000000000..70f2181d62 --- /dev/null +++ b/test/testSamples/lookdevXUsd/isInternalConnection.usda @@ -0,0 +1,264 @@ +#usda 1.0 + +def Sphere "Sphere1" ( + prepend apiSchemas = ["MaterialBindingAPI"] +) +{ + rel material:binding = +} + +def Scope "mtl" +{ + def Material "surface1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + customData = { + dictionary Autodesk = { + string ldx_inputPos = "-679.5 -58" + string ldx_outputPos = "226 -61" + } + } + ) + { + uniform float2 ui:nodegraph:node:pos = (0.055555556, 0.055555556) + + def NodeGraph "compound1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + customData = { + dictionary Autodesk = { + string ldx_inputPos = "-449.5 -61" + string ldx_outputPos = "225.5 -61" + } + } + ) + { + color3f inputs:in1 = (0, 0, 0) ( + sdrMetadata = { + string uiorder = "0" + string uisoftmax = "1,1,1" + string uisoftmin = "0,0,0" + } + ) + color3f inputs:in1.connect = + float inputs:in2 = 0 ( + sdrMetadata = { + string uiorder = "1" + string uisoftmax = "1" + string uisoftmin = "0" + } + ) + float inputs:in2.connect = + color3f inputs:in3 = (0, 0, 0) ( + sdrMetadata = { + string uiorder = "2" + string uisoftmax = "1,1,1" + string uisoftmin = "0,0,0" + } + ) + color3f inputs:in3.connect = + color3f outputs:out ( + sdrMetadata = { + string uiorder = "0" + } + ) + color3f outputs:out.connect = + float outputs:out1 ( + sdrMetadata = { + string uiorder = "1" + } + ) + float outputs:out1.connect = + color3f outputs:out2 ( + sdrMetadata = { + string uiorder = "2" + } + ) + color3f outputs:out2.connect = + uniform float2 ui:nodegraph:node:pos = (-1.6305555, -0.25) + + def Shader "add1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_color3" + color3f inputs:in1.connect = + color3f outputs:out + uniform float2 ui:nodegraph:node:pos = (-0.73055553, -0.8) + } + + def Shader "add2" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_color3" + color3f inputs:in1.connect = + color3f outputs:out + uniform float2 ui:nodegraph:node:pos = (-0.725, 0.3611111) + } + + def Shader "separate1" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_separate3_color3" + color3f inputs:in.connect = + float outputs:outr + } + + def Shader "combine1" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_combine3_color3" + float inputs:in3.connect = + color3f outputs:out + } + + def Shader "add3" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_float" + float inputs:in1.connect = + float outputs:out + uniform float2 ui:nodegraph:node:pos = (-0.725, 1.55) + } + + def Shader "adsk_converter1" ( + customData = { + dictionary Autodesk = { + string hiddenInternalConverter = "true" + string internalConverter = "true" + } + } + ) + { + uniform token info:id = "ND_adsk_converter_float_color3" + float inputs:in.connect = + color3f outputs:out + } + + def Shader "adsk_converter2" ( + customData = { + dictionary Autodesk = { + string hiddenInternalConverter = "true" + string internalConverter = "true" + } + } + ) + { + uniform token info:id = "ND_adsk_converter_color3_float" + color3f inputs:in.connect = + float outputs:out + } + } + + def Shader "add1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_color3" + color3f inputs:in1.connect = + color3f inputs:in2.connect = + uniform float2 ui:nodegraph:node:pos = (-0.013888889, -0.35555556) + } + + def Shader "combine1" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_combine3_color3" + float inputs:in2.connect = + color3f outputs:out + } + + def Shader "add2" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_float" + float inputs:in1.connect = + uniform float2 ui:nodegraph:node:pos = (-0.125, 1.4666667) + } + + def Shader "adsk_converter1" ( + customData = { + dictionary Autodesk = { + string hiddenInternalConverter = "true" + string internalConverter = "true" + } + } + ) + { + uniform token info:id = "ND_adsk_converter_color3_float" + color3f inputs:in.connect = + float outputs:out + } + + def Shader "add3" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_color3" + color3f outputs:out + uniform float2 ui:nodegraph:node:pos = (-3.4757276, 0.4533011) + } + + def Shader "add4" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_color3" + color3f outputs:out + uniform float2 ui:nodegraph:node:pos = (-3.4905112, 1.7764945) + } + + def Shader "separate1" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_separate3_color3" + color3f inputs:in.connect = + float outputs:outb + } + + def Shader "add5" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_float" + float outputs:out + uniform float2 ui:nodegraph:node:pos = (-3.5126889, 3.447122) + } + + def Shader "adsk_converter2" ( + customData = { + dictionary Autodesk = { + string hiddenInternalConverter = "true" + string internalConverter = "true" + } + } + ) + { + uniform token info:id = "ND_adsk_converter_float_color3" + float inputs:in.connect = + color3f outputs:out + } + } +} + diff --git a/test/testSamples/lookdevXUsd/test_component.usda b/test/testSamples/lookdevXUsd/test_component.usda new file mode 100644 index 0000000000..699c9d4bba --- /dev/null +++ b/test/testSamples/lookdevXUsd/test_component.usda @@ -0,0 +1,89 @@ +#usda 1.0 + +def Sphere "Sphere1" ( + prepend apiSchemas = ["MaterialBindingAPI"] +) +{ + rel material:binding = +} + +def Scope "mtl" +{ + def Material "standard_surface1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + customData = { + dictionary Autodesk = { + token ldx_inputPos = "-450 -61" + token ldx_outputPos = "225 -61" + } + } + ) + { + token outputs:mtlx:surface.connect = + uniform float2 ui:nodegraph:node:pos = (0.055555556, 0.055555556) + + def Shader "standard_surface1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_standard_surface_surfaceshader" + color3f inputs:base_color.connect = + token outputs:out + uniform float2 ui:nodegraph:node:pos = (-0.44444445, -0.20555556) + } + + def Shader "constant1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_constant_color3" + color3f outputs:out + uniform float2 ui:nodegraph:node:pos = (-1.57685, -1.0148168) + } + + def Shader "add1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "ND_add_color3" + color3f outputs:out + color3f inputs:in1.connect = + } + + def Shader "separate1" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_separate3_color3" + color3f inputs:in.connect = + float outputs:outr + } + + def Shader "combine1" ( + customData = { + dictionary Autodesk = { + string hidden = "true" + } + } + ) + { + uniform token info:id = "ND_combine3_color3" + float inputs:in1.timeSamples = { + 1: 0.8, + } + float inputs:in1.connect = + float inputs:in2.timeSamples = { + 1: 0.8, + } + float inputs:in3.timeSamples = { + 1: 0.8, + } + color3f outputs:out + } + } +} + From 79f05331ef41ad5f3177b199d00ab9a8711172f7 Mon Sep 17 00:00:00 2001 From: Jerry Gamache Date: Thu, 6 Feb 2025 14:26:50 -0500 Subject: [PATCH 3/5] Added LookdevXUfe::DebugHandler test --- test/lib/ufe/CMakeLists.txt | 1 + test/lib/ufe/testLdxConnection.py | 2 +- test/lib/ufe/testLdxDebugHandler.py | 95 +++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 test/lib/ufe/testLdxDebugHandler.py diff --git a/test/lib/ufe/CMakeLists.txt b/test/lib/ufe/CMakeLists.txt index 4ad2c71658..bffa3d7283 100644 --- a/test/lib/ufe/CMakeLists.txt +++ b/test/lib/ufe/CMakeLists.txt @@ -157,6 +157,7 @@ if(LOOKDEVXUFE_HAS_PYTHON_BINDINGS) testLdxCapabilityHandler.py testLdxComponentConnections.py testLdxConnection.py + testLdxDebugHandler.py ) endif() diff --git a/test/lib/ufe/testLdxConnection.py b/test/lib/ufe/testLdxConnection.py index 812744fb4f..8bf0ef1b15 100644 --- a/test/lib/ufe/testLdxConnection.py +++ b/test/lib/ufe/testLdxConnection.py @@ -47,7 +47,7 @@ def verifyConnections(testHarness, connections, numRegularConnections, numCompon testHarness.assertEqual(numComponent, numComponentConnections) testHarness.assertEqual(numConverter, numConverterConnections) -class ComponentConnectionsTestCase(unittest.TestCase): +class LookdevXUfeConnectionTestCase(unittest.TestCase): '''Verify the USD implementation of LookdevXUfe connections. ''' pluginsLoaded = False diff --git a/test/lib/ufe/testLdxDebugHandler.py b/test/lib/ufe/testLdxDebugHandler.py new file mode 100644 index 0000000000..7f5e9f69cb --- /dev/null +++ b/test/lib/ufe/testLdxDebugHandler.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python + +# +# Copyright 2025 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 fixturesUtils +import mayaUtils +import testUtils +import ufeUtils + +from maya import cmds +from maya import standalone + +import ufe +from ufe.extensions import lookdevXUfe as lxufe +import mayaUsd.ufe + +from pxr import UsdGeom, UsdShade, Sdf, Usd, Gf, Vt + +import os +import unittest + +class LookdevXUfeDebugHandlerTestCase(unittest.TestCase): + '''Verify the USD implementation of LookdevXUfe DebugHandler. + ''' + pluginsLoaded = False + + @classmethod + def setUpClass(cls): + fixturesUtils.readOnlySetUpClass(__file__, loadPlugin=False) + + if not cls.pluginsLoaded: + cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded() + + @classmethod + def tearDownClass(cls): + standalone.uninitialize() + + @staticmethod + def loadUsdFile(fileName): + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('lookdevXUsd', fileName) + mayaUtils.createProxyFromFile(testFile) + + def test_DebugHandler(self): + self.loadUsdFile('test_component.usda') + + mtlPathStr = "|stage|stageShape,/mtl/standard_surface1" + + stdSurfaceSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(mtlPathStr+"/standard_surface1")) + self.assertTrue(stdSurfaceSceneItem) + + # Use the debug handler to get the data model string representation for the scene item. + debugHandler = lxufe.DebugHandler.get(stdSurfaceSceneItem.runTimeId()) + sceneItemDump = debugHandler.exportToString(stdSurfaceSceneItem) + + # Look for the #sdf header to ensure it's a valid file, and look for signs + # of the xform primitive. + self.assertTrue("#sdf" in sceneItemDump) + self.assertTrue('def Shader "standard_surface1"' in sceneItemDump) + + nodeDefHandler = ufe.RunTimeMgr.instance().nodeDefHandler(ufe.RunTimeMgr.instance().getId('USD')) + self.assertTrue(nodeDefHandler) + + nodeDef = nodeDefHandler.definition("UsdPreviewSurface") + self.assertTrue(nodeDef) + + # TODO: Unimplemented. + self.assertTrue(debugHandler.hasViewportSupport(nodeDef)) + + # TODO: Unimplemented. + self.assertFalse(debugHandler.getImplementation(nodeDef)) + + # TODO: Unimplemented. + self.assertFalse(debugHandler.isLibraryItem(stdSurfaceSceneItem)) + + # TODO: Unimplemented. + self.assertTrue(debugHandler.isEditable(stdSurfaceSceneItem)) + + +if __name__ == '__main__': + unittest.main(verbosity=2) From 504bde14d4caa0e3c7dae5af031a798c882199f5 Mon Sep 17 00:00:00 2001 From: Jerry Gamache Date: Wed, 26 Feb 2025 11:24:58 -0500 Subject: [PATCH 4/5] Added FileHandler tests Also fixed relative path resolution for UDIM file patterns. --- lib/lookdevXUsd/UsdFileHandler.cpp | 92 ++++----------- test/lib/ufe/CMakeLists.txt | 1 + test/lib/ufe/testLdxFileHandler.py | 110 ++++++++++++++++++ .../RelativeReferencedUdimTextures.usda | 12 ++ .../assets/simplePlane/simplePlane.usda | 88 ++++++++++++++ .../simplePlane/textures/diffuse.1001.jpg | Bin 0 -> 22647 bytes .../simplePlane/textures/diffuse.1002.jpg | Bin 0 -> 23678 bytes .../simplePlane/textures/diffuse.1003.jpg | Bin 0 -> 23690 bytes 8 files changed, 235 insertions(+), 68 deletions(-) create mode 100644 test/lib/ufe/testLdxFileHandler.py create mode 100644 test/testSamples/lookdevXUsd/RelativeReferencedUdimTextures.usda create mode 100644 test/testSamples/lookdevXUsd/assets/simplePlane/simplePlane.usda create mode 100644 test/testSamples/lookdevXUsd/assets/simplePlane/textures/diffuse.1001.jpg create mode 100644 test/testSamples/lookdevXUsd/assets/simplePlane/textures/diffuse.1002.jpg create mode 100644 test/testSamples/lookdevXUsd/assets/simplePlane/textures/diffuse.1003.jpg diff --git a/lib/lookdevXUsd/UsdFileHandler.cpp b/lib/lookdevXUsd/UsdFileHandler.cpp index 2e58c41655..c156df290a 100644 --- a/lib/lookdevXUsd/UsdFileHandler.cpp +++ b/lib/lookdevXUsd/UsdFileHandler.cpp @@ -12,12 +12,11 @@ #include -#include +#include #include #include -#include namespace LookdevXUsd { @@ -44,68 +43,20 @@ std::string getRelativePath(const Ufe::AttributeFilename::Ptr& fnAttr, const std return path; } -// This function is basically: -// return prefix + middle + suffix; -// But in a way that allocates memory only once and keeps clang-tidy happy. -// See: performance-inefficient-string-concatenation in clang-tidy docs. -std::string buildFullPath(const std::string_view& prefix, - const std::string_view& middle, - const std::string_view& suffix) -{ - std::string path; - path.reserve(prefix.size() + middle.size() + suffix.size() + 1); - path.append(prefix); - path.append(middle); - path.append(suffix); - return path; -} - -std::string resolveUdimPath(const std::string& path, const Ufe::Attribute::Ptr& attribute) -{ - constexpr auto kUdimMin = 1001; - constexpr auto kUdimMax = 1100; - constexpr auto kUdimNumSize = 4; - constexpr std::string_view kUdimTag = ""; - - const auto udimPos = path.rfind(kUdimTag); - if (udimPos == std::string::npos) - { - return {}; - } - - const auto stage = MayaUsdAPI::usdStage(attribute); - if (!stage || !stage->GetEditTarget().GetLayer()) - { - return {}; - } - - // Going for minimal alloc code using string_view to the fullest :) - const auto udimPrefix = std::string_view{path.data(), udimPos}; - const auto udimEndPos = udimPos + kUdimTag.size(); - - const auto udimPostfix = - std::string_view{path.data() + static_cast(udimEndPos), path.size() - udimEndPos}; - - // Build numericPath once and re-use it in the loop: - auto numericPath = buildFullPath(udimPrefix, std::to_string(kUdimMin), udimPostfix); - const auto numericStart = numericPath.begin() + static_cast(udimPrefix.size()); - const auto numericEnd = numericStart + static_cast(kUdimNumSize); - - for (auto i = kUdimMin; i < kUdimMax; ++i) - { - numericPath.replace(numericStart, numericEnd, std::to_string(i)); - - const auto resolved = - PXR_NS::SdfComputeAssetPathRelativeToLayer(stage->GetEditTarget().GetLayer(), numericPath); - if (resolved.size() != numericPath.size()) - { - // Restore the tag in the resolved path: - const auto prefixLength = resolved.size() - udimPostfix.size() - kUdimNumSize; - return buildFullPath({resolved.data(), prefixLength}, kUdimTag, udimPostfix); +// From USD's materialParamsUtil.cpp: +// We need to find the first layer that changes the value +// of the parameter so that we anchor relative paths to that. +static +PXR_NS::SdfLayerHandle +findLayerHandle(const PXR_NS::UsdAttribute& attr, const PXR_NS::UsdTimeCode& time) { + for (const auto& spec: attr.GetPropertyStack(time)) { + if (spec->HasDefaultValue() || + spec->GetLayer()->GetNumTimeSamplesForPath( + spec->GetPath()) > 0) { + return spec->GetLayer(); } } - - return {}; + return PXR_NS::TfNullPtr; } } // namespace @@ -123,15 +74,20 @@ std::string UsdFileHandler::getResolvedPath(const Ufe::AttributeFilename::Ptr& f auto attributeType = MayaUsdAPI::usdAttributeType(fnAttr); if (attributeType == PXR_NS::SdfValueTypeNames->Asset) { - PXR_NS::VtValue vt; - if (MayaUsdAPI::getUsdValue(fnAttr, vt, MayaUsdAPI::getTime(fnAttr->sceneItem()->path())) && - vt.IsHolding()) + const auto prim = MayaUsdAPI::getPrimForUsdSceneItem(fnAttr->sceneItem()); + const auto usdAttribute = prim.GetAttribute(PXR_NS::TfToken(fnAttr->name())); + const auto attrQuery = PXR_NS::UsdAttributeQuery(usdAttribute); + const auto time = MayaUsdAPI::getTime(fnAttr->sceneItem()->path()); + PXR_NS::SdfAssetPath assetPath; + + if (attrQuery.Get(&assetPath, time)) { - const auto& assetPath = vt.UncheckedGet(); auto path = assetPath.GetResolvedPath(); - if (path.empty()) + if (path.empty() && PXR_NS::UsdShadeUdimUtils::IsUdimIdentifier(assetPath.GetAssetPath())) { - path = resolveUdimPath(assetPath.GetAssetPath(), fnAttr); + path = + PXR_NS::UsdShadeUdimUtils::ResolveUdimPath( + assetPath.GetAssetPath(), findLayerHandle(usdAttribute, time)); } #ifdef _WIN32 std::replace(path.begin(), path.end(), '\\', '/'); diff --git a/test/lib/ufe/CMakeLists.txt b/test/lib/ufe/CMakeLists.txt index bffa3d7283..ab6b81b9a0 100644 --- a/test/lib/ufe/CMakeLists.txt +++ b/test/lib/ufe/CMakeLists.txt @@ -158,6 +158,7 @@ if(LOOKDEVXUFE_HAS_PYTHON_BINDINGS) testLdxComponentConnections.py testLdxConnection.py testLdxDebugHandler.py + testLdxFileHandler.py ) endif() diff --git a/test/lib/ufe/testLdxFileHandler.py b/test/lib/ufe/testLdxFileHandler.py new file mode 100644 index 0000000000..7d71e90182 --- /dev/null +++ b/test/lib/ufe/testLdxFileHandler.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python + +# +# Copyright 2025 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 fixturesUtils +import mayaUtils +import testUtils +import ufeUtils + +from maya import cmds +from maya import standalone + +import ufe +from ufe.extensions import lookdevXUfe as lxufe +import mayaUsd.ufe + +from pxr import UsdGeom, UsdShade, Sdf, Usd, Gf, Vt + +import os +import unittest + +class LookdevXUfeDebugHandlerTestCase(unittest.TestCase): + '''Verify the USD implementation of LookdevXUfe DebugHandler. + ''' + pluginsLoaded = False + + @classmethod + def setUpClass(cls): + fixturesUtils.readOnlySetUpClass(__file__, loadPlugin=False) + + if not cls.pluginsLoaded: + cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded() + + @classmethod + def tearDownClass(cls): + standalone.uninitialize() + + @staticmethod + def loadUsdFile(fileName): + cmds.file(new=True, force=True) + testFile = testUtils.getTestScene('lookdevXUsd', fileName) + mayaUtils.createProxyFromFile(testFile) + + def test_DebugHandler(self): + self.loadUsdFile('RelativeReferencedUdimTextures.usda') + + uvTexturePathStr = "|stage|stageShape,/Def1/pPlane1/mtl/UsdPreviewSurface1/UsdUVTexture1" + + uvTextureItem = ufe.Hierarchy.createItem(ufe.PathString.path(uvTexturePathStr)) + self.assertTrue(uvTextureItem) + + # Exercise the File Handler + fileHandler = lxufe.FileHandler.get(uvTextureItem.runTimeId()) + + # Make sure the updated code for getResolvedPath is returning the correct value. + uvTextureAttrs = ufe.Attributes.attributes(uvTextureItem) + fileAttr = uvTextureAttrs.attribute("inputs:file") + + originalFilePath = fileAttr.get() + + resolvedFilePath = fileHandler.getResolvedPath(fileAttr) + folder = os.path.split(resolvedFilePath)[0] + self.assertTrue(os.path.exists(folder)) + self.assertTrue(os.path.isdir(folder)) + zeroUdimFile = resolvedFilePath.replace("", "1001") + self.assertTrue(os.path.exists(zeroUdimFile)) + self.assertTrue(os.path.isfile(zeroUdimFile)) + + cmd1 = fileHandler.convertPathToAbsoluteCmd(fileAttr) + self.assertTrue(cmd1) + cmd1.execute() + + absoluteFilePath = fileAttr.get() + self.assertEqual(resolvedFilePath, absoluteFilePath) + + cmd2 = fileHandler.convertPathToRelativeCmd(fileAttr) + self.assertTrue(cmd2) + cmd2.execute() + + relativeFilePath = fileAttr.get() + # Original path is relative to asset. New relative path is relative to edit layer. + self.assertNotEqual(originalFilePath, relativeFilePath) + # But still resolves to the same path. + self.assertEqual(resolvedFilePath, fileHandler.getResolvedPath(fileAttr)) + + # Undo back to absolute: + cmd2.undo() + self.assertEqual(absoluteFilePath, fileAttr.get()) + + # Undo back to original relative path: + cmd1.undo() + self.assertEqual(originalFilePath, fileAttr.get()) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/test/testSamples/lookdevXUsd/RelativeReferencedUdimTextures.usda b/test/testSamples/lookdevXUsd/RelativeReferencedUdimTextures.usda new file mode 100644 index 0000000000..623bccdd27 --- /dev/null +++ b/test/testSamples/lookdevXUsd/RelativeReferencedUdimTextures.usda @@ -0,0 +1,12 @@ +#usda 1.0 +( + metersPerUnit = 0.01 + upAxis = "Y" +) + +def "Def1" ( + prepend references = @assets/simplePlane/simplePlane.usda@ +) +{ +} + diff --git a/test/testSamples/lookdevXUsd/assets/simplePlane/simplePlane.usda b/test/testSamples/lookdevXUsd/assets/simplePlane/simplePlane.usda new file mode 100644 index 0000000000..f318b2622a --- /dev/null +++ b/test/testSamples/lookdevXUsd/assets/simplePlane/simplePlane.usda @@ -0,0 +1,88 @@ +#usda 1.0 +( + metersPerUnit = 0.01 + upAxis = "Y" + defaultPrim = "root" +) + +def Xform "root" +{ + + def Mesh "pPlane1" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, 0, -0.5), (0.5, 0, 0.5)] + int[] faceVertexCounts = [4] + int[] faceVertexIndices = [0, 1, 3, 2] + rel material:binding = + point3f[] points = [(-0.5, 0, 0.5), (0.5, 0, 0.5), (-0.5, 0, -0.5), (0.5, 0, -0.5)] + color3f[] primvars:displayColor = [(1, 1, 1)] ( + customData = { + dictionary Maya = { + bool generated = 1 + } + } + ) + texCoord2f[] primvars:st = [(0, 0), (1, 0), (0, 1), (1, 1)] ( + customData = { + dictionary Maya = { + token name = "map1" + } + } + interpolation = "faceVarying" + ) + int[] primvars:st:indices = [0, 1, 3, 2] + token visibility = "inherited" + + def Scope "mtl" + { + def Material "UsdPreviewSurface1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + customData = { + dictionary Autodesk = { + string ldx_inputPos = "-151 371" + string ldx_outputPos = "524 371" + } + } + ) + { + token outputs:surface.connect = + uniform float2 ui:nodegraph:node:pos = (0.055555556, 0.055555556) + + def Shader "UsdPreviewSurface1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "UsdPreviewSurface" + color3f inputs:diffuseColor.connect = + token outputs:surface + uniform float2 ui:nodegraph:node:pos = (1.2166667, 2.0611112) + } + + def Shader "UsdUVTexture1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "UsdUVTexture" + asset inputs:file = @textures/diffuse..jpg@ + float2 inputs:st.connect = + float3 outputs:rgb + uniform float2 ui:nodegraph:node:pos = (-0.06388889, 0.78333336) + } + + def Shader "UsdPrimvarReader1" ( + prepend apiSchemas = ["NodeGraphNodeAPI"] + ) + { + uniform token info:id = "UsdPrimvarReader_float2" + string inputs:varname = "st" + float2 outputs:result + uniform float2 ui:nodegraph:node:pos = (-1.425, 0.78333336) + } + } + } + + } +} \ No newline at end of file diff --git a/test/testSamples/lookdevXUsd/assets/simplePlane/textures/diffuse.1001.jpg b/test/testSamples/lookdevXUsd/assets/simplePlane/textures/diffuse.1001.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3f41d6cff878d3aad1d81562252b82084640df1e GIT binary patch literal 22647 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 z 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 literal 0 HcmV?d00001 From 10ca1811d5dca0ed0f27b027b0584df9f167fca1 Mon Sep 17 00:00:00 2001 From: Jerry Gamache Date: Thu, 27 Feb 2025 10:59:51 -0500 Subject: [PATCH 5/5] Fixed unit test name. --- test/lib/ufe/testLdxFileHandler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/lib/ufe/testLdxFileHandler.py b/test/lib/ufe/testLdxFileHandler.py index 7d71e90182..d5bf59867f 100644 --- a/test/lib/ufe/testLdxFileHandler.py +++ b/test/lib/ufe/testLdxFileHandler.py @@ -33,8 +33,8 @@ import os import unittest -class LookdevXUfeDebugHandlerTestCase(unittest.TestCase): - '''Verify the USD implementation of LookdevXUfe DebugHandler. +class LookdevXUfeFileHandlerTestCase(unittest.TestCase): + '''Verify the USD implementation of LookdevXUfe FileHandler. ''' pluginsLoaded = False @@ -55,7 +55,7 @@ def loadUsdFile(fileName): testFile = testUtils.getTestScene('lookdevXUsd', fileName) mayaUtils.createProxyFromFile(testFile) - def test_DebugHandler(self): + def test_FileHandler(self): self.loadUsdFile('RelativeReferencedUdimTextures.usda') uvTexturePathStr = "|stage|stageShape,/Def1/pPlane1/mtl/UsdPreviewSurface1/UsdUVTexture1"