class TaskMutexTest(GafferTest.TestCase): @unittest.skipIf( GafferTest.inCI(), "Causes intermittent failures in CI due to limited core availability") def test(self): GafferTest.testTaskMutex() def testWithinIsolate(self): GafferTest.testTaskMutexWithinIsolate() @unittest.skipIf( GafferTest.inCI(), "Causes intermittent failures in CI due to limited core availability") def testJoiningOuterTasks(self): GafferTest.testTaskMutexJoiningOuterTasks() @GafferTest.TestRunner.PerformanceTestMethod() def testHeavyContentionWithWorkAcceptance(self): GafferTest.testTaskMutexHeavyContention(True) @GafferTest.TestRunner.PerformanceTestMethod() def testHeavyContentionWithoutWorkAcceptance(self): GafferTest.testTaskMutexHeavyContention(False) def testWorkerRecursion(self): GafferTest.testTaskMutexWorkerRecursion() def testAcquireOr(self): GafferTest.testTaskMutexAcquireOr() def testExceptions(self): GafferTest.testTaskMutexExceptions() def testWorkerExceptions(self): GafferTest.testTaskMutexWorkerExceptions() def testDontSilentlyCancel(self): GafferTest.testTaskMutexDontSilentlyCancel() def testCancellation(self): GafferTest.testTaskMutexCancellation()
def testCancellation(self): c = GafferImage.Constant() r = GafferImage.Resample() r["in"].setInput(c["out"]) r["filterScale"].setValue(imath.V2f(2000)) bt = Gaffer.ParallelAlgo.callOnBackgroundThread( r["out"], lambda: GafferImageTest.processTiles(r["out"])) # Give background tasks time to get into full swing time.sleep(0.1) # Check that we can cancel them in reasonable time acceptableCancellationDelay = 4.0 if GafferTest.inCI() else 0.25 t = time.time() bt.cancelAndWait() self.assertLess(time.time() - t, acceptableCancellationDelay) # Check that we can do the same when using a non-separable filter r["filter"].setValue("disk") bt = Gaffer.ParallelAlgo.callOnBackgroundThread( r["out"], lambda: GafferImageTest.processTiles(r["out"])) time.sleep(0.1) t = time.time() bt.cancelAndWait() self.assertLess(time.time() - t, acceptableCancellationDelay)
def testCancellation(self): c = GafferImage.Constant() m = GafferImage.Median() m["in"].setInput(c["out"]) m["radius"].setValue(imath.V2i(2000)) bt = Gaffer.ParallelAlgo.callOnBackgroundThread( m["out"], lambda: GafferImageTest.processTiles(m["out"])) # Give background tasks time to get into full swing time.sleep(0.1) # Check that we can cancel them in reasonable time acceptableCancellationDelay = 4.0 if GafferTest.inCI() else 0.25 t = time.time() bt.cancelAndWait() self.assertLess(time.time() - t, acceptableCancellationDelay) # Check that we can do the same when using a master # channel. m["masterChannel"].setValue("R") bt = Gaffer.ParallelAlgo.callOnBackgroundThread( m["out"], lambda: GafferImageTest.processTiles(m["out"])) time.sleep(0.1) t = time.time() bt.cancelAndWait() self.assertLess(time.time() - t, acceptableCancellationDelay)
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # ########################################################################## import os import unittest import Gaffer import GafferTest import GafferScene import GafferSceneTest import GafferOSL import GafferDelight @unittest.skipIf(GafferTest.inCI(), "No license available in cloud") class InteractiveDelightRenderTest(GafferSceneTest.InteractiveRenderTest): # Temporarily disable this test (which is implemented in the # base class) because it fails. The issue is that we're automatically # instancing the geometry for the two lights, and that appears to # trigger a bug in 3delight where the sampling goes awry. @unittest.skip("Awaiting feedback from 3delight developers") def testAddLight(self): pass # Disable this test for now as we don't have light linking support in # 3Delight, yet. @unittest.skip("No light linking support just yet") def testLightLinking(self):
import unittest import imath import inspect import IECore import IECoreScene import IECoreGL import Gaffer import GafferTest import GafferImage import GafferScene import GafferSceneTest @unittest.skipIf(GafferTest.inCI(), "OpenGL not set up") class OpenGLShaderTest(GafferSceneTest.SceneTestCase): def test(self): s = GafferScene.OpenGLShader() s.loadShader("Texture") self.assertEqual(len(s["parameters"]), 3) self.assertTrue(isinstance(s["parameters"]["mult"], Gaffer.FloatPlug)) self.assertTrue(isinstance(s["parameters"]["tint"], Gaffer.Color4fPlug)) self.assertTrue( isinstance(s["parameters"]["texture"], GafferImage.ImagePlug)) s["parameters"]["mult"].setValue(0.5) s["parameters"]["tint"].setValue(imath.Color4f(1, 0.5, 0.25, 1))
import Gaffer import GafferArnold import GafferImage import GafferImageUI import GafferScene import GafferSceneTest import GafferTest import GafferUI import GafferUITest import GafferImageTest from Qt import QtCore @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") class InteractiveArnoldRenderPerformanceTest(GafferUITest.TestCase): # Arnold outputs licensing warnings that would cause failures failureMessageLevel = IECore.MessageHandler.Level.Error def runInteractive(self, useUI, useBlur, resolution): script = Gaffer.ScriptNode() script["Camera"] = GafferScene.Camera() script["Camera"]["transform"]["translate"]["z"].setValue(6) script["Sphere"] = GafferScene.Sphere("Sphere") script["Sphere"]["radius"].setValue(10)
class ArnoldTextureBakeTest( GafferSceneTest.SceneTestCase ) : class SimpleEdgeDetect( GafferImage.ImageProcessor ): def __init__( self, name = "SimpleEdgeDetect" ) : GafferImage.ImageProcessor.__init__( self, name ) self["HorizTransform"] = GafferImage.ImageTransform() self["HorizTransform"]["in"].setInput( self["in"] ) self["HorizTransform"]["transform"]["translate"].setValue( imath.V2f( 1, 0 ) ) self["HorizDiff"] = GafferImage.Merge() self["HorizDiff"]["in"]["in0"].setInput( self["HorizTransform"]["out"] ) self["HorizDiff"]["in"]["in1"].setInput( self["in"] ) self["HorizDiff"]["operation"].setValue( 10 ) self["VertTransform"] = GafferImage.ImageTransform() self["VertTransform"]["in"].setInput( self["in"] ) self["VertTransform"]["transform"]["translate"].setValue( imath.V2f( 0, 1 ) ) self["VertDiff"] = GafferImage.Merge() self["VertDiff"]["in"]["in0"].setInput( self["VertTransform"]["out"] ) self["VertDiff"]["in"]["in1"].setInput( self["in"] ) self["VertDiff"]["operation"].setValue( 10 ) self["Max"] = GafferImage.Merge() self["Max"]["in"]["in0"].setInput( self["HorizDiff"]["out"] ) self["Max"]["in"]["in1"].setInput( self["VertDiff"]["out"] ) self["Max"]["operation"].setValue( 13 ) self["out"].setInput( self["Max"]["out"] ) self["out"].setFlags( Gaffer.Plug.Flags.Serialisable, False ) def testManyImages( self ): allFilter = GafferScene.PathFilter() allFilter["paths"].setValue( IECore.StringVectorData( [ '/...' ] ) ) sphere = GafferScene.Sphere() sphere["transform"]["translate"].setValue( imath.V3f( -3, 0, 0 ) ) standardSurface = GafferArnold.ArnoldShader() standardSurface.loadShader( "standard_surface" ) shaderAssignment = GafferScene.ShaderAssignment() shaderAssignment["in"].setInput( sphere["out"] ) shaderAssignment["filter"].setInput( allFilter["out"] ) shaderAssignment["shader"].setInput( standardSurface["out"] ) uvScaleCode = GafferOSL.OSLCode() uvScaleCode["out"].addChild( Gaffer.V3fPlug( "uvScaled", direction = Gaffer.Plug.Direction.Out ) ) uvScaleCode["code"].setValue( 'uvScaled = vector( u * 2, v * 2, 0 );' ) outUV = GafferOSL.OSLShader() outUV.loadShader( "ObjectProcessing/OutUV" ) outUV["parameters"]["value"].setInput( uvScaleCode["out"]["uvScaled"] ) outObject2 = GafferOSL.OSLShader() outObject2.loadShader( "ObjectProcessing/OutObject" ) outObject2["parameters"]["in0"].setInput( outUV["out"]["primitiveVariable"] ) uvScaleOSL = GafferOSL.OSLObject() uvScaleOSL["in"].setInput( shaderAssignment["out"] ) uvScaleOSL["filter"].setInput( allFilter["out"] ) uvScaleOSL["shader"].setInput( outObject2["out"]["out"] ) uvScaleOSL["interpolation"].setValue( 5 ) mapOffset = GafferScene.MapOffset() mapOffset["in"].setInput( uvScaleOSL["out"] ) mapOffset["filter"].setInput( allFilter["out"] ) mapOffset["udim"].setValue( 1033 ) offsetGroup = GafferScene.Group() offsetGroup["in"]["in0"].setInput( mapOffset["out"] ) offsetGroup["name"].setValue( 'offset' ) offsetGroup["transform"]["translate"].setValue( imath.V3f( 6, 0, 3 ) ) combineGroup = GafferScene.Group() combineGroup["in"]["in0"].setInput( uvScaleOSL["out"] ) combineGroup["in"]["in1"].setInput( offsetGroup["out"] ) lights = [] for color, rotate in [ ( ( 1, 0, 0 ), ( 0, 0, 0) ), ( ( 0, 1, 0 ), ( 0, 90, 0 ) ), ( ( 0, 0, 1 ), ( -90, 0, 0 ) ) ] : light = GafferArnold.ArnoldLight() light.loadShader( "distant_light" ) light["parameters"]["color"].setValue( imath.Color3f( *color ) ) light["transform"]["rotate"].setValue( imath.V3f( *rotate ) ) combineGroup["in"][-1].setInput( light["out"] ) lights.append( light ) arnoldTextureBake = GafferArnold.ArnoldTextureBake() arnoldTextureBake["in"].setInput( combineGroup["out"] ) arnoldTextureBake["filter"].setInput( allFilter["out"] ) arnoldTextureBake["bakeDirectory"].setValue( self.temporaryDirectory() + '/bakeSpheres/' ) arnoldTextureBake["defaultResolution"].setValue( 32 ) arnoldTextureBake["aovs"].setValue( 'beauty:RGBA diffuse:diffuse' ) arnoldTextureBake["tasks"].setValue( 3 ) arnoldTextureBake["cleanupIntermediateFiles"].setValue( True ) # Dispatch the bake script = Gaffer.ScriptNode() script.addChild( arnoldTextureBake ) dispatcher = GafferDispatch.LocalDispatcher() dispatcher["jobsDirectory"].setValue( self.temporaryDirectory() ) dispatcher.dispatch( [ arnoldTextureBake ] ) # Test that we are writing all expected files, and that we have cleaned up all temp files expectedUdims = [ i + j for j in [ 1001, 1033 ] for i in [ 0, 1, 10, 11 ] ] self.assertEqual( sorted( os.listdir( self.temporaryDirectory() + '/bakeSpheres/' ) ), [ "beauty", "diffuse" ] ) self.assertEqual( sorted( os.listdir( self.temporaryDirectory() + '/bakeSpheres/beauty' ) ), [ "beauty.%i.tx"%i for i in expectedUdims ] ) self.assertEqual( sorted( os.listdir( self.temporaryDirectory() + '/bakeSpheres/diffuse' ) ), [ "diffuse.%i.tx"%i for i in expectedUdims ] ) # Read back in the 4 udim tiles of a sphere reader = GafferImage.ImageReader() imageTransform = GafferImage.ImageTransform() imageTransform["in"].setInput( reader["out"] ) exprBox = Gaffer.Box() expression = Gaffer.Expression() exprBox.addChild( reader ) exprBox.addChild( imageTransform ) exprBox.addChild( expression ) expression.setExpression( inspect.cleandoc( """ i = context.get( "loop:index", 0 ) layer = context.get( "collect:layerName", "beauty" ) x = i % 2 y = i // 2 parent["ImageReader"]["fileName"] = '""" + self.temporaryDirectory() + """/bakeSpheres/%s/%s.%i.tx' % ( layer, layer, 1001 + x + y * 10 ) parent["ImageTransform"]["transform"]["translate"] = imath.V2f( 32 * x, 32 * y ) """ ), "python" ) udimLoop = Gaffer.Loop() udimLoop.setup( GafferImage.ImagePlug() ) udimLoop["iterations"].setValue( 4 ) udimMerge = GafferImage.Merge() udimMerge["in"]["in0"].setInput( imageTransform["out"] ) udimMerge["in"]["in1"].setInput( udimLoop["previous"] ) udimLoop["next"].setInput( udimMerge["out"] ) aovCollect = GafferImage.CollectImages() aovCollect["in"].setInput( udimLoop["out"] ) aovCollect["rootLayers"].setValue( IECore.StringVectorData( [ 'beauty', 'diffuse' ] ) ) # We have a little reference image for how the diffuse should look imageReaderRef = GafferImage.ImageReader() imageReaderRef["fileName"].setValue( os.path.dirname( __file__ ) + "/images/sphereLightBake.exr" ) resizeRef = GafferImage.Resize() resizeRef["in"].setInput( imageReaderRef["out"] ) resizeRef["format"].setValue( GafferImage.Format( 64, 64, 1.000 ) ) shuffleRef = GafferImage.Shuffle() shuffleRef["in"].setInput( resizeRef["out"] ) for layer in [ "beauty", "diffuse" ]: for channel in [ "R", "G", "B" ]: shuffleRef["channels"].addChild( GafferImage.Shuffle.ChannelPlug() ) shuffleRef["channels"][-1]["in"].setValue( channel ) shuffleRef["channels"][-1]["out"].setValue( layer + "." + channel ) differenceMerge = GafferImage.Merge() differenceMerge["in"]["in0"].setInput( aovCollect["out"] ) differenceMerge["in"]["in1"].setInput( shuffleRef["out"] ) differenceMerge["operation"].setValue( GafferImage.Merge.Operation.Difference ) stats = GafferImage.ImageStats() stats["in"].setInput( differenceMerge["out"] ) stats["area"].setValue( imath.Box2i( imath.V2i( 0, 0 ), imath.V2i( 64, 64 ) ) ) # We should get a very close match to our single tile low res reference bake stats["channels"].setValue( IECore.StringVectorData( [ 'diffuse.R', 'diffuse.G', 'diffuse.B', 'diffuse.A' ] ) ) for i in range( 3 ): self.assertLess( stats["average"].getValue()[i], 0.002 ) self.assertLess( stats["max"].getValue()[i], 0.02 ) # The beauty should be mostly a close match, but with a high max difference due to the spec pings stats["channels"].setValue( IECore.StringVectorData( [ 'beauty.R', 'beauty.G', 'beauty.B', 'beauty.A' ] ) ) for i in range( 3 ): self.assertLess( stats["average"].getValue()[i], 0.1 ) self.assertGreater( stats["max"].getValue()[i], 0.3 ) def testTasks( self ): allFilter = GafferScene.PathFilter() allFilter["paths"].setValue( IECore.StringVectorData( [ '/...' ] ) ) sphere = GafferScene.Sphere() sphere["transform"]["translate"].setValue( imath.V3f( -3, 0, 0 ) ) uvScaleCode = GafferOSL.OSLCode() uvScaleCode["out"].addChild( Gaffer.V3fPlug( "uvScaled", direction = Gaffer.Plug.Direction.Out ) ) uvScaleCode["code"].setValue( 'uvScaled = vector( u * 2, v * 2, 0 );' ) outUV = GafferOSL.OSLShader() outUV.loadShader( "ObjectProcessing/OutUV" ) outUV["parameters"]["value"].setInput( uvScaleCode["out"]["uvScaled"] ) outObject2 = GafferOSL.OSLShader() outObject2.loadShader( "ObjectProcessing/OutObject" ) outObject2["parameters"]["in0"].setInput( outUV["out"]["primitiveVariable"] ) uvScaleOSL = GafferOSL.OSLObject() uvScaleOSL["in"].setInput( sphere["out"] ) uvScaleOSL["filter"].setInput( allFilter["out"] ) uvScaleOSL["shader"].setInput( outObject2["out"]["out"] ) uvScaleOSL["interpolation"].setValue( 5 ) mapOffset = GafferScene.MapOffset() mapOffset["in"].setInput( uvScaleOSL["out"] ) mapOffset["filter"].setInput( allFilter["out"] ) mapOffset["udim"].setValue( 1033 ) offsetGroup = GafferScene.Group() offsetGroup["in"]["in0"].setInput( mapOffset["out"] ) offsetGroup["name"].setValue( 'offset' ) offsetGroup["transform"]["translate"].setValue( imath.V3f( 6, 0, 3 ) ) combineGroup = GafferScene.Group() combineGroup["in"]["in0"].setInput( uvScaleOSL["out"] ) combineGroup["in"]["in1"].setInput( offsetGroup["out"] ) arnoldTextureBake = GafferArnold.ArnoldTextureBake() arnoldTextureBake["in"].setInput( combineGroup["out"] ) arnoldTextureBake["filter"].setInput( allFilter["out"] ) arnoldTextureBake["bakeDirectory"].setValue( self.temporaryDirectory() + '/bakeSpheres/' ) arnoldTextureBake["defaultResolution"].setValue( 1 ) arnoldTextureBake["aovs"].setValue( 'beauty:RGBA diffuse:diffuse' ) arnoldTextureBake["tasks"].setValue( 3 ) arnoldTextureBake["cleanupIntermediateFiles"].setValue( False ) # Dispatch the bake script = Gaffer.ScriptNode() script.addChild( arnoldTextureBake ) dispatcher = GafferDispatch.LocalDispatcher() dispatcher["jobsDirectory"].setValue( self.temporaryDirectory() ) dispatcher.dispatch( [ arnoldTextureBake ] ) self.assertEqual( sorted( os.listdir( self.temporaryDirectory() + '/bakeSpheres/' ) ), [ "BAKE_FILE_INDEX_0.0001.txt", "BAKE_FILE_INDEX_1.0001.txt", "BAKE_FILE_INDEX_2.0001.txt", "beauty", "diffuse" ] ) # Make sure the 16 images that need writing get divided into very approximate thirds for i in range( 3 ): l = len( open( self.temporaryDirectory() + '/bakeSpheres/BAKE_FILE_INDEX_%i.0001.txt'%i ).readlines() ) self.assertGreater( l, 2 ) self.assertLess( l, 8 ) @unittest.skipIf( GafferTest.inCI() or os.environ.get( "ARNOLD_LICENSE_ORDER" ) == "none", "Arnold license not available" ) def testMerging( self ): allFilter = GafferScene.PathFilter() allFilter["paths"].setValue( IECore.StringVectorData( [ '/...' ] ) ) plane = GafferScene.Plane() plane["divisions"].setValue( imath.V2i( 20, 20 ) ) # Assign a basic gradient shader uvGradientCode = GafferOSL.OSLCode() uvGradientCode["out"].addChild( Gaffer.Color3fPlug( "out", direction = Gaffer.Plug.Direction.Out ) ) uvGradientCode["code"].setValue( 'out = color( u, v, 0.5 );' ) shaderAssignment = GafferScene.ShaderAssignment() shaderAssignment["in"].setInput( plane["out"] ) shaderAssignment["filter"].setInput( allFilter["out"] ) shaderAssignment["shader"].setInput( uvGradientCode["out"]["out"] ) # Set up a random id from 0 - 3 on each face randomCode = GafferOSL.OSLCode() randomCode["out"].addChild( Gaffer.IntPlug( "randomId", direction = Gaffer.Plug.Direction.Out ) ) randomCode["code"].setValue( 'randomId = int(cellnoise( P * 100 ) * 4);' ) outInt = GafferOSL.OSLShader() outInt.loadShader( "ObjectProcessing/OutInt" ) outInt["parameters"]["name"].setValue( 'randomId' ) outInt["parameters"]["value"].setInput( randomCode["out"]["randomId"] ) outObject = GafferOSL.OSLShader() outObject.loadShader( "ObjectProcessing/OutObject" ) outObject["parameters"]["in0"].setInput( outInt["out"]["primitiveVariable"] ) oSLObject = GafferOSL.OSLObject() oSLObject["in"].setInput( shaderAssignment["out"] ) oSLObject["filter"].setInput( allFilter["out"] ) oSLObject["shader"].setInput( outObject["out"]["out"] ) oSLObject["interpolation"].setValue( 2 ) # Create 4 meshes by picking each of the 4 ids deleteContextVariables = Gaffer.DeleteContextVariables() deleteContextVariables.setup( GafferScene.ScenePlug() ) deleteContextVariables["variables"].setValue( 'collect:rootName' ) deleteContextVariables["in"].setInput( oSLObject["out"] ) pickCode = GafferOSL.OSLCode() pickCode["parameters"].addChild( Gaffer.IntPlug( "targetId" ) ) pickCode["out"].addChild( Gaffer.IntPlug( "cull", direction = Gaffer.Plug.Direction.Out ) ) pickCode["code"].setValue( 'int randomId; getattribute( "randomId", randomId ); cull = randomId != targetId;' ) expression = Gaffer.Expression() pickCode.addChild( expression ) expression.setExpression( 'parent.parameters.targetId = stoi( context( "collect:rootName", "0" ) );', "OSL" ) outInt1 = GafferOSL.OSLShader() outInt1.loadShader( "ObjectProcessing/OutInt" ) outInt1["parameters"]["name"].setValue( 'deleteFaces' ) outInt1["parameters"]["value"].setInput( pickCode["out"]["cull"] ) outObject1 = GafferOSL.OSLShader() outObject1.loadShader( "ObjectProcessing/OutObject" ) outObject1["parameters"]["in0"].setInput( outInt1["out"]["primitiveVariable"] ) oSLObject1 = GafferOSL.OSLObject() oSLObject1["in"].setInput( deleteContextVariables["out"] ) oSLObject1["filter"].setInput( allFilter["out"] ) oSLObject1["shader"].setInput( outObject1["out"]["out"] ) oSLObject1["interpolation"].setValue( 2 ) deleteFaces = GafferScene.DeleteFaces() deleteFaces["in"].setInput( oSLObject1["out"] ) deleteFaces["filter"].setInput( allFilter["out"] ) collectScenes = GafferScene.CollectScenes() collectScenes["in"].setInput( deleteFaces["out"] ) collectScenes["rootNames"].setValue( IECore.StringVectorData( [ '0', '1', '2', '3' ] ) ) collectScenes["sourceRoot"].setValue( '/plane' ) # First variant: bake everything, covering the whole 1001 UDIM customAttributes1 = GafferScene.CustomAttributes() customAttributes1["attributes"].addChild( Gaffer.NameValuePlug( 'bake:fileName', IECore.StringData( '${bakeDirectory}/complete/<AOV>/<AOV>.<UDIM>.exr' ) ) ) customAttributes1["in"].setInput( collectScenes["out"] ) # Second vaiant: bake just 2 of the 4 meshes, leaving lots of holes that will need filling pruneFilter = GafferScene.PathFilter() pruneFilter["paths"].setValue( IECore.StringVectorData( [ '/2', '/3' ] ) ) prune = GafferScene.Prune() prune["in"].setInput( collectScenes["out"] ) prune["filter"].setInput( pruneFilter["out"] ) customAttributes2 = GafferScene.CustomAttributes() customAttributes2["attributes"].addChild( Gaffer.NameValuePlug( 'bake:fileName', IECore.StringData( '${bakeDirectory}/incomplete/<AOV>/<AOV>.<UDIM>.exr' ) ) ) customAttributes2["in"].setInput( prune["out"] ) # Third variant: bake everything, but with one mesh at a higher resolution customAttributes3 = GafferScene.CustomAttributes() customAttributes3["attributes"].addChild( Gaffer.NameValuePlug( 'bake:fileName', IECore.StringData( '${bakeDirectory}/mismatch/<AOV>/<AOV>.<UDIM>.exr' ) ) ) customAttributes3["in"].setInput( collectScenes["out"] ) pathFilter2 = GafferScene.PathFilter() pathFilter2["paths"].setValue( IECore.StringVectorData( [ '/2' ] ) ) customAttributes = GafferScene.CustomAttributes() customAttributes["attributes"].addChild( Gaffer.NameValuePlug( 'bake:resolution', IECore.IntData( 200 ) ) ) customAttributes["filter"].setInput( pathFilter2["out"] ) customAttributes["in"].setInput( customAttributes3["out"] ) # Merge the 3 variants mergeGroup = GafferScene.Group() mergeGroup["in"][-1].setInput( customAttributes["out"] ) mergeGroup["in"][-1].setInput( customAttributes1["out"] ) mergeGroup["in"][-1].setInput( customAttributes2["out"] ) arnoldTextureBake = GafferArnold.ArnoldTextureBake() arnoldTextureBake["in"].setInput( mergeGroup["out"] ) arnoldTextureBake["filter"].setInput( allFilter["out"] ) arnoldTextureBake["bakeDirectory"].setValue( self.temporaryDirectory() + '/bakeMerge/' ) arnoldTextureBake["defaultResolution"].setValue( 128 ) # We want to check the intermediate results arnoldTextureBake["cleanupIntermediateFiles"].setValue( False ) # Dispatch the bake script = Gaffer.ScriptNode() script.addChild( arnoldTextureBake ) dispatcher = GafferDispatch.LocalDispatcher() dispatcher["jobsDirectory"].setValue( self.temporaryDirectory() ) dispatcher.dispatch( [ arnoldTextureBake ] ) # Check results imageReader = GafferImage.ImageReader() outLayer = GafferOSL.OSLShader() outLayer.loadShader( "ImageProcessing/OutLayer" ) outLayer["parameters"]["layerColor"].setInput( uvGradientCode["out"]["out"] ) outImage = GafferOSL.OSLShader() outImage.loadShader( "ImageProcessing/OutImage" ) outImage["parameters"]["in0"].setInput( outLayer["out"]["layer"] ) oSLImage = GafferOSL.OSLImage() oSLImage["in"].setInput( imageReader["out"] ) oSLImage["shader"].setInput( outImage["out"]["out"] ) merge3 = GafferImage.Merge() merge3["in"]["in0"].setInput( oSLImage["out"] ) merge3["in"]["in1"].setInput( imageReader["out"] ) merge3["operation"].setValue( 10 ) edgeDetect = self.SimpleEdgeDetect() edgeDetect["in"].setInput( imageReader["out"] ) edgeStats = GafferImage.ImageStats() edgeStats["in"].setInput( edgeDetect["out"] ) refDiffStats = GafferImage.ImageStats() refDiffStats["in"].setInput( merge3["out"] ) oneLayerReader = GafferImage.ImageReader() grade = GafferImage.Grade() grade["in"].setInput( oneLayerReader["out"] ) grade["channels"].setValue( '[A]' ) grade["blackPoint"].setValue( imath.Color4f( 0, 0, 0, 0.999899983 ) ) copyChannels = GafferImage.CopyChannels() copyChannels["in"]["in0"].setInput( merge3["out"] ) copyChannels["in"]["in1"].setInput( grade["out"] ) copyChannels["channels"].setValue( '[A]' ) premultiply = GafferImage.Premultiply() premultiply["in"].setInput( copyChannels["out"] ) refDiffCoveredStats = GafferImage.ImageStats() refDiffCoveredStats["in"].setInput( premultiply["out"] ) # We are testing 3 different cases: # complete : Should be an exact match. # incomplete : Expect some mild variance of slopes and some error, because we have to # reconstruct a lot of missing data. # mismatch : We should get a larger image, sized to the highest override on any mesh. # Match won't be as perfect, because we're combining source images at # different resolutions for name, expectedSize, maxEdge, maxRefDiff, maxMaskedDiff in [ ( "complete", 128, 0.01, 0.000001, 0.000001 ), ( "incomplete", 128, 0.05, 0.15, 0.000001 ), ( "mismatch", 200, 0.01, 0.01, 0.01 ) ]: imageReader["fileName"].setValue( self.temporaryDirectory() + "/bakeMerge/" + name + "/beauty/beauty.1001.tx" ) oneLayerReader["fileName"].setValue( self.temporaryDirectory() + "/bakeMerge/" + name + "/beauty/beauty.1001.exr" ) self.assertEqual( imageReader["out"]["format"].getValue().width(), expectedSize ) self.assertEqual( imageReader["out"]["format"].getValue().height(), expectedSize ) edgeStats["area"].setValue( imath.Box2i( imath.V2i( 1 ), imath.V2i( expectedSize - 1 ) ) ) refDiffStats["area"].setValue( imath.Box2i( imath.V2i( 1 ), imath.V2i( expectedSize - 1 ) ) ) refDiffCoveredStats["area"].setValue( imath.Box2i( imath.V2i( 0 ), imath.V2i( expectedSize ) ) ) # Blue channel is constant, so everything should line up perfectly self.assertEqual( 0, edgeStats["max"].getValue()[2] ) self.assertEqual( 0, refDiffStats["max"].getValue()[2] ) self.assertEqual( 0, refDiffCoveredStats["max"].getValue()[2] ) for i in range(2): # Make sure we've got actual data, by checking that we have some error ( we're not expecting # to perfectly reconstruct the gradient when the input is incomplete ) self.assertGreater( edgeStats["max"].getValue()[i], 0.005 ) if name == "incomplete": self.assertGreater( edgeStats["max"].getValue()[i], 0.03 ) self.assertGreater( refDiffStats["max"].getValue()[i], 0.06 ) self.assertLess( edgeStats["max"].getValue()[i], maxEdge ) self.assertLess( refDiffStats["max"].getValue()[i], maxRefDiff ) self.assertLess( refDiffCoveredStats["max"].getValue()[i], maxMaskedDiff )
self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(1)), ["/group/plane"]) sg.setSelectionMask(IECore.StringVectorData(["Camera"])) self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(1)), ["/group/camera"]) def setUp(self): GafferUITest.TestCase.setUp(self) self.__cachedConverterMaxMemory = IECoreGL.CachedConverter.defaultCachedConverter( ).getMaxMemory() def tearDown(self): GafferUITest.TestCase.tearDown(self) IECoreGL.CachedConverter.defaultCachedConverter().setMaxMemory( self.__cachedConverterMaxMemory) if GafferTest.inCI({'travis'}): SceneGadgetTest.testExpansion = unittest.expectedFailure( SceneGadgetTest.testExpansion) if __name__ == "__main__": unittest.main()
class SerialisationTest(GafferTest.TestCase): class SerialisationTestNode(Gaffer.Node): def __init__(self, name="SerialisationTestNode", initArgument=10): Gaffer.Node.__init__(self, name) self.initArgument = initArgument self.needsAdditionalModules = False self["childNodeNeedingSerialisation"] = GafferTest.AddNode() self["childNodeNotNeedingSerialisation"] = GafferTest.AddNode() IECore.registerRunTimeTyped(SerialisationTestNode) def testCustomSerialiser(self): class CustomSerialiser(Gaffer.NodeSerialiser): def moduleDependencies(self, node, serialisation): return {"GafferTest" } | Gaffer.NodeSerialiser.moduleDependencies( self, node, serialisation) def constructor(self, node, serialisation): if node.needsAdditionalModules: serialisation.addModule("ConstructorModule") return ( "GafferTest.SerialisationTest.SerialisationTestNode( \"%s\", %d )" % (node.getName(), node.initArgument)) def postConstructor(self, node, identifier, serialisation): result = Gaffer.NodeSerialiser.postConstructor( self, node, identifier, serialisation) result += identifier + ".postConstructorWasHere = True\n" if node.needsAdditionalModules: serialisation.addModule("PostConstructorModule") return result def postHierarchy(self, node, identifier, serialisation): result = Gaffer.NodeSerialiser.postHierarchy( self, node, identifier, serialisation) result += identifier + ".postHierarchyWasHere = True\n" if node.needsAdditionalModules: serialisation.addModule("PostHierarchyModule") return result def postScript(self, node, identifier, serialisation): result = Gaffer.NodeSerialiser.postScript( self, node, identifier, serialisation) result += identifier + ".postScriptWasHere = True\n" if node.needsAdditionalModules: serialisation.addModule("PostScriptModule") return result def childNeedsSerialisation(self, child, serialisation): if isinstance(child, Gaffer.Node) and child.getName( ) == "childNodeNeedingSerialisation": return True return Gaffer.NodeSerialiser.childNeedsSerialisation( self, child, serialisation) def childNeedsConstruction(self, child, serialisation): if isinstance(child, Gaffer.Node): return False return Gaffer.NodeSerialiser.childNeedsConstruction( self, child, serialisation) customSerialiser = CustomSerialiser() Gaffer.Serialisation.registerSerialiser(self.SerialisationTestNode, customSerialiser) s = Gaffer.ScriptNode() s["n"] = self.SerialisationTestNode("a", initArgument=20) s["n"]["childNodeNeedingSerialisation"]["op1"].setValue(101) s["n"]["childNodeNotNeedingSerialisation"]["op1"].setValue(101) s["n"]["dynamicPlug"] = Gaffer.FloatPlug( defaultValue=10, flags=Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic) self.assertTrue( Gaffer.Serialisation.acquireSerialiser( s["n"]).isSame(customSerialiser)) s2 = Gaffer.ScriptNode() s2.execute(s.serialise()) self.assertTrue(isinstance(s2["n"], self.SerialisationTestNode)) self.assertEqual(s["n"].keys(), s2["n"].keys()) self.assertEqual(s2["n"].initArgument, 20) self.assertEqual( s2["n"]["childNodeNeedingSerialisation"]["op1"].getValue(), 101) self.assertEqual( s2["n"]["childNodeNotNeedingSerialisation"]["op1"].getValue(), 0) self.assertEqual(s2["n"]["dynamicPlug"].getValue(), 10) self.assertEqual(s2["n"].postConstructorWasHere, True) self.assertEqual(s2["n"].postHierarchyWasHere, True) self.assertEqual(s2["n"].postScriptWasHere, True) # Test calls to `Serialisation.addModule()` s["n"].needsAdditionalModules = True ss = s.serialise() self.assertIn("import ConstructorModule", ss) self.assertIn("import PostConstructorModule", ss) self.assertIn("import PostHierarchyModule", ss) self.assertIn("import PostScriptModule", ss) def testParentAccessor(self): n = Gaffer.Node() s = Gaffer.Serialisation(n) self.assertTrue(s.parent().isSame(n)) def testClassPath(self): self.assertEqual(Gaffer.Serialisation.classPath(Gaffer.Node()), "Gaffer.Node") self.assertEqual(Gaffer.Serialisation.classPath(Gaffer.Node), "Gaffer.Node") self.assertEqual(Gaffer.Serialisation.classPath(GafferTest.AddNode()), "GafferTest.AddNode") self.assertEqual(Gaffer.Serialisation.classPath(GafferTest.AddNode), "GafferTest.AddNode") def testModulePath(self): self.assertEqual(Gaffer.Serialisation.modulePath(Gaffer.Node()), "Gaffer") self.assertEqual(Gaffer.Serialisation.modulePath(Gaffer.Node), "Gaffer") self.assertEqual(Gaffer.Serialisation.modulePath(GafferTest.AddNode()), "GafferTest") self.assertEqual(Gaffer.Serialisation.modulePath(GafferTest.AddNode), "GafferTest") def testIncludeParentMetadataWhenExcludingChildren(self): n1 = Gaffer.Node() Gaffer.Metadata.registerValue(n1, "test", imath.Color3f(1, 2, 3)) with Gaffer.Context() as c: c["serialiser:includeParentMetadata"] = IECore.BoolData(True) s = Gaffer.Serialisation(n1, filter=Gaffer.StandardSet()) scope = {"parent": Gaffer.Node()} exec(s.result(), scope, scope) self.assertEqual(Gaffer.Metadata.value(scope["parent"], "test"), imath.Color3f(1, 2, 3)) class Outer(object): class Inner(object): # Emulate feature coming in Python 3. # See https://www.python.org/dev/peps/pep-3155/ __qualname__ = "Outer.Inner" def testClassPathForNestedClasses(self): self.assertEqual(Gaffer.Serialisation.classPath(self.Outer.Inner), "GafferTest.SerialisationTest.Outer.Inner") def testVersionMetadata(self): n = Gaffer.Node() serialisationWithMetadata = Gaffer.Serialisation(n).result() with Gaffer.Context() as c: c["serialiser:includeVersionMetadata"] = IECore.BoolData(False) serialisationWithoutMetadata = Gaffer.Serialisation(n).result() scope = {"parent": Gaffer.Node()} exec(serialisationWithMetadata, scope, scope) self.assertEqual( Gaffer.Metadata.value(scope["parent"], "serialiser:milestoneVersion"), Gaffer.About.milestoneVersion()) self.assertEqual( Gaffer.Metadata.value(scope["parent"], "serialiser:majorVersion"), Gaffer.About.majorVersion()) self.assertEqual( Gaffer.Metadata.value(scope["parent"], "serialiser:minorVersion"), Gaffer.About.minorVersion()) self.assertEqual( Gaffer.Metadata.value(scope["parent"], "serialiser:patchVersion"), Gaffer.About.patchVersion()) scope = {"parent": Gaffer.Node()} exec(serialisationWithoutMetadata, scope, scope) self.assertEqual( Gaffer.Metadata.value(scope["parent"], "serialiser:milestoneVersion"), None) self.assertEqual( Gaffer.Metadata.value(scope["parent"], "serialiser:majorVersion"), None) self.assertEqual( Gaffer.Metadata.value(scope["parent"], "serialiser:minorVersion"), None) self.assertEqual( Gaffer.Metadata.value(scope["parent"], "serialiser:patchVersion"), None) def testProtectParentNamespace(self): n = Gaffer.Node() n["a"] = GafferTest.AddNode() n["b"] = GafferTest.AddNode() n["b"]["op1"].setInput(n["a"]["sum"]) serialisationWithProtection = Gaffer.Serialisation(n).result() with Gaffer.Context() as c: c["serialiser:protectParentNamespace"] = IECore.BoolData(False) serialisationWithoutProtection = Gaffer.Serialisation(n).result() scope = {"parent": Gaffer.Node()} scope["parent"]["a"] = Gaffer.StringPlug() exec(serialisationWithProtection, scope, scope) self.assertIsInstance(scope["parent"]["a"], Gaffer.StringPlug) self.assertIn("a1", scope["parent"]) self.assertEqual(scope["parent"]["b"]["op1"].getInput(), scope["parent"]["a1"]["sum"]) scope = {"parent": Gaffer.Node()} scope["parent"]["a"] = Gaffer.StringPlug() exec(serialisationWithoutProtection, scope, scope) self.assertIsInstance(scope["parent"]["a"], GafferTest.AddNode) self.assertNotIn("a1", scope["parent"]) self.assertEqual(scope["parent"]["b"]["op1"].getInput(), scope["parent"]["a"]["sum"]) def testChildIdentifier(self): node = Gaffer.Node() node["a"] = GafferTest.AddNode() node["b"] = GafferTest.AddNode() filter = Gaffer.StandardSet([node["a"]]) serialisation = Gaffer.Serialisation(node, filter=filter) aIdentifier = serialisation.identifier(node["a"]) self.assertEqual( serialisation.identifier(node["a"]["op1"]), serialisation.childIdentifier(aIdentifier, node["a"]["op1"])) bIdentifier = serialisation.identifier(node["b"]) self.assertEqual(bIdentifier, "") self.assertEqual( serialisation.childIdentifier(bIdentifier, node["b"]["op1"]), "") def testBase64ObjectConversions(self): # Test StringData with all possible byte values (except 0, # because we can't construct a StringData with a null at the start). allBytes = bytes().join([six.int2byte(i) for i in range(1, 256)]) for i in range(0, len(allBytes)): o = IECore.StringData(allBytes[:i]) b = Gaffer.Serialisation.objectToBase64(o) self.assertEqual(Gaffer.Serialisation.objectFromBase64(b), o) # Test CompoundData o = IECore.CompoundData({ "a": 10, "b": 20, "c": IECore.CompoundData({"d": imath.V3f(1, 2, 3)}) }) b = Gaffer.Serialisation.objectToBase64(o) self.assertEqual(Gaffer.Serialisation.objectFromBase64(b), o) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=1) def testSwitchPerformance(self): script = Gaffer.ScriptNode() def build(maxDepth, upstreamNode=None, depth=0): node = Gaffer.Switch() node.setup(Gaffer.V3iPlug()) if upstreamNode is not None: node["in"][0].setInput(upstreamNode["out"]) script.addChild(node) if depth < maxDepth: build(maxDepth, node, depth + 1) build(maxDepth, node, depth + 1) with Gaffer.DirtyPropagationScope(): build(13) with GafferTest.TestRunner.PerformanceScope(): script.serialise() def testAddModule(self): node = Gaffer.Node() serialisation = Gaffer.Serialisation(node) serialisation.addModule("MyModule") serialisation.addModule("MyModule") self.assertEqual(serialisation.result().count("import MyModule"), 1)
class FilterResultsTest(GafferSceneTest.SceneTestCase): def assertExpectedOutStrings(self, node): self.assertEqual( node["outStrings"].getValue(), IECore.StringVectorData(node["out"].getValue().value.paths())) def testChangingFilter(self): p = GafferScene.Plane() s = GafferScene.Sphere() g = GafferScene.Group() g["in"][0].setInput(p["out"]) g["in"][1].setInput(s["out"]) f = GafferScene.PathFilter() f["paths"].setValue(IECore.StringVectorData(["/group/*"])) n = GafferScene.FilterResults() n["scene"].setInput(g["out"]) n["filter"].setInput(f["out"]) self.assertEqual(n["out"].getValue().value, IECore.PathMatcher(["/group/sphere", "/group/plane"])) self.assertExpectedOutStrings(n) f["paths"].setValue(IECore.StringVectorData(["/group/p*"])) self.assertEqual(n["out"].getValue().value, IECore.PathMatcher(["/group/plane"])) self.assertExpectedOutStrings(n) def testChangingScene(self): p = GafferScene.Plane() g = GafferScene.Group() g["in"][0].setInput(p["out"]) f = GafferScene.PathFilter() f["paths"].setValue(IECore.StringVectorData(["/group/plain"])) n = GafferScene.FilterResults() n["scene"].setInput(g["out"]) n["filter"].setInput(f["out"]) self.assertEqual(n["out"].getValue().value, IECore.PathMatcher()) self.assertExpectedOutStrings(n) p["name"].setValue("plain") self.assertEqual(n["out"].getValue().value, IECore.PathMatcher(["/group/plain"])) self.assertExpectedOutStrings(n) def testDirtyPropagation(self): p = GafferScene.Plane() p["sets"].setValue("A") f = GafferScene.SetFilter() f["setExpression"].setValue("A") n = GafferScene.FilterResults() n["scene"].setInput(p["out"]) n["filter"].setInput(f["out"]) cs = GafferTest.CapturingSlot(n.plugDirtiedSignal()) f["setExpression"].setValue("planeSet") self.assertTrue(n["out"] in {x[0] for x in cs}) del cs[:] p["name"].setValue("thing") self.assertTrue(n["out"] in {x[0] for x in cs}) def testOutputIntoExpression(self): script = Gaffer.ScriptNode() script["plane"] = GafferScene.Plane() script["sphere"] = GafferScene.Sphere() script["instancer"] = GafferScene.Instancer() script["instancer"]["in"].setInput(script["plane"]["out"]) script["instancer"]["prototypes"].setInput(script["sphere"]["out"]) script["instancer"]["parent"].setValue("/plane") script["filter"] = GafferScene.PathFilter() script["filter"]["paths"].setValue( IECore.StringVectorData(["/plane/instances/sphere/*"])) script["filterResults"] = GafferScene.FilterResults() script["filterResults"]["scene"].setInput(script["instancer"]["out"]) script["filterResults"]["filter"].setInput(script["filter"]["out"]) script["filterResults"]["user"][ "strings"] = Gaffer.StringVectorDataPlug( defaultValue=IECore.StringVectorData()) script["expression"] = Gaffer.Expression() script["expression"].setExpression( "parent['filterResults']['user']['strings'] = IECore.StringVectorData( sorted( parent['filterResults']['out'].value.paths() ) )" ) self.assertEqual( script["filterResults"]["user"]["strings"].getValue(), IECore.StringVectorData([ "/plane/instances/sphere/0", "/plane/instances/sphere/1", "/plane/instances/sphere/2", "/plane/instances/sphere/3", ])) def testComputeCacheRecursion(self): script = Gaffer.ScriptNode() script["plane"] = GafferScene.Plane() script["filter1"] = GafferScene.PathFilter() script["filter1"]["paths"].setValue(IECore.StringVectorData(["/*"])) script["filterResults1"] = GafferScene.FilterResults() script["filterResults1"]["scene"].setInput(script["plane"]["out"]) script["filterResults1"]["filter"].setInput(script["filter1"]["out"]) script["filter2"] = GafferScene.PathFilter() script["expression"] = Gaffer.Expression() script["expression"].setExpression( 'parent["filter2"]["paths"] = IECore.StringVectorData( parent["filterResults1"]["out"].value.paths() )' ) script["filterResults2"] = GafferScene.FilterResults() script["filterResults2"]["scene"].setInput(script["plane"]["out"]) script["filterResults2"]["filter"].setInput(script["filter2"]["out"]) h = script["filterResults2"]["out"].hash() Gaffer.ValuePlug.clearCache() script["filterResults2"]["out"].getValue(h) def testRoot(self): # /group # /group # /plane # /plane plane = GafferScene.Plane() innerGroup = GafferScene.Group() innerGroup["in"][0].setInput(plane["out"]) outerGroup = GafferScene.Group() outerGroup["in"][0].setInput(innerGroup["out"]) outerGroup["in"][1].setInput(plane["out"]) filter = GafferScene.PathFilter() filter["paths"].setValue(IECore.StringVectorData(["/..."])) filterResults = GafferScene.FilterResults() filterResults["scene"].setInput(outerGroup["out"]) filterResults["filter"].setInput(filter["out"]) self.assertEqual( filterResults["out"].getValue().value, IECore.PathMatcher([ "/", "/group", "/group/group", "/group/group/plane", "/group/plane", ])) hash = filterResults["out"].hash() filterResults["root"].setValue("/group/group") self.assertEqual( filterResults["out"].getValue().value, IECore.PathMatcher([ "/group/group", "/group/group/plane", ])) self.assertNotEqual(filterResults["out"].hash(), hash) def testRootMatchVsNoMatch(self): plane = GafferScene.Plane() pathFilter = GafferScene.PathFilter() filterResults = GafferScene.FilterResults() filterResults["scene"].setInput(plane["out"]) filterResults["filter"].setInput(pathFilter["out"]) self.assertEqual(filterResults["outStrings"].getValue(), IECore.StringVectorData()) pathFilter["paths"].setValue(IECore.StringVectorData(["/"])) self.assertEqual(filterResults["outStrings"].getValue(), IECore.StringVectorData(["/"])) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod() def testHashPerformance(self): sphere = GafferScene.Sphere() duplicate = GafferScene.Duplicate() duplicate["in"].setInput(sphere["out"]) duplicate["target"].setValue('/sphere') duplicate["copies"].setValue(1000000) pathFilter = GafferScene.PathFilter() pathFilter["paths"].setValue(IECore.StringVectorData(['...'])) filterResults = GafferScene.FilterResults() filterResults["scene"].setInput(duplicate["out"]) filterResults["filter"].setInput(pathFilter["out"]) # Evaluate the root childNames beforehand to focus our timing on the hash duplicate["out"].childNames("/") with GafferTest.TestRunner.PerformanceScope(): filterResults["out"].hash() @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod() def testComputePerformance(self): sphere = GafferScene.Sphere() duplicate = GafferScene.Duplicate() duplicate["in"].setInput(sphere["out"]) duplicate["target"].setValue('/sphere') duplicate["copies"].setValue(1000000) pathFilter = GafferScene.PathFilter() pathFilter["paths"].setValue(IECore.StringVectorData(['...'])) filterResults = GafferScene.FilterResults() filterResults["scene"].setInput(duplicate["out"]) filterResults["filter"].setInput(pathFilter["out"]) # Evaluate the root childNames beforehand to focus our timing on the compute duplicate["out"].childNames("/") with GafferTest.TestRunner.PerformanceScope(): filterResults["out"].getValue()
def testDurations(self): class DurationNode(Gaffer.ComputeNode): def __init__(self, name="DurationNode"): Gaffer.ComputeNode.__init__(self, name) self["in"] = Gaffer.FloatPlug() self["out"] = Gaffer.FloatPlug( direction=Gaffer.Plug.Direction.Out) self["hashDuration"] = Gaffer.FloatPlug() self["computeDuration"] = Gaffer.FloatPlug() def affects(self, input): result = Gaffer.ComputeNode.affects(self, input) if input in (self["in"], self["hashDuration"], self["computeDuration"]): result.append(self["out"]) return result def hash(self, output, context, h): if output.isSame(self["out"]): self["in"].hash(h) self["computeDuration"].hash(h) time.sleep(self["hashDuration"].getValue()) def compute(self, plug, context): if plug.isSame(self["out"]): d = self["computeDuration"].getValue() time.sleep(d) self["out"].setValue(self["in"].getValue() + d) else: ComputeNode.compute(self, plug, context) IECore.registerRunTimeTyped(DurationNode) n1 = DurationNode("n1") n1["hashDuration"].setValue(0.2) n1["computeDuration"].setValue(0.4) n2 = DurationNode("n2") n2["in"].setInput(n1["out"]) n2["hashDuration"].setValue(0.1) n2["computeDuration"].setValue(0.2) with Gaffer.PerformanceMonitor() as m: n2["out"].getValue() def seconds(n): return n / (1000000000.0) self.assertEqual(len(m.allStatistics()), 2) # 1.0 is an excessively wide tolerance but we've seen huge variations # in CI due to machine contention. We're leaving the test in as it # would potentially still catch some catastrophic orders-of-magnitude # timing increase bug... delta = 1.0 if GafferTest.inCI() else 0.01 self.assertEqual(m.plugStatistics(n1["out"]).hashCount, 1) self.assertEqual(m.plugStatistics(n1["out"]).computeCount, 1) self.assertAlmostEqual(seconds( m.plugStatistics(n1["out"]).hashDuration), 0.2, delta=delta) self.assertAlmostEqual(seconds( m.plugStatistics(n1["out"]).computeDuration), 0.4, delta=delta) self.assertEqual(m.plugStatistics(n2["out"]).hashCount, 1) self.assertEqual(m.plugStatistics(n2["out"]).computeCount, 1) self.assertAlmostEqual(seconds( m.plugStatistics(n2["out"]).hashDuration), 0.1, delta=delta) self.assertAlmostEqual(seconds( m.plugStatistics(n2["out"]).computeDuration), 0.2, delta=delta) with m: with Gaffer.Context() as c: c["test"] = 1 # force rehash, but not recompute n2["out"].getValue() self.assertEqual(m.plugStatistics(n1["out"]).hashCount, 2) self.assertEqual(m.plugStatistics(n1["out"]).computeCount, 1) self.assertAlmostEqual(seconds( m.plugStatistics(n1["out"]).hashDuration), 0.4, delta=delta) self.assertAlmostEqual(seconds( m.plugStatistics(n1["out"]).computeDuration), 0.4, delta=delta) self.assertEqual(m.plugStatistics(n2["out"]).hashCount, 2) self.assertEqual(m.plugStatistics(n2["out"]).computeCount, 1) self.assertAlmostEqual(seconds( m.plugStatistics(n2["out"]).hashDuration), 0.2, delta=delta) self.assertAlmostEqual(seconds( m.plugStatistics(n2["out"]).computeDuration), 0.2, delta=delta)
import unittest import imath import IECore import IECoreScene import IECoreImage import Gaffer import GafferTest import GafferScene import GafferSceneTest import GafferImage import GafferArnold @unittest.skipIf(GafferTest.inCI({'travis'}), "No license available on Travis") class InteractiveArnoldRenderTest(GafferSceneTest.InteractiveRenderTest): def testTwoRenders(self): s = Gaffer.ScriptNode() s["s"] = GafferScene.Sphere() s["o"] = GafferScene.Outputs() s["o"].addOutput( "beauty", IECoreScene.Output("test", "ieDisplay", "rgba", { "driverType": "ImageDisplayDriver", "handle": "myLovelySphere", })) s["o"]["in"].setInput(s["s"]["out"])
class ParallelAlgoTest(GafferTest.TestCase): # Context manager used to run code which is expected to generate a # call to `ParallelAlgo.callOnUIThread()`. This emulates the # UIThreadCallHandler that would otherwise be provided by GafferUI. class ExpectedUIThreadCall(object): __conditionStack = [] __conditionStackMutex = threading.Lock() __callHandlerRegistered = False def __enter__(self): self.__condition = threading.Condition() self.__condition.toCall = None with self.__conditionStackMutex: self.__conditionStack.append(self.__condition) self.__registeredCallHandler = False if not self.__class__.__callHandlerRegistered: Gaffer.ParallelAlgo.pushUIThreadCallHandler( self.__callOnUIThread) self.__class__.__callHandlerRegistered = True self.__registeredCallHandler = True def __exit__(self, type, value, traceBack): with self.__condition: while self.__condition.toCall is None: self.__condition.wait() self.__condition.toCall() self.__condition.toCall = None if self.__registeredCallHandler: assert (self.__class__.__callHandlerRegistered == True) Gaffer.ParallelAlgo.popUIThreadCallHandler() self.__class__.__callHandlerRegistered = False @classmethod def __callOnUIThread(cls, f): with cls.__conditionStackMutex: condition = cls.__conditionStack.pop() with condition: assert (condition.toCall is None) condition.toCall = f condition.notify() def testCallOnUIThread(self): s = Gaffer.ScriptNode() def uiThreadFunction(): s.setName("test") s.uiThreadId = thread.get_ident() with self.ExpectedUIThreadCall(): t = threading.Thread(target=lambda: Gaffer.ParallelAlgo. callOnUIThread(uiThreadFunction)) t.start() t.join() self.assertEqual(s.getName(), "test") self.assertEqual(s.uiThreadId, thread.get_ident()) @unittest.skipIf(GafferTest.inCI( ), "Unknown CI issue. TODO: Investigate why we only see this in the test harness" ) def testNestedCallOnUIThread(self): # This is testing our `ExpectedUIThreadCall` utility # class more than it's testing `ParallelAlgo`. s = Gaffer.ScriptNode() def uiThreadFunction1(): s.setName("test") s.uiThreadId1 = thread.get_ident() def uiThreadFunction2(): s["fileName"].setValue("test") s.uiThreadId2 = thread.get_ident() with self.ExpectedUIThreadCall(): t1 = threading.Thread(target=lambda: Gaffer.ParallelAlgo. callOnUIThread(uiThreadFunction1)) t1.start() with self.ExpectedUIThreadCall(): t2 = threading.Thread(target=lambda: Gaffer.ParallelAlgo. callOnUIThread(uiThreadFunction2)) t2.start() self.assertEqual(s.getName(), "test") self.assertEqual(s.uiThreadId1, thread.get_ident()) self.assertEqual(s["fileName"].getValue(), "test") self.assertEqual(s.uiThreadId2, thread.get_ident()) t1.join() t2.join() def testCallOnBackgroundThread(self): script = Gaffer.ScriptNode() script["n"] = GafferTest.AddNode() foregroundContext = Gaffer.Context(script.context()) foregroundContext["a"] = "a" def f(): backgroundContext = Gaffer.Context.current() self.assertFalse(backgroundContext.isSame(foregroundContext)) self.assertEqual(backgroundContext, foregroundContext) with self.assertRaises(IECore.Cancelled): while True: script["n"]["sum"].getValue() # We might expect that `script["n"]["sum"].getValue()` # would be guaranteed to throw after cancellation has been # requested. But that is not the case if both the hash and the # value are already cached, because cancellation is only checked # for automatically when a Process is constructed. So we take # a belt and braces approach and perform an explicit check here. # # The alternative would be to move the cancellation check outside # of the Process class, so it is performed before the cache lookup. # This may be the better approach, but we would need to benchmark # it to ensure that performance was not adversely affected. To our # knowledge, this "cache hits avoid cancellation" problem has not # been responsible for unresponsive cancellation in the wild, because # background tasks are typically triggered by `plugDirtiedSignal()`, # and the hash cache is cleared when a plug is dirtied. IECore.Canceller.check(backgroundContext.canceller()) # Explicit cancellation with foregroundContext: backgroundTask = Gaffer.ParallelAlgo.callOnBackgroundThread( script["n"]["sum"], f) backgroundTask.cancel() # Implicit cancellation through graph edit with foregroundContext: backgroundTask = Gaffer.ParallelAlgo.callOnBackgroundThread( script["n"]["sum"], f) script["n"]["op1"].setValue(10) # Cancellation through deletion with foregroundContext: backgroundTask = Gaffer.ParallelAlgo.callOnBackgroundThread( script["n"]["sum"], f) del backgroundTask def testBackgroundThreadMonitoring(self): s = Gaffer.ScriptNode() s["n"] = GafferTest.MultiplyNode() s["n"]["op2"].setValue(1) s["e"] = Gaffer.Expression() s["e"].setExpression("""parent["n"]["op1"] = context["op1"]""") def backgroundFunction(): with Gaffer.Context() as c: for i in range(0, 10000): c["op1"] = i self.assertEqual(s["n"]["product"].getValue(), i) with Gaffer.PerformanceMonitor() as m: t = Gaffer.ParallelAlgo.callOnBackgroundThread( s["n"]["product"], backgroundFunction) t.wait() # The monitor was active when we launched the background # process, so we expect it to have been transferred to the # background thread and remained active there for the duration. self.assertEqual( m.plugStatistics(s["n"]["product"]).computeCount, 10000)
class MergeTest(GafferImageTest.ImageTestCase): rPath = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/redWithDataWindow.100x100.exr" ) gPath = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/greenWithDataWindow.100x100.exr" ) bPath = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/blueWithDataWindow.100x100.exr" ) checkerPath = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/checkerboard.100x100.exr") checkerRGBPath = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/rgbOverChecker.100x100.exr" ) rgbPath = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/rgb.100x100.exr") mergeBoundariesRefPath = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/mergeBoundariesRef.exr") # Do several tests to check the cache is working correctly: def testHashes(self): r1 = GafferImage.ImageReader() r1["fileName"].setValue(self.checkerPath) r2 = GafferImage.ImageReader() r2["fileName"].setValue(self.gPath) ########################################## # Test to see if the hash changes. ########################################## merge = GafferImage.Merge() merge["operation"].setValue(GafferImage.Merge.Operation.Over) merge["in"][0].setInput(r1["out"]) merge["in"][1].setInput(r2["out"]) h1 = GafferImage.ImageAlgo.imageHash(merge["out"]) # Switch the inputs. merge["in"][1].setInput(r1["out"]) merge["in"][0].setInput(r2["out"]) h2 = GafferImage.ImageAlgo.imageHash(merge["out"]) self.assertNotEqual(h1, h2) ########################################## # Test to see if the hash remains the same # when the output should be the same but the # input plugs used are not. ########################################## merge = GafferImage.Merge() merge["operation"].setValue(GafferImage.Merge.Operation.Over) expectedHash = h1 # Connect up a load of inputs ... merge["in"][0].setInput(r1["out"]) merge["in"][1].setInput(r1["out"]) merge["in"][2].setInput(r1["out"]) merge["in"][3].setInput(r2["out"]) # but then disconnect two so that the result should still be the same... merge["in"][1].setInput(None) merge["in"][2].setInput(None) h1 = GafferImage.ImageAlgo.imageHash(merge["out"]) self.assertEqual(h1, expectedHash) # The pass through for disabled is working, but I don't see any sign that a pass through # for a single input was ever implemented. ( For a long time this test was broken ) @unittest.expectedFailure def testHashPassThrough(self): r1 = GafferImage.ImageReader() r1["fileName"].setValue(self.checkerPath) ########################################## # Test to see if the input hash is always passed # through if only the first input is connected. ########################################## merge = GafferImage.Merge() merge["operation"].setValue(GafferImage.Merge.Operation.Over) expectedHash = GafferImage.ImageAlgo.imageHash(r1["out"]) merge["in"][0].setInput(r1["out"]) h1 = GafferImage.ImageAlgo.imageHash(merge["out"]) self.assertEqual(h1, expectedHash) ########################################## # Test that if we disable the node the hash gets passed through. ########################################## merge["enabled"].setValue(False) h1 = GafferImage.ImageAlgo.imageHash(merge["out"]) self.assertEqual(h1, expectedHash) # Overlay a red, green and blue tile of different data window sizes and check the data window is expanded on the result and looks as we expect. def testOverRGBA(self): r = GafferImage.ImageReader() r["fileName"].setValue(self.rPath) g = GafferImage.ImageReader() g["fileName"].setValue(self.gPath) b = GafferImage.ImageReader() b["fileName"].setValue(self.bPath) merge = GafferImage.Merge() merge["operation"].setValue(GafferImage.Merge.Operation.Over) merge["in"][0].setInput(r["out"]) merge["in"][1].setInput(g["out"]) merge["in"][2].setInput(b["out"]) expected = GafferImage.ImageReader() expected["fileName"].setValue(self.rgbPath) self.assertImagesEqual(merge["out"], expected["out"], maxDifference=0.001, ignoreMetadata=True) # Overlay a red, green and blue tile of different data window sizes and check the data window is expanded on the result and looks as we expect. def testOverRGBAonRGB(self): c = GafferImage.ImageReader() c["fileName"].setValue(self.checkerPath) r = GafferImage.ImageReader() r["fileName"].setValue(self.rPath) g = GafferImage.ImageReader() g["fileName"].setValue(self.gPath) b = GafferImage.ImageReader() b["fileName"].setValue(self.bPath) merge = GafferImage.Merge() merge["operation"].setValue(GafferImage.Merge.Operation.Over) merge["in"][0].setInput(c["out"]) merge["in"][1].setInput(r["out"]) merge["in"][2].setInput(g["out"]) merge["in"][3].setInput(b["out"]) expected = GafferImage.ImageReader() expected["fileName"].setValue(self.checkerRGBPath) self.assertImagesEqual(merge["out"], expected["out"], maxDifference=0.001, ignoreMetadata=True) def testAffects(self): c1 = GafferImage.Constant() c2 = GafferImage.Constant() m = GafferImage.Merge() m["in"][0].setInput(c1["out"]) m["in"][1].setInput(c2["out"]) cs = GafferTest.CapturingSlot(m.plugDirtiedSignal()) c1["color"]["r"].setValue(0.1) self.assertEqual(len(cs), 5) self.assertTrue(cs[0][0].isSame(m["in"][0]["channelData"])) self.assertTrue(cs[1][0].isSame(m["in"][0])) self.assertTrue(cs[2][0].isSame(m["in"])) self.assertTrue(cs[3][0].isSame(m["out"]["channelData"])) self.assertTrue(cs[4][0].isSame(m["out"])) del cs[:] c2["color"]["g"].setValue(0.2) self.assertEqual(len(cs), 5) self.assertTrue(cs[0][0].isSame(m["in"][1]["channelData"])) self.assertTrue(cs[1][0].isSame(m["in"][1])) self.assertTrue(cs[2][0].isSame(m["in"])) self.assertTrue(cs[3][0].isSame(m["out"]["channelData"])) self.assertTrue(cs[4][0].isSame(m["out"])) def testEnabledAffects(self): m = GafferImage.Merge() affected = m.affects(m["enabled"]) self.assertTrue(m["out"]["channelData"] in affected) def testPassThrough(self): c = GafferImage.Constant() f = GafferImage.Resize() f["in"].setInput(c["out"]) f["format"].setValue( GafferImage.Format(imath.Box2i(imath.V2i(0), imath.V2i(10)), 1)) d = GafferImage.ImageMetadata() d["metadata"].addChild( Gaffer.NameValuePlug( "comment", IECore.StringData("reformated and metadata updated"))) d["in"].setInput(f["out"]) m = GafferImage.Merge() m["in"][0].setInput(c["out"]) m["in"][1].setInput(d["out"]) self.assertEqual(m["out"]["format"].hash(), c["out"]["format"].hash()) self.assertEqual(m["out"]["metadata"].hash(), c["out"]["metadata"].hash()) self.assertEqual(m["out"]["format"].getValue(), c["out"]["format"].getValue()) self.assertEqual(m["out"]["metadata"].getValue(), c["out"]["metadata"].getValue()) m["in"][0].setInput(d["out"]) m["in"][1].setInput(c["out"]) self.assertEqual(m["out"]["format"].hash(), d["out"]["format"].hash()) self.assertEqual(m["out"]["metadata"].hash(), d["out"]["metadata"].hash()) self.assertEqual(m["out"]["format"].getValue(), d["out"]["format"].getValue()) self.assertEqual(m["out"]["metadata"].getValue(), d["out"]["metadata"].getValue()) def testSmallDataWindowOverLarge(self): b = GafferImage.Constant() b["format"].setValue(GafferImage.Format(500, 500, 1.0)) b["color"].setValue(imath.Color4f(1, 0, 0, 1)) a = GafferImage.Constant() a["format"].setValue(GafferImage.Format(500, 500, 1.0)) a["color"].setValue(imath.Color4f(0, 1, 0, 1)) aCrop = GafferImage.Crop() aCrop["in"].setInput(a["out"]) aCrop["areaSource"].setValue(aCrop.AreaSource.Area) aCrop["area"].setValue(imath.Box2i(imath.V2i(50), imath.V2i(162))) aCrop["affectDisplayWindow"].setValue(False) m = GafferImage.Merge() m["operation"].setValue(m.Operation.Over) m["in"][0].setInput(b["out"]) m["in"][1].setInput(aCrop["out"]) redSampler = GafferImage.Sampler( m["out"], "R", m["out"]["format"].getValue().getDisplayWindow()) greenSampler = GafferImage.Sampler( m["out"], "G", m["out"]["format"].getValue().getDisplayWindow()) blueSampler = GafferImage.Sampler( m["out"], "B", m["out"]["format"].getValue().getDisplayWindow()) def sample(x, y): return imath.Color3f( redSampler.sample(x, y), greenSampler.sample(x, y), blueSampler.sample(x, y), ) # We should only have overed green in areas which are inside # the data window of aCrop. Everywhere else we should have # red still. self.assertEqual(sample(49, 49), imath.Color3f(1, 0, 0)) self.assertEqual(sample(50, 50), imath.Color3f(0, 1, 0)) self.assertEqual(sample(161, 161), imath.Color3f(0, 1, 0)) self.assertEqual(sample(162, 162), imath.Color3f(1, 0, 0)) def testLargeDataWindowAddedToSmall(self): b = GafferImage.Constant() b["format"].setValue(GafferImage.Format(500, 500, 1.0)) b["color"].setValue(imath.Color4f(1, 0, 0, 1)) a = GafferImage.Constant() a["format"].setValue(GafferImage.Format(500, 500, 1.0)) a["color"].setValue(imath.Color4f(0, 1, 0, 1)) bCrop = GafferImage.Crop() bCrop["in"].setInput(b["out"]) bCrop["areaSource"].setValue(bCrop.AreaSource.Area) bCrop["area"].setValue(imath.Box2i(imath.V2i(50), imath.V2i(162))) bCrop["affectDisplayWindow"].setValue(False) m = GafferImage.Merge() m["operation"].setValue(m.Operation.Add) m["in"][0].setInput(bCrop["out"]) m["in"][1].setInput(a["out"]) redSampler = GafferImage.Sampler( m["out"], "R", m["out"]["format"].getValue().getDisplayWindow()) greenSampler = GafferImage.Sampler( m["out"], "G", m["out"]["format"].getValue().getDisplayWindow()) blueSampler = GafferImage.Sampler( m["out"], "B", m["out"]["format"].getValue().getDisplayWindow()) def sample(x, y): return imath.Color3f( redSampler.sample(x, y), greenSampler.sample(x, y), blueSampler.sample(x, y), ) # We should only have yellow in areas where the background exists, # and should have just green everywhere else. self.assertEqual(sample(49, 49), imath.Color3f(0, 1, 0)) self.assertEqual(sample(50, 50), imath.Color3f(1, 1, 0)) self.assertEqual(sample(161, 161), imath.Color3f(1, 1, 0)) self.assertEqual(sample(162, 162), imath.Color3f(0, 1, 0)) def testCrashWithResizedInput(self): b = GafferImage.Constant() b["format"].setValue(GafferImage.Format(2048, 1556)) bResized = GafferImage.Resize() bResized["in"].setInput(b["out"]) bResized["format"].setValue(GafferImage.Format(1920, 1080)) bResized["fitMode"].setValue(bResized.FitMode.Fit) a = GafferImage.Constant() a["format"].setValue(GafferImage.Format(1920, 1080)) merge = GafferImage.Merge() merge["operation"].setValue(merge.Operation.Over) merge["in"][0].setInput(bResized["out"]) merge["in"][1].setInput(a["out"]) GafferImageTest.processTiles(merge["out"]) def testModes(self): b = GafferImage.Constant() b["color"].setValue(imath.Color4f(0.1, 0.2, 0.3, 0.4)) a = GafferImage.Constant() a["color"].setValue(imath.Color4f(1, 0.3, 0.1, 0.2)) merge = GafferImage.Merge() merge["in"][0].setInput(b["out"]) merge["in"][1].setInput(a["out"]) sampler = GafferImage.ImageSampler() sampler["image"].setInput(merge["out"]) sampler["pixel"].setValue(imath.V2f(10)) self.longMessage = True for operation, expected in [ (GafferImage.Merge.Operation.Add, (1.1, 0.5, 0.4, 0.6)), (GafferImage.Merge.Operation.Atop, (0.48, 0.28, 0.28, 0.4)), (GafferImage.Merge.Operation.Divide, (10, 1.5, 1 / 3.0, 0.5)), (GafferImage.Merge.Operation.In, (0.4, 0.12, 0.04, 0.08)), (GafferImage.Merge.Operation.Out, (0.6, 0.18, 0.06, 0.12)), (GafferImage.Merge.Operation.Mask, (0.02, 0.04, 0.06, 0.08)), (GafferImage.Merge.Operation.Matte, (0.28, 0.22, 0.26, 0.36)), (GafferImage.Merge.Operation.Multiply, (0.1, 0.06, 0.03, 0.08)), (GafferImage.Merge.Operation.Over, (1.08, 0.46, 0.34, 0.52)), (GafferImage.Merge.Operation.Subtract, (0.9, 0.1, -0.2, -0.2)), (GafferImage.Merge.Operation.Difference, (0.9, 0.1, 0.2, 0.2)), (GafferImage.Merge.Operation.Under, (0.7, 0.38, 0.36, 0.52)), (GafferImage.Merge.Operation.Min, (0.1, 0.2, 0.1, 0.2)), (GafferImage.Merge.Operation.Max, (1, 0.3, 0.3, 0.4)) ]: merge["operation"].setValue(operation) self.assertAlmostEqual(sampler["color"]["r"].getValue(), expected[0], msg=operation) self.assertAlmostEqual(sampler["color"]["g"].getValue(), expected[1], msg=operation) self.assertAlmostEqual(sampler["color"]["b"].getValue(), expected[2], msg=operation) self.assertAlmostEqual(sampler["color"]["a"].getValue(), expected[3], msg=operation) def testChannelRequest(self): a = GafferImage.Constant() a["color"].setValue(imath.Color4f(0.1, 0.2, 0.3, 0.4)) ad = GafferImage.DeleteChannels() ad["in"].setInput(a["out"]) ad["mode"].setValue(GafferImage.DeleteChannels.Mode.Delete) ad["channels"].setValue("R") b = GafferImage.Constant() b["color"].setValue(imath.Color4f(1.0, 0.3, 0.1, 0.2)) bd = GafferImage.DeleteChannels() bd["in"].setInput(b["out"]) bd["mode"].setValue(GafferImage.DeleteChannels.Mode.Delete) bd["channels"].setValue("G") merge = GafferImage.Merge() merge["in"][0].setInput(ad["out"]) merge["in"][1].setInput(bd["out"]) merge["operation"].setValue(GafferImage.Merge.Operation.Add) sampler = GafferImage.ImageSampler() sampler["image"].setInput(merge["out"]) sampler["pixel"].setValue(imath.V2f(10)) self.assertAlmostEqual(sampler["color"]["r"].getValue(), 0.0 + 1.0) self.assertAlmostEqual(sampler["color"]["g"].getValue(), 0.2 + 0.0) self.assertAlmostEqual(sampler["color"]["b"].getValue(), 0.3 + 0.1) self.assertAlmostEqual(sampler["color"]["a"].getValue(), 0.4 + 0.2) def testNonFlatThrows(self): deep = GafferImage.Empty() flat = GafferImage.Constant() merge = GafferImage.Merge() merge["in"][0].setInput(flat["out"]) merge["in"][1].setInput(flat["out"]) self.assertNotEqual(GafferImage.ImageAlgo.imageHash(merge["out"]), GafferImage.ImageAlgo.imageHash(flat["out"])) merge["in"][0].setInput(deep["out"]) six.assertRaisesRegex(self, RuntimeError, 'Deep data not supported in input "in.in0"', GafferImage.ImageAlgo.image, merge["out"]) merge["in"][0].setInput(flat["out"]) merge["in"][1].setInput(deep["out"]) six.assertRaisesRegex(self, RuntimeError, 'Deep data not supported in input "in.in1"', GafferImage.ImageAlgo.image, merge["out"]) def testDefaultFormat(self): a = GafferImage.Constant() a["format"].setValue(GafferImage.Format(100, 200)) m = GafferImage.Merge() m["in"][1].setInput(a["out"]) with Gaffer.Context() as c: GafferImage.FormatPlug().setDefaultFormat( c, GafferImage.Format(1000, 2000)) self.assertEqual(m["out"]["format"].getValue(), GafferImage.Format(1000, 2000)) def testDataWindowWhenBNotConnected(self): a = GafferImage.Constant() a["format"].setValue(GafferImage.Format(100, 200)) m = GafferImage.Merge() m["in"][1].setInput(a["out"]) self.assertEqual(m["out"]["dataWindow"].getValue(), a["out"]["dataWindow"].getValue()) # Make sure we don't fail by pulling tiles outside the data window when merging images with # misaligned data def testTilesOutsideDataWindow(self): r = GafferImage.ImageReader() r["fileName"].setValue(self.checkerPath) o = GafferImage.Offset() o["in"].setInput(r["out"]) o["offset"].setValue(imath.V2i(-10)) merge = GafferImage.Merge() merge["in"][0].setInput(r["out"]) merge["in"][1].setInput(o["out"]) GafferImage.ImageAlgo.image(merge["out"]) def testPassthroughs(self): ts = GafferImage.ImagePlug.tileSize() checkerboardB = GafferImage.Checkerboard() checkerboardB["format"]["displayWindow"].setValue( imath.Box2i(imath.V2i(0), imath.V2i(4096))) checkerboardA = GafferImage.Checkerboard() checkerboardA["format"]["displayWindow"].setValue( imath.Box2i(imath.V2i(0), imath.V2i(4096))) checkerboardA["size"].setValue(imath.V2f(5)) cropB = GafferImage.Crop() cropB["in"].setInput(checkerboardB["out"]) cropB["area"].setValue( imath.Box2i(imath.V2i(ts * 0.5), imath.V2i(ts * 4.5))) cropB["affectDisplayWindow"].setValue(False) cropA = GafferImage.Crop() cropA["in"].setInput(checkerboardA["out"]) cropA["area"].setValue( imath.Box2i(imath.V2i(ts * 2.5), imath.V2i(ts * 6.5))) cropA["affectDisplayWindow"].setValue(False) merge = GafferImage.Merge() merge["in"][0].setInput(cropB["out"]) merge["in"][1].setInput(cropA["out"]) merge["operation"].setValue(8) sampleTileOrigins = { "insideBoth": imath.V2i(ts * 3, ts * 3), "outsideBoth": imath.V2i(ts * 5, ts), "outsideEdgeB": imath.V2i(ts, 0), "insideB": imath.V2i(ts, ts), "internalEdgeB": imath.V2i(ts * 4, ts), "internalEdgeA": imath.V2i(ts * 5, ts * 2), "insideA": imath.V2i(ts * 5, ts * 5), "outsideEdgeA": imath.V2i(ts * 6, ts * 5) } for opName, onlyA, onlyB in [("Atop", "black", "passB"), ("Divide", "operate", "black"), ("Out", "passA", "black"), ("Multiply", "black", "black"), ("Over", "passA", "passB"), ("Subtract", "passA", "operate"), ("Difference", "operate", "operate")]: op = getattr(GafferImage.Merge.Operation, opName) merge["operation"].setValue(op) results = {} for name, tileOrigin in sampleTileOrigins.items(): # We want to check the value pass through code independently # of the hash passthrough code, which we can do by dropping # the value cached and evaluating values first Gaffer.ValuePlug.clearCache() with Gaffer.Context() as c: c["image:tileOrigin"] = tileOrigin c["image:channelName"] = "R" data = merge["out"]["channelData"].getValue(_copy=False) if data.isSame( GafferImage.ImagePlug.blackTile(_copy=False)): computeMode = "black" elif data.isSame( cropB["out"]["channelData"].getValue(_copy=False)): computeMode = "passB" elif data.isSame( cropA["out"]["channelData"].getValue(_copy=False)): computeMode = "passA" else: computeMode = "operate" h = merge["out"]["channelData"].hash() if h == GafferImage.ImagePlug.blackTile().hash(): hashMode = "black" elif h == cropB["out"]["channelData"].hash(): hashMode = "passB" elif h == cropA["out"]["channelData"].hash(): hashMode = "passA" else: hashMode = "operate" self.assertEqual(hashMode, computeMode) results[name] = hashMode self.assertEqual(results["insideBoth"], "operate") self.assertEqual(results["outsideBoth"], "black") self.assertEqual(results["outsideEdgeB"], onlyB) self.assertEqual(results["insideB"], onlyB) self.assertEqual(results["outsideEdgeA"], onlyA) self.assertEqual(results["insideA"], onlyA) if onlyA == "black" or onlyB == "black": self.assertEqual(results["internalEdgeB"], onlyB) self.assertEqual(results["internalEdgeA"], onlyA) else: self.assertEqual(results["internalEdgeB"], "operate") self.assertEqual(results["internalEdgeA"], "operate") # This somewhat sloppy test cobbled together from a Gaffer scene tests a bunch of the weird cases # for how data windows can overlap each other and the tile. It was added because I'm experimenting # with an approach for treating the tile in regions, which does add a little bit of arithmetic that # I could get wrong def runBoundaryCorrectness(self, scale): testMerge = GafferImage.Merge() subImageNodes = [] for checkSize, col, bound in [ (2, (0.672299981, 0.672299981, 0), ((11, 7), (61, 57))), (4, (0.972599983, 0.493499994, 1), ((9, 5), (59, 55))), (6, (0.310799986, 0.843800008, 1), ((0, 21), (1024, 41))), (8, (0.958999991, 0.672299981, 0.0296), ((22, 0), (42, 1024))), (10, (0.950900018, 0.0899000019, 0.235499993), ((7, 10), (47, 50))), ]: checkerboard = GafferImage.Checkerboard() checkerboard["format"].setValue( GafferImage.Format(1024 * scale, 1024 * scale, 1.000)) checkerboard["size"].setValue(imath.V2f(checkSize * scale)) checkerboard["colorA"].setValue( imath.Color4f(0.1 * col[0], 0.1 * col[1], 0.1 * col[2], 0.3)) checkerboard["colorB"].setValue( imath.Color4f(0.5 * col[0], 0.5 * col[1], 0.5 * col[2], 0.7)) crop = GafferImage.Crop("Crop") crop["in"].setInput(checkerboard["out"]) crop["area"].setValue( imath.Box2i( imath.V2i(scale * bound[0][0], scale * bound[0][1]), imath.V2i(scale * bound[1][0], scale * bound[1][1]))) crop["affectDisplayWindow"].setValue(False) subImageNodes.append(checkerboard) subImageNodes.append(crop) testMerge["in"][-1].setInput(crop["out"]) testMerge["expression"] = Gaffer.Expression() testMerge["expression"].setExpression( 'parent["operation"] = context[ "loop:index" ]') inverseScale = GafferImage.ImageTransform() inverseScale["in"].setInput(testMerge["out"]) inverseScale["filter"].setValue("box") inverseScale["transform"]["scale"].setValue(imath.V2f(1.0 / scale)) crop1 = GafferImage.Crop() crop1["in"].setInput(inverseScale["out"]) crop1["area"].setValue(imath.Box2i(imath.V2i(0, 0), imath.V2i(64, 64))) loopInit = GafferImage.Constant() loopInit["format"].setValue(GafferImage.Format(896, 64, 1.000)) loopInit["color"].setValue(imath.Color4f(0)) loopOffset = GafferImage.Offset() loopOffset["in"].setInput(crop1["out"]) loopOffset["expression"] = Gaffer.Expression() loopOffset["expression"].setExpression( 'parent["offset"]["x"] = 64 * context[ "loop:index" ]') loopMerge = GafferImage.Merge() loopMerge["in"][1].setInput(loopOffset["out"]) loop = Gaffer.Loop() loop.setup(GafferImage.ImagePlug("in", )) loop["iterations"].setValue(14) loop["in"].setInput(loopInit["out"]) loop["next"].setInput(loopMerge["out"]) loopMerge["in"][0].setInput(loop["previous"]) # Uncomment for debug #imageWriter = GafferImage.ImageWriter( "ImageWriter" ) #imageWriter["in"].setInput( loop["out"] ) #imageWriter['openexr']['dataType'].setValue( "float" ) #imageWriter["fileName"].setValue( "/tmp/mergeBoundaries.exr" ) #imageWriter.execute() reader = GafferImage.ImageReader() reader["fileName"].setValue(self.mergeBoundariesRefPath) self.assertImagesEqual(loop["out"], reader["out"], ignoreMetadata=True, maxDifference=1e-5 if scale > 1 else 0) def testBoundaryCorrectness(self): self.runBoundaryCorrectness(1) self.runBoundaryCorrectness(2) self.runBoundaryCorrectness(4) self.runBoundaryCorrectness(8) self.runBoundaryCorrectness(16) self.runBoundaryCorrectness(32) def testEmptyDataWindowMerge(self): constant = GafferImage.Constant() constant["format"].setValue(GafferImage.Format(512, 512, 1.000)) constant["color"].setValue(imath.Color4f(1)) offset = GafferImage.Offset() offset["in"].setInput(constant["out"]) offset["offset"].setValue(imath.V2i(-1024)) emptyCrop = GafferImage.Crop() emptyCrop["in"].setInput(constant["out"]) emptyCrop["area"].setValue(imath.Box2i(imath.V2i(-10), imath.V2i(-100))) merge = GafferImage.Merge() merge["in"][0].setInput(offset["out"]) merge["in"][1].setInput(emptyCrop["out"]) self.assertEqual(merge["out"].dataWindow(), imath.Box2i(imath.V2i(-1024), imath.V2i(-512))) def mergePerf(self, operation, mismatch): r = GafferImage.Checkerboard("Checkerboard") r["format"].setValue(GafferImage.Format(4096, 3112, 1.000)) # Make the size of the checkerboard not a perfect multiple of tile size # in case we ever fix Checkerboard to notice when tiles are repeated # and return an identical hash ( which would invalidate this performance # test ) r["size"].setValue(imath.V2f(64.01)) alphaShuffle = GafferImage.Shuffle() alphaShuffle["in"].setInput(r["out"]) alphaShuffle["channels"].addChild( GafferImage.Shuffle.ChannelPlug("A", "R")) transform = GafferImage.Offset() transform["in"].setInput(alphaShuffle["out"]) if mismatch: transform["offset"].setValue(imath.V2i(4000, 3000)) else: transform["offset"].setValue(imath.V2i(26, 42)) merge = GafferImage.Merge() merge["operation"].setValue(operation) merge["in"][0].setInput(alphaShuffle["out"]) merge["in"][1].setInput(transform["out"]) # Precache upstream network, we're only interested in the performance of Merge GafferImageTest.processTiles(alphaShuffle["out"]) GafferImageTest.processTiles(transform["out"]) with GafferTest.TestRunner.PerformanceScope(): GafferImageTest.processTiles(merge["out"]) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testAddPerf(self): self.mergePerf(GafferImage.Merge.Operation.Add, False) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testAddMismatchPerf(self): self.mergePerf(GafferImage.Merge.Operation.Add, True) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testAtopPerf(self): self.mergePerf(GafferImage.Merge.Operation.Atop, False) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testAtopMismatchPerf(self): self.mergePerf(GafferImage.Merge.Operation.Atop, True) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testDividePerf(self): self.mergePerf(GafferImage.Merge.Operation.Divide, False) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testDivideMismatchPerf(self): self.mergePerf(GafferImage.Merge.Operation.Divide, True) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testInPerf(self): self.mergePerf(GafferImage.Merge.Operation.In, False) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testInMismatchPerf(self): self.mergePerf(GafferImage.Merge.Operation.In, True) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testOutPerf(self): self.mergePerf(GafferImage.Merge.Operation.Out, False) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testOutMismatchPerf(self): self.mergePerf(GafferImage.Merge.Operation.Out, True) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testMaskPerf(self): self.mergePerf(GafferImage.Merge.Operation.Mask, False) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testMaskMismatchPerf(self): self.mergePerf(GafferImage.Merge.Operation.Mask, True) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testOutPerf(self): self.mergePerf(GafferImage.Merge.Operation.Out, False) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testOutMismatchPerf(self): self.mergePerf(GafferImage.Merge.Operation.Out, True) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testMaskPerf(self): self.mergePerf(GafferImage.Merge.Operation.Mask, False) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testMaskMismatchPerf(self): self.mergePerf(GafferImage.Merge.Operation.Mask, True) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testMattePerf(self): self.mergePerf(GafferImage.Merge.Operation.Matte, False) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testMatteMismatchPerf(self): self.mergePerf(GafferImage.Merge.Operation.Matte, True) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testMultiplyPerf(self): self.mergePerf(GafferImage.Merge.Operation.Multiply, False) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testMultiplyMismatchPerf(self): self.mergePerf(GafferImage.Merge.Operation.Multiply, True) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testOverPerf(self): self.mergePerf(GafferImage.Merge.Operation.Over, False) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testOverMismatchPerf(self): self.mergePerf(GafferImage.Merge.Operation.Over, True) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testSubtractPerf(self): self.mergePerf(GafferImage.Merge.Operation.Subtract, False) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testSubtractMismatchPerf(self): self.mergePerf(GafferImage.Merge.Operation.Subtract, True) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testDifferencePerf(self): self.mergePerf(GafferImage.Merge.Operation.Difference, False) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testDifferenceMismatchPerf(self): self.mergePerf(GafferImage.Merge.Operation.Difference, True) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testUnderPerf(self): self.mergePerf(GafferImage.Merge.Operation.Under, False) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testUnderMismatchPerf(self): self.mergePerf(GafferImage.Merge.Operation.Under, True) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testMinPerf(self): self.mergePerf(GafferImage.Merge.Operation.Min, False) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testMinMismatchPerf(self): self.mergePerf(GafferImage.Merge.Operation.Min, True) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testMaxPerf(self): self.mergePerf(GafferImage.Merge.Operation.Max, False) @unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform") @GafferTest.TestRunner.PerformanceTestMethod(repeat=5) def testMaxMismatchPerf(self): self.mergePerf(GafferImage.Merge.Operation.Max, True)
def envHasMemoryIssues(): return GafferTest.inCI() and (Gaffer.isDebug() or sys.platform == 'darwin')
class SceneGadgetTest(GafferUITest.TestCase): def testBound(self): s = Gaffer.ScriptNode() s["p"] = GafferScene.Plane() s["g"] = GafferScene.Group() s["g"]["in"][0].setInput(s["p"]["out"]) s["g"]["transform"]["translate"]["x"].setValue(2) sg = GafferSceneUI.SceneGadget() sg.setScene(s["g"]["out"]) sg.waitForCompletion() self.assertEqual(sg.bound(), s["g"]["out"].bound("/")) s["g"]["transform"]["translate"]["y"].setValue(4) sg.waitForCompletion() self.assertEqual(sg.bound(), s["g"]["out"].bound("/")) def assertObjectAt(self, gadget, ndcPosition, path): viewportGadget = gadget.ancestor(GafferUI.ViewportGadget) rasterPosition = ndcPosition * imath.V2f(viewportGadget.getViewport()) gadgetLine = viewportGadget.rasterToGadgetSpace(rasterPosition, gadget) self.assertEqual(gadget.objectAt(gadgetLine), path) def assertObjectsAt(self, gadget, ndcBox, paths): viewportGadget = gadget.ancestor(GafferUI.ViewportGadget) rasterMin = ndcBox.min() * imath.V2f(viewportGadget.getViewport()) rasterMax = ndcBox.max() * imath.V2f(viewportGadget.getViewport()) gadgetMin = viewportGadget.rasterToGadgetSpace(rasterMin, gadget).p0 gadgetMax = viewportGadget.rasterToGadgetSpace(rasterMax, gadget).p1 objectsAt = IECore.PathMatcher() gadget.objectsAt(gadgetMin, gadgetMax, objectsAt) objects = set(objectsAt.paths()) expectedObjects = set(IECore.PathMatcher(paths).paths()) self.assertEqual(objects, expectedObjects) def testObjectVisibility(self): s = Gaffer.ScriptNode() s["s"] = GafferScene.Sphere() s["g"] = GafferScene.Group() s["g"]["in"][0].setInput(s["s"]["out"]) s["a"] = GafferScene.StandardAttributes() s["a"]["in"].setInput(s["g"]["out"]) sg = GafferSceneUI.SceneGadget() sg.setMinimumExpansionDepth(1) sg.setScene(s["a"]["out"]) with GafferUI.Window() as w: gw = GafferUI.GadgetWidget(sg) w.setVisible(True) self.waitForIdle(1000) sg.waitForCompletion() gw.getViewportGadget().frame(sg.bound()) self.assertObjectAt( sg, imath.V2f(0.5), IECore.InternedStringVectorData(["group", "sphere"])) s["a"]["attributes"]["visibility"]["enabled"].setValue(True) s["a"]["attributes"]["visibility"]["value"].setValue(False) sg.waitForCompletion() self.assertObjectAt(sg, imath.V2f(0.5), None) s["a"]["attributes"]["visibility"]["enabled"].setValue(True) s["a"]["attributes"]["visibility"]["value"].setValue(True) sg.waitForCompletion() self.assertObjectAt( sg, imath.V2f(0.5), IECore.InternedStringVectorData(["group", "sphere"])) @unittest.skipIf(GafferTest.inCI(), "Unknown problem running in cloud") def testExpansion(self): s = Gaffer.ScriptNode() s["s"] = GafferScene.Sphere() s["g"] = GafferScene.Group() s["g"]["in"][0].setInput(s["s"]["out"]) s["a"] = GafferScene.StandardAttributes() s["a"]["in"].setInput(s["g"]["out"]) sg = GafferSceneUI.SceneGadget() sg.setScene(s["a"]["out"]) with GafferUI.Window() as w: gw = GafferUI.GadgetWidget(sg) w.setVisible(True) self.waitForIdle(10000) sg.waitForCompletion() gw.getViewportGadget().frame(sg.bound()) self.waitForIdle(10000) self.assertObjectAt(sg, imath.V2f(0.5), None) self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(1)), ["/group"]) sg.setExpandedPaths(IECore.PathMatcher(["/group"])) sg.waitForCompletion() self.assertObjectAt( sg, imath.V2f(0.5), IECore.InternedStringVectorData(["group", "sphere"])) self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(1)), ["/group/sphere"]) sg.setExpandedPaths(IECore.PathMatcher([])) sg.waitForCompletion() self.assertObjectAt(sg, imath.V2f(0.5), None) self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(1)), ["/group"]) def testExpressions(self): s = Gaffer.ScriptNode() s["p"] = GafferScene.Plane() s["g"] = GafferScene.Group() s["g"]["in"][0].setInput(s["p"]["out"]) s["g"]["in"][1].setInput(s["p"]["out"]) s["g"]["in"][2].setInput(s["p"]["out"]) s["e"] = Gaffer.Expression() s["e"].setExpression( "parent['p']['dimensions']['x'] = 1 + context.getFrame() * 0.1") g = GafferSceneUI.SceneGadget() g.setScene(s["g"]["out"]) g.bound() def testGLResourceDestruction(self): s = Gaffer.ScriptNode() s["p"] = GafferScene.Plane() s["g"] = GafferScene.Group() s["g"]["in"][0].setInput(s["p"]["out"]) s["g"]["in"][1].setInput(s["p"]["out"]) s["g"]["in"][2].setInput(s["p"]["out"]) s["g"]["in"][3].setInput(s["p"]["out"]) sg = GafferSceneUI.SceneGadget() sg.setScene(s["g"]["out"]) sg.setMinimumExpansionDepth(2) with GafferUI.Window() as w: gw = GafferUI.GadgetWidget(sg) w.setVisible(True) # Reduce the GL cache size so that not everything will fit, and we'll # need to dispose of some objects. We can't dispose of objects on any # old thread, just the main GL thread, so it's important that we test # that we're doing that appropriately. IECoreGL.CachedConverter.defaultCachedConverter().setMaxMemory(100) for i in range(1, 1000): s["p"]["dimensions"]["x"].setValue(i) self.waitForIdle(10) def testExceptionsDuringCompute(self): # Make this scene # # - bigSphere # - littleSphere (with exception in attributes expression) s = Gaffer.ScriptNode() s["s1"] = GafferScene.Sphere() s["s1"]["name"].setValue("bigSphere") s["s2"] = GafferScene.Sphere() s["s2"]["name"].setValue("littleSphere") s["s2"]["radius"].setValue(0.1) s["p"] = GafferScene.Parent() s["p"]["in"].setInput(s["s1"]["out"]) s["p"]["children"][0].setInput(s["s2"]["out"]) s["p"]["parent"].setValue("/bigSphere") s["a"] = GafferScene.StandardAttributes() s["a"]["in"].setInput(s["p"]["out"]) s["a"]["attributes"]["doubleSided"]["enabled"].setValue(True) s["e"] = Gaffer.Expression() s["e"].setExpression( 'parent["a"]["attributes"]["doubleSided"]["value"] = context["nonexistent"]' ) s["f"] = GafferScene.PathFilter() s["f"]["paths"].setValue( IECore.StringVectorData(["/bigSphere/littleSphere"])) s["a"]["filter"].setInput(s["f"]["out"]) # Try to view it sg = GafferSceneUI.SceneGadget() sg.setScene(s["a"]["out"]) sg.setMinimumExpansionDepth(4) with GafferUI.Window() as w: gw = GafferUI.GadgetWidget(sg) gw.getViewportGadget().setPlanarMovement(False) gw.getViewportGadget().setCamera( IECoreScene.Camera(parameters={ "projection": "perspective", })) originalMessageHandler = IECore.MessageHandler.getDefaultHandler() mh = IECore.CapturingMessageHandler() IECore.MessageHandler.setDefaultHandler( IECore.LevelFilteredMessageHandler( mh, IECore.LevelFilteredMessageHandler.defaultLevel())) try: w.setVisible(True) self.waitForIdle(1000) sg.waitForCompletion() # Check we were told about the problem self.assertEqual(len(mh.messages), 1) self.assertEqual(mh.messages[0].level, mh.Level.Error) self.assertTrue("nonexistent" in mh.messages[0].message) # And that there isn't some half-assed partial scene # being displayed. self.assertTrue(sg.bound().isEmpty()) gw.getViewportGadget().frame( imath.Box3f(imath.V3f(-1), imath.V3f(1))) self.assertObjectAt(sg, imath.V2f(0.5), None) # And that redraws don't cause more fruitless attempts # to compute the scene. gw.getViewportGadget().frame( imath.Box3f(imath.V3f(-1.1), imath.V3f(1.1))) self.waitForIdle(1000) self.assertEqual(len(mh.messages), 1) self.assertObjectAt(sg, imath.V2f(0.5), None) self.assertTrue(sg.bound().isEmpty()) # Fix the problem with the scene, and check that we can see something now s["f"]["enabled"].setValue(False) sg.waitForCompletion() self.assertEqual(len(mh.messages), 1) self.assertFalse(sg.bound().isEmpty()) self.assertObjectAt(sg, imath.V2f(0.5), IECore.InternedStringVectorData(["bigSphere"])) finally: IECore.MessageHandler.setDefaultHandler(originalMessageHandler) def testObjectsAtBox(self): plane = GafferScene.Plane() sphere = GafferScene.Sphere() sphere["radius"].setValue(0.25) instancer = GafferScene.Instancer() instancer["in"].setInput(plane["out"]) instancer["prototypes"].setInput(sphere["out"]) instancer["parent"].setValue("/plane") subTree = GafferScene.SubTree() subTree["in"].setInput(instancer["out"]) subTree["root"].setValue("/plane") sg = GafferSceneUI.SceneGadget() sg.setScene(subTree["out"]) sg.setMinimumExpansionDepth(100) with GafferUI.Window() as w: gw = GafferUI.GadgetWidget(sg) w.setVisible(True) self.waitForIdle(10000) gw.getViewportGadget().frame(sg.bound()) self.waitForIdle(10000) self.assertObjectsAt( sg, imath.Box2f(imath.V2f(0), imath.V2f(1)), ["/instances/sphere/{}".format(i) for i in range(0, 4)]) self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(0.5)), ["/instances/sphere/2"]) self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0.5, 0), imath.V2f(1, 0.5)), ["/instances/sphere/3"]) self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0, 0.5), imath.V2f(0.5, 1)), ["/instances/sphere/0"]) self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0.5), imath.V2f(1)), ["/instances/sphere/1"]) def testObjectAtLine(self): cubes = [] names = ("left", "center", "right") for i in range(3): cube = GafferScene.Cube() cube["transform"]["translate"].setValue( imath.V3f((i - 1) * 2.0, 0.0, -2.5)) cube["name"].setValue(names[i]) cubes.append(cube) group = GafferScene.Group() for i, cube in enumerate(cubes): group["in"][i].setInput(cube["out"]) sg = GafferSceneUI.SceneGadget() sg.setScene(group["out"]) sg.setMinimumExpansionDepth(100) with GafferUI.Window() as w: gw = GafferUI.GadgetWidget(sg) w.setVisible(True) self.waitForIdle(10000) vp = gw.getViewportGadget() # This is the single most important line in this test. If you don't set # this to false, you get an orthographic camera, even if you set a # perspective projection. vp.setPlanarMovement(False) c = IECoreScene.Camera() c.setProjection("perspective") c.setFocalLength(35) c.setAperture(imath.V2f(36, 24)) vp.setCamera(c) cameraTransform = imath.M44f() cameraTransform.translate(imath.V3f(0, 0, 2)) vp.setCameraTransform(cameraTransform) self.waitForIdle(10000) # We assume in this case, that gadget space is world space leftCubeDir = IECore.LineSegment3f(imath.V3f(0, 0, 2), imath.V3f(-2, 0, -2)) pathA = sg.objectAt(leftCubeDir) pathB, hitPoint = sg.objectAndIntersectionAt(leftCubeDir) self.assertIsNotNone(pathA) self.assertEqual(pathA, IECore.InternedStringVectorData(["group", "left"])) self.assertEqual(pathA, pathB) self.assertAlmostEqual(hitPoint.x, -2, delta=0.01) self.assertAlmostEqual(hitPoint.y, 0, delta=0.01) self.assertAlmostEqual(hitPoint.z, -2, delta=0.01) centerCubeDir = IECore.LineSegment3f(imath.V3f(0, 0, 1), imath.V3f(0, 0, -1)) pathA = sg.objectAt(centerCubeDir) pathB, hitPoint = sg.objectAndIntersectionAt(centerCubeDir) self.assertIsNotNone(pathA) self.assertEqual(pathA, IECore.InternedStringVectorData(["group", "center"])) self.assertEqual(pathA, pathB) self.assertAlmostEqual(hitPoint.x, 0, delta=0.01) self.assertAlmostEqual(hitPoint.y, 0, delta=0.01) self.assertAlmostEqual(hitPoint.z, -2, delta=0.01) rightCubeDir = IECore.LineSegment3f(imath.V3f(0, 0, 2), imath.V3f(2, 0, -2)) pathA = sg.objectAt(rightCubeDir) pathB, hitPoint = sg.objectAndIntersectionAt(rightCubeDir) self.assertIsNotNone(pathA) self.assertEqual(pathA, IECore.InternedStringVectorData(["group", "right"])) self.assertEqual(pathA, pathB) self.assertAlmostEqual(hitPoint.x, 2, delta=0.01) self.assertAlmostEqual(hitPoint.y, 0, delta=0.01) self.assertAlmostEqual(hitPoint.z, -2, delta=0.01) missDir = IECore.LineSegment3f(imath.V3f(0, 0, 2), imath.V3f(0, 10, -2)) pathA = sg.objectAt(missDir) pathB, hitPoint = sg.objectAndIntersectionAt(missDir) self.assertIsNone(pathA) self.assertIsNone(pathB) def testSetAndGetScene(self): plane = GafferScene.Plane() sphere = GafferScene.Sphere() sg = GafferSceneUI.SceneGadget() self.assertEqual(sg.getScene(), None) sg.setScene(plane["out"]) self.assertEqual(sg.getScene(), plane["out"]) sg.setScene(sphere["out"]) self.assertEqual(sg.getScene(), sphere["out"]) def testBoundOfUnexpandedEmptyChildren(self): group1 = GafferScene.Group() group2 = GafferScene.Group() group2["in"][0].setInput(group1["out"]) sg = GafferSceneUI.SceneGadget() sg.setScene(group2["out"]) sg.waitForCompletion() self.assertEqual(sg.bound(), imath.Box3f()) def testSelectionMaskAccessors(self): sg = GafferSceneUI.SceneGadget() self.assertEqual(sg.getSelectionMask(), None) m = IECore.StringVectorData(["MeshPrimitive"]) sg.setSelectionMask(m) self.assertEqual(sg.getSelectionMask(), m) m.append("Camera") self.assertNotEqual(sg.getSelectionMask(), m) sg.setSelectionMask(m) self.assertEqual(sg.getSelectionMask(), m) sg.setSelectionMask(None) self.assertEqual(sg.getSelectionMask(), None) def testSelectionMask(self): plane = GafferScene.Plane() plane["dimensions"].setValue(imath.V2f(10)) plane["transform"]["translate"]["z"].setValue(4) camera = GafferScene.Camera() group = GafferScene.Group() group["in"][0].setInput(plane["out"]) group["in"][1].setInput(camera["out"]) sg = GafferSceneUI.SceneGadget() sg.setScene(group["out"]) sg.setMinimumExpansionDepth(100) with GafferUI.Window() as w: gw = GafferUI.GadgetWidget(sg) w.setVisible(True) self.waitForIdle(10000) sg.waitForCompletion() gw.getViewportGadget().frame(sg.bound(), imath.V3f(0, 0, -1)) self.waitForIdle(10000) self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(1)), ["/group/plane", "/group/camera"]) sg.setSelectionMask(IECore.StringVectorData(["MeshPrimitive"])) self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(1)), ["/group/plane"]) sg.setSelectionMask(IECore.StringVectorData(["Camera"])) self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(1)), ["/group/camera"]) def setUp(self): GafferUITest.TestCase.setUp(self) self.__cachedConverterMaxMemory = IECoreGL.CachedConverter.defaultCachedConverter( ).getMaxMemory() def tearDown(self): GafferUITest.TestCase.tearDown(self) IECoreGL.CachedConverter.defaultCachedConverter().setMaxMemory( self.__cachedConverterMaxMemory)