diff --git a/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp b/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp index 77ed4e5c4a..42faa4c910 100644 --- a/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp +++ b/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp @@ -44,6 +44,7 @@ #include "pxr/usd/usdLux/cylinderLight.h" #include "pxr/usd/usdLux/nonboundableLightBase.h" #include "pxr/usd/usdLux/sphereLight.h" +#include "pxr/usd/usdLux/meshLightAPI.h" #include "pxr/usd/usd/schemaRegistry.h" @@ -645,7 +646,8 @@ void IECoreUSD::ShaderAlgo::writeLight( const IECoreScene::ShaderNetwork *shader pxr::TfType type = pxr::UsdSchemaRegistry::GetInstance().GetTypeFromName( pxr::TfToken( outputShader->getName() ) ); if( !type.IsA() && - !type.IsA() + !type.IsA() && + outputShader->getName() != "MeshLight" ) { IECore::msg( IECore::Msg::Warning, "ShaderAlgo::writeLight", "Shader `{}` is not a valid UsdLux light type", outputShader->getName() ); @@ -655,7 +657,14 @@ void IECoreUSD::ShaderAlgo::writeLight( const IECoreScene::ShaderNetwork *shader // Write the light itself onto the prim we've been given. ShaderMap usdShaders; - prim.SetTypeName( pxr::TfToken( outputShader->getName() ) ); + if( outputShader->getName() == "MeshLight" ) + { + pxr::UsdLuxMeshLightAPI::Apply( prim ); + } + else + { + prim.SetTypeName( pxr::TfToken( outputShader->getName() ) ); + } writeShaderParameterValues( outputShader, pxr::UsdShadeConnectableAPI( prim ) ); usdShaders[shaderNetwork->getOutput().shader] = pxr::UsdShadeConnectableAPI( prim ); diff --git a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py index 0a6c86995e..130954dd12 100644 --- a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py +++ b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py @@ -53,6 +53,7 @@ import pxr.Usd import pxr.UsdGeom +import pxr.UsdLux if pxr.Usd.GetVersion() < ( 0, 19, 3 ) : pxr.Usd.Attribute.HasAuthoredValue = pxr.Usd.Attribute.HasAuthoredValueOpinion @@ -3900,6 +3901,155 @@ def testWriteDomeLightFile( self ) : self.assertEqual( light.GetTextureFileAttr().Get(), "test.exr" ) self.assertEqual( light.GetTextureFormatAttr().Get(), "latlong" ) + @unittest.skipIf( pxr.Usd.GetVersion() < ( 0, 21, 11 ), "UsdLuxLightAPI not available" ) + def testWriteMeshLight( self ) : + + # Write to USD + + fileName = os.path.join( self.temporaryDirectory(), "meshLight.usda" ) + root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Write ) + + lightNetwork = IECoreScene.ShaderNetwork( + shaders = { + "output" : IECoreScene.Shader( "MeshLight", "light", {} ), + }, + output = "output", + ) + + meshLightObject = IECoreScene.MeshPrimitive.createPlane( + imath.Box2f( imath.V2f( 0, 0 ), imath.V2f( 1, 1 ) ), + imath.V2i( 16, 16 ) + ) + + meshLight = root.createChild( "meshLight" ) + meshLight.writeObject( meshLightObject, 0 ) + meshLight.writeAttribute( "light", lightNetwork, 0 ) + + del meshLight, root + + # Verify via USD API. + + stage = pxr.Usd.Stage.Open( fileName ) + prim = stage.GetPrimAtPath( "/meshLight" ) + + self.assertTrue( prim.IsValid() ) + self.assertTrue( prim.IsA( pxr.UsdGeom.Mesh ) ) + self.assertTrue( prim.HasAPI( pxr.UsdLux.MeshLightAPI ) ) + self.assertFalse( pxr.UsdShade.MaterialBindingAPI.ComputeBoundMaterials( [ prim ] )[0][0] ) + + @unittest.skipIf( pxr.Usd.GetVersion() < ( 0, 21, 11 ), "UsdLuxLightAPI not available" ) + def testReadMeshLight( self ) : + + fileName = os.path.dirname( __file__ ) + "/data/meshLight.usda" + root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Read ) + meshLightXForm = root.child( "meshLight" ) + + self.assertEqual( meshLightXForm.attributeNames(), [] ) + self.assertEqual( meshLightXForm.childNames(), ["meshLightMesh"] ) + + meshLight = meshLightXForm.child( "meshLightMesh" ) + + meshLightObject = meshLight.readObject( 0 ) + self.assertIsInstance( meshLightObject, IECoreScene.MeshPrimitive ) + self.assertTrue( meshLightObject.arePrimitiveVariablesValid() ) + self.assertEqual( len( meshLightObject["P"].data ), 8 ) + + self.assertTrue( meshLight.hasAttribute( "light" ) ) + network = meshLight.readAttribute( "light", 0 ) + + self.assertEqual( + network, + IECoreScene.ShaderNetwork( + shaders = { + "meshLightMesh" : IECoreScene.Shader( + "MeshLight", + "light", + { + "color" : IECore.Color3fData( imath.Color3f( 0, 1, 0 ) ), + "intensity" : IECore.FloatData( 2.0 ), + } + ), + }, + output = "meshLightMesh" + ) + ) + + self.assertTrue( meshLight.hasAttribute( "surface" ) ) + network = meshLight.readAttribute( "surface", 0 ) + + self.assertEqual( + network, + IECoreScene.ShaderNetwork( + shaders = { + "previewSurface" : IECoreScene.Shader( + "UsdPreviewSurface", + "surface", + { + "diffuseColor" : IECore.Color3fData( imath.Color3f( 0, 1, 0 ) ), + "roughness" : IECore.FloatData( 0.5 ), + } + ), + }, + output = ( "previewSurface", "surface" ), + ) + ) + + self.assertIn( "__lights", root.setNames() ) + self.assertEqual( root.readSet( "__lights" ), IECore.PathMatcher( [ "/meshLight/meshLightMesh" ] ) ) + + @unittest.skipIf( pxr.Usd.GetVersion() < ( 0, 21, 11 ), "UsdLuxLightAPI not available" ) + def testRoundTripMeshLight( self ) : + + # Write to USD + + fileName = os.path.join( self.temporaryDirectory(), "meshLight.usda" ) + root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Write ) + + lightNetwork = IECoreScene.ShaderNetwork( + shaders = { + "output" : IECoreScene.Shader( "MeshLight", "light", {} ), + }, + output = "output", + ) + surfaceNetwork = IECoreScene.ShaderNetwork( + shaders = { + "output" : IECoreScene.Shader( "UsdPreviewSurface", "surface", {} ), + "texture" : IECoreScene.Shader( "UsdUVTexture", "surface", {} ), + }, + output = "output", + connections = [ ( ( "texture", "rgb" ), ( "output", "glowColor" ) ) ], + ) + + meshLightObject = IECoreScene.MeshPrimitive.createPlane( + imath.Box2f( imath.V2f( 0, 0 ), imath.V2f( 1, 1 ) ), + imath.V2i( 16, 16 ) + ) + + meshLight = root.createChild( "meshLight" ) + meshLight.writeObject( meshLightObject, 0 ) + meshLight.writeAttribute( "light", lightNetwork, 0 ) + meshLight.writeAttribute( "surface", surfaceNetwork, 0 ) + + root.writeSet( "__lights", IECore.PathMatcher( [ "/meshLight" ] ) ) + + del meshLight, root + + root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Read ) + + meshLight = root.child( "meshLight" ) + self.assertIn( "light", meshLight.attributeNames() ) + + # The Cortex shader handle is lost when converting to USD because we are only attaching + # the `MeshLightAPI` to the prim. Therefore the shader networks will not be equal, but we + # can check that the output shaders are equal. + self.assertEqual( meshLight.readAttribute( "light", 0 ).outputShader(), lightNetwork.outputShader() ) + + # The surface shader networks should be equivalent + self.assertEqual( meshLight.readAttribute( "surface", 0 ), surfaceNetwork ) + + self.assertIn( "__lights", root.setNames() ) + self.assertEqual( root.readSet( "__lights" ), IECore.PathMatcher( [ "/meshLight" ] ) ) + def testPointInstancerPrimvars( self ) : # Use the USD API to author a point instancer with primvars on it. diff --git a/contrib/IECoreUSD/test/IECoreUSD/data/meshLight.usda b/contrib/IECoreUSD/test/IECoreUSD/data/meshLight.usda new file mode 100644 index 0000000000..333a3feba3 --- /dev/null +++ b/contrib/IECoreUSD/test/IECoreUSD/data/meshLight.usda @@ -0,0 +1,35 @@ +#usda 1.0 + +def Xform "meshLight" ( +) +{ + def Material "previewMaterial" + { + token outputs:surface.connect = + + def Shader "previewSurface" + { + uniform token info:id = "UsdPreviewSurface" + color3f inputs:diffuseColor = (0, 1, 0) + float inputs:roughness = 0.5 + token outputs:surface + } + } + + def Mesh "meshLightMesh" ( + prepend apiSchemas = ["MeshLightAPI", "MaterialBindingAPI"] + ) + { + rel material:binding = + + color3f inputs:color = (0, 1, 0) + float inputs:intensity = 2.0 + + # Cube + float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)] + int[] faceVertexCounts = [4, 4, 4, 4, 4, 4] + int[] faceVertexIndices = [0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 1, 0, 1, 7, 5, 3, 6, 0, 2, 4] + point3f[] points = [(-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (-0.5, 0.5, 0.5), (0.5, 0.5, 0.5), (-0.5, 0.5, -0.5), (0.5, 0.5, -0.5), (-0.5, -0.5, -0.5), (0.5, -0.5, -0.5)] + + } +} \ No newline at end of file