def test(self): # Make a few input scenes script = Gaffer.ScriptNode() script["Cube"] = GafferScene.Cube("Cube") script["Group"] = GafferScene.Group("Group") script["Group"]["in"][0].setInput(script["Cube"]["out"]) script["Group"]["transform"]["translate"].setValue(imath.V3f(30)) script["PathFilter"] = GafferScene.PathFilter("PathFilter") script["PathFilter"]["paths"].setValue( IECore.StringVectorData(['/group/cube'])) script["Transform"] = GafferScene.Transform("Transform") script["Transform"]["in"].setInput(script["Group"]["out"]) script["Transform"]["filter"].setInput(script["PathFilter"]["out"]) script["CustomAttributes"] = GafferScene.CustomAttributes( "CustomAttributes") script["CustomAttributes"]["in"].setInput(script["Transform"]["out"]) script["CustomAttributes"]["filter"].setInput( script["PathFilter"]["out"]) script['CustomAttributes']['attributes'].addChild( Gaffer.NameValuePlug("existingAttr", IECore.StringData("test"))) script["CollectTransforms"] = GafferScene.CollectTransforms( "CollectTransforms") script["CollectTransforms"]["in"].setInput( script["CustomAttributes"]["out"]) script["CollectTransforms"]["filter"].setInput( script["PathFilter"]["out"]) script["Expression1"] = Gaffer.Expression("Expression1") script["Expression1"].setExpression( """ s = context.get( "collect:transformName", "attr0" ) i = int( s[4] ) parent["Transform"]["transform"]["translate"] = imath.V3f( i ) """, "python") ref = IECore.CompoundObject() ref["existingAttr"] = IECore.StringData("test") self.assertEqual( script["CollectTransforms"]["out"].attributes("/group/cube"), ref) script["CollectTransforms"]["attributes"].setValue( IECore.StringVectorData(['attr1', 'attr2'])) ref["attr1"] = IECore.M44fData(imath.M44f().translate(imath.V3f(1))) ref["attr2"] = IECore.M44fData(imath.M44f().translate(imath.V3f(2))) self.assertEqual( script["CollectTransforms"]["out"].attributes("/group/cube"), ref) # Switch to writing custom variable, which expression doesn't use, so we don't get # special transforms script["CollectTransforms"]["attributeContextVariable"].setValue( "collect:customVar") ref["attr1"] = IECore.M44fData(imath.M44f().translate(imath.V3f(0))) ref["attr2"] = IECore.M44fData(imath.M44f().translate(imath.V3f(0))) self.assertEqual( script["CollectTransforms"]["out"].attributes("/group/cube"), ref) # Test requireVariation script["CollectTransforms"]["requireVariation"].setValue(True) del ref["attr1"] del ref["attr2"] self.assertEqual( script["CollectTransforms"]["out"].attributes("/group/cube"), ref) # Test reading custom variable script["Expression1"].setExpression( """ s = context.get( "collect:customVar", "attr0" ) i = int( s[4] ) parent["Transform"]["transform"]["translate"] = imath.V3f( i ) """, "python") ref["attr1"] = IECore.M44fData(imath.M44f().translate(imath.V3f(1))) ref["attr2"] = IECore.M44fData(imath.M44f().translate(imath.V3f(2))) self.assertEqual( script["CollectTransforms"]["out"].attributes("/group/cube"), ref) # Test space script["CollectTransforms"]["space"].setValue( GafferScene.Transform.Space.World) ref["attr1"] = IECore.M44fData(imath.M44f().translate(imath.V3f(31))) ref["attr2"] = IECore.M44fData(imath.M44f().translate(imath.V3f(32))) self.assertEqual( script["CollectTransforms"]["out"].attributes("/group/cube"), ref) script["CollectTransforms"]["attributeContextVariable"].setValue( "collect:bogus") del ref["attr1"] del ref["attr2"] self.assertEqual( script["CollectTransforms"]["out"].attributes("/group/cube"), ref) script["CollectTransforms"]["requireVariation"].setValue(False) ref["attr1"] = IECore.M44fData(imath.M44f().translate(imath.V3f(30))) ref["attr2"] = IECore.M44fData(imath.M44f().translate(imath.V3f(30))) self.assertEqual( script["CollectTransforms"]["out"].attributes("/group/cube"), ref) script["CollectTransforms"]["space"].setValue( GafferScene.Transform.Space.Local) # Test overwriting existing attribute script["Expression1"].setExpression("", "python") script["CollectTransforms"]["requireVariation"].setValue(False) script["CollectTransforms"]["attributes"].setValue( IECore.StringVectorData(['existingAttr'])) ref = IECore.CompoundObject() ref["existingAttr"] = IECore.M44fData(imath.M44f().translate( imath.V3f(0))) self.assertEqual( script["CollectTransforms"]["out"].attributes("/group/cube"), ref) # Test naughtily pulling directly on the "transforms" plug self.assertEqual(script["CollectTransforms"]["transforms"].getValue(), IECore.CompoundObject()) context = Gaffer.Context() context.set("scene:path", IECore.InternedStringVectorData(["group", "cube"])) with context: self.assertEqual( script["CollectTransforms"]["transforms"].getValue(), ref)
def testDynamicPlugsAndGIL(self): script = Gaffer.ScriptNode() script["plane"] = GafferScene.Plane() script["plane"]["divisions"].setValue(IECore.V2i(20)) script["sphere"] = GafferScene.Sphere() script["expression"] = Gaffer.Expression() script["expression"].setExpression( "parent['sphere']['radius'] = context.getFrame() + float( context['instancer:id'] )" ) script["instancer"] = GafferScene.Instancer() script["instancer"]["in"].setInput(script["plane"]["out"]) script["instancer"]["instance"].setInput(script["sphere"]["out"]) script["instancer"]["parent"].setValue("/plane") script["attributes"] = GafferScene.CustomAttributes() script["attributes"]["in"].setInput(script["instancer"]["out"]) script["outputs"] = GafferScene.Outputs() script["outputs"]["in"].setInput(script["attributes"]["out"]) # Simulate an InteractiveRender or Viewer traversal of the scene # every time it is dirtied. If the GIL isn't released when dirtiness # is signalled, we'll end up with a deadlock as the traversal enters # python on another thread to evaluate the expression. We increment the frame # between each test to ensure the expression result is not cached and # we do truly enter python. traverseConnection = Gaffer.ScopedConnection( GafferSceneTest.connectTraverseSceneToPlugDirtiedSignal( script["outputs"]["out"])) with Gaffer.Context() as c: c.setFrame(1) script["attributes"]["attributes"].addMember( "test1", IECore.IntData(10)) c.setFrame(2) script["attributes"]["attributes"].addOptionalMember( "test2", IECore.IntData(20)) c.setFrame(3) script["attributes"]["attributes"].addMembers( IECore.CompoundData({ "test3": 30, "test4": 40, })) c.setFrame(4) p = script["attributes"]["attributes"][0] del script["attributes"]["attributes"][p.getName()] c.setFrame(5) script["attributes"]["attributes"].addChild(p) c.setFrame(6) script["attributes"]["attributes"].removeChild(p) c.setFrame(7) script["attributes"]["attributes"].setChild(p.getName(), p) c.setFrame(8) script["attributes"]["attributes"].removeChild(p) c.setFrame(9) script["attributes"]["attributes"][p.getName()] = p c.setFrame(10) script["outputs"].addOutput( "test", IECoreScene.Display("beauty.exr", "exr", "rgba"))
def testNullPaths( self ) : f = GafferScene.PathFilter() with Gaffer.Context() as c : c["scene:path"] = IECore.InternedStringVectorData( [ "a" ] ) self.assertEqual( f["out"].getValue(), int( IECore.PathMatcher.Result.NoMatch ) )
def __testExtension( self, ext, formatName, options = {}, metadataToIgnore = [] ) : r = GafferImage.ImageReader() r["fileName"].setValue( self.__rgbFilePath+".exr" ) expectedFile = self.__rgbFilePath+"."+ext tests = [ { 'name': "default", 'plugs': {}, 'metadata': options.get( "metadata", {} ), 'maxError': options.get( "maxError", 0.0 ) } ] for optPlugName in options['plugs'] : for optPlugVal in options['plugs'][optPlugName] : name = "{}_{}".format(optPlugName, optPlugVal['value']) optMetadata = dict(options.get( "metadata", {} )) optMetadata.update( optPlugVal.get( "metadata", {} ) ) tests.append( { 'name': name, 'plugs': { optPlugName: optPlugVal['value'] }, 'metadata': optMetadata, 'maxError': optPlugVal.get( "maxError", options['maxError'] ) } ) for test in tests: name = test['name'] maxError = test['maxError'] overrideMetadata = test['metadata'] testFile = self.__testFile( name, "RGBA", ext ) self.failIf( os.path.exists( testFile ), "Temporary file already exists : {}".format( testFile ) ) # Setup the writer. w = GafferImage.ImageWriter() w["in"].setInput( r["out"] ) w["fileName"].setValue( testFile ) w["channels"].setValue( IECore.StringVectorData( r["out"]["channelNames"].getValue() ) ) for opt in test['plugs']: w[formatName][opt].setValue( test['plugs'][opt] ) # Execute with Gaffer.Context() : w["task"].execute() self.failUnless( os.path.exists( testFile ), "Failed to create file : {} ({}) : {}".format( ext, name, testFile ) ) # Check the output. expectedOutput = GafferImage.ImageReader() expectedOutput["fileName"].setValue( expectedFile ) writerOutput = GafferImage.ImageReader() writerOutput["fileName"].setValue( testFile ) expectedMetadata = expectedOutput["out"]["metadata"].getValue() writerMetadata = writerOutput["out"]["metadata"].getValue() # they were written at different times so # we can't expect those values to match if "DateTime" in writerMetadata : expectedMetadata["DateTime"] = writerMetadata["DateTime"] # the writer adds several standard attributes that aren't in the original file expectedMetadata["Software"] = IECore.StringData( "Gaffer " + Gaffer.About.versionString() ) expectedMetadata["HostComputer"] = IECore.StringData( platform.node() ) expectedMetadata["Artist"] = IECore.StringData( os.getlogin() ) expectedMetadata["DocumentName"] = IECore.StringData( "untitled" ) for key in overrideMetadata : expectedMetadata[key] = overrideMetadata[key] # some formats support IPTC standards, and some of the standard metadata # is translated automatically by OpenImageIO. for key in writerMetadata.keys() : if key.startswith( "IPTC:" ) : expectedMetadata["IPTC:OriginatingProgram"] = expectedMetadata["Software"] expectedMetadata["IPTC:Creator"] = expectedMetadata["Artist"] break # some input files don't contain all the metadata that the ImageWriter # will create, and some output files don't support all the metadata # that the ImageWriter attempt to create. for metaName in metadataToIgnore : if metaName in writerMetadata : del writerMetadata[metaName] if metaName in expectedMetadata : del expectedMetadata[metaName] for metaName in expectedMetadata.keys() : self.assertTrue( metaName in writerMetadata.keys(), "Writer Metadata missing expected key \"{}\" set to \"{}\" : {} ({})".format(metaName, str(expectedMetadata[metaName]), ext, name) ) self.assertEqual( expectedMetadata[metaName], writerMetadata[metaName], "Metadata does not match for key \"{}\" : {} ({})".format(metaName, ext, name) ) op = IECore.ImageDiffOp() op["maxError"].setValue( maxError ) res = op( imageA = expectedOutput["out"].image(), imageB = writerOutput["out"].image() ) if res.value : matchingError = 0.0 for i in range( 10 ) : maxError += 0.1 op["maxError"].setValue( maxError ) res = op( imageA = expectedOutput["out"].image(), imageB = writerOutput["out"].image() ) if not res.value : matchingError = maxError break if matchingError > 0.0 : self.assertFalse( True, "Image data does not match : {} ({}). Matches with max error of {}".format( ext, name, matchingError ) ) else: self.assertFalse( True, "Image data does not match : {} ({}).".format( ext, name ) )
def __testExtension( self, ext, formatName, options = {}, metadataToIgnore = [] ) : r = GafferImage.ImageReader() r["fileName"].setValue( self.__rgbFilePath+".exr" ) expectedFile = "{base}.{ext}".format( base=self.__rgbFilePath, ext=ext ) tests = [ { 'name': "default", 'plugs': {}, 'metadata': options.get( "metadata", {} ), 'maxError': options.get( "maxError", 0.0 ) } ] for optPlugName in options['plugs'] : for optPlugVal in options['plugs'][optPlugName] : name = "{}_{}".format(optPlugName, optPlugVal['value']) optMetadata = dict(options.get( "metadata", {} )) optMetadata.update( optPlugVal.get( "metadata", {} ) ) tests.append( { 'name': name, 'plugs': { optPlugName: optPlugVal['value'] }, 'metadata': optMetadata, 'maxError': optPlugVal.get( "maxError", options['maxError'] ) } ) for test in tests: name = test['name'] maxError = test['maxError'] overrideMetadata = test['metadata'] testFile = self.__testFile( name, "RGBA", ext ) self.failIf( os.path.exists( testFile ), "Temporary file already exists : {}".format( testFile ) ) # Setup the writer. w = GafferImage.ImageWriter() w["in"].setInput( r["out"] ) w["fileName"].setValue( testFile ) w["channels"].setValue( "*" ) for opt in test['plugs']: w[formatName][opt].setValue( test['plugs'][opt] ) # Execute with Gaffer.Context() : w["task"].execute() self.failUnless( os.path.exists( testFile ), "Failed to create file : {} ({}) : {}".format( ext, name, testFile ) ) # Check the output. expectedOutput = GafferImage.ImageReader() expectedOutput["fileName"].setValue( expectedFile ) writerOutput = GafferImage.ImageReader() writerOutput["fileName"].setValue( testFile ) expectedMetadata = expectedOutput["out"]["metadata"].getValue() writerMetadata = writerOutput["out"]["metadata"].getValue() # they were written at different times so # we can't expect those values to match if "DateTime" in writerMetadata : expectedMetadata["DateTime"] = writerMetadata["DateTime"] # the writer adds several standard attributes that aren't in the original file expectedMetadata["Software"] = IECore.StringData( "Gaffer " + Gaffer.About.versionString() ) expectedMetadata["HostComputer"] = IECore.StringData( platform.node() ) expectedMetadata["Artist"] = IECore.StringData( os.environ["USER"] ) expectedMetadata["DocumentName"] = IECore.StringData( "untitled" ) for key in overrideMetadata : expectedMetadata[key] = overrideMetadata[key] self.__addExpectedIPTCMetadata( writerMetadata, expectedMetadata ) for metaName in expectedMetadata.keys() : if metaName in metadataToIgnore : continue if metaName in ( "fileFormat", "dataType" ) : # These are added on automatically by the ImageReader, and # we can't expect them to be the same when converting between # image formats. continue self.assertTrue( metaName in writerMetadata.keys(), "Writer Metadata missing expected key \"{}\" set to \"{}\" : {} ({})".format(metaName, str(expectedMetadata[metaName]), ext, name) ) self.assertEqual( expectedMetadata[metaName], writerMetadata[metaName], "Metadata does not match for key \"{}\" : {} ({})".format(metaName, ext, name) ) self.assertImagesEqual( expectedOutput["out"], writerOutput["out"], maxDifference = maxError, ignoreMetadata = True )
def testAllRenderedSignal(self): class AllRenderedTest(object): allRenderedSignalCalled = False def allRendered(self): self.allRenderedSignalCalled = True script = Gaffer.ScriptNode() script["plane"] = GafferScene.Plane() # test creating/deleting a single procedural: t = AllRenderedTest() allRenderedConnection = GafferScene.SceneProcedural.allRenderedSignal( ).connect(t.allRendered) procedural = GafferScene.SceneProcedural(script["plane"]["out"], Gaffer.Context(), "/") self.assertEqual(t.allRenderedSignalCalled, False) del procedural self.assertEqual(t.allRenderedSignalCalled, True) # create/delete two of 'em: t = AllRenderedTest() allRenderedConnection = GafferScene.SceneProcedural.allRenderedSignal( ).connect(t.allRendered) procedural1 = GafferScene.SceneProcedural(script["plane"]["out"], Gaffer.Context(), "/") procedural2 = GafferScene.SceneProcedural(script["plane"]["out"], Gaffer.Context(), "/") self.assertEqual(t.allRenderedSignalCalled, False) del procedural1 self.assertEqual(t.allRenderedSignalCalled, False) del procedural2 self.assertEqual(t.allRenderedSignalCalled, True) # now actually render them: renderer = IECore.CapturingRenderer() t = AllRenderedTest() allRenderedConnection = GafferScene.SceneProcedural.allRenderedSignal( ).connect(t.allRendered) procedural1 = GafferScene.SceneProcedural(script["plane"]["out"], Gaffer.Context(), "/") procedural2 = GafferScene.SceneProcedural(script["plane"]["out"], Gaffer.Context(), "/") self.assertEqual(t.allRenderedSignalCalled, False) with IECore.WorldBlock(renderer): renderer.procedural(procedural1) self.assertEqual(t.allRenderedSignalCalled, False) with IECore.WorldBlock(renderer): renderer.procedural(procedural2) self.assertEqual(t.allRenderedSignalCalled, True) # now render one and delete one: renderer = IECore.CapturingRenderer() t = AllRenderedTest() allRenderedConnection = GafferScene.SceneProcedural.allRenderedSignal( ).connect(t.allRendered) procedural1 = GafferScene.SceneProcedural(script["plane"]["out"], Gaffer.Context(), "/") procedural2 = GafferScene.SceneProcedural(script["plane"]["out"], Gaffer.Context(), "/") self.assertEqual(t.allRenderedSignalCalled, False) del procedural1 self.assertEqual(t.allRenderedSignalCalled, False) with IECore.WorldBlock(renderer): renderer.procedural(procedural2) self.assertEqual(t.allRenderedSignalCalled, True)
class NodeToolbar( GafferUI.Widget ) : __fallbackContext = Gaffer.Context() def __init__( self, node, topLevelWidget, **kw ) : GafferUI.Widget.__init__( self, topLevelWidget, **kw ) self.__node = node scriptNode = self.__node.scriptNode() self.__context = scriptNode.context() if scriptNode is not None else self.__fallbackContext ## Returns the node the toolbar represents. def node( self ) : return self.__node def getContext( self ) : return self.__context def setContext( self, context ) : self.__context = context ## Creates a NodeToolbar instance for the specified node and edge. # Note that not all nodes have toolbars, so None may be returned. @classmethod def create( cls, node, edge = GafferUI.Edge.Top ) : # Try to create a toolbar using metadata. toolbarType = Gaffer.Metadata.value( node, "nodeToolbar:%s:type" % str( edge ).lower() ) if toolbarType is not None : if toolbarType == "" : return None path = toolbarType.split( "." ) toolbarClass = __import__( path[0] ) for n in path[1:] : toolbarClass = getattr( toolbarClass, n ) return toolbarClass( node ) # Fall back to deprecated registry. if edge == GafferUI.Edge.Top : nodeHierarchy = IECore.RunTimeTyped.baseTypeIds( node.typeId() ) for typeId in [ node.typeId() ] + nodeHierarchy : creator = cls.__creators.get( typeId, None ) if creator is not None : return creator( node ) return None __creators = {} ## Registers a subclass of NodeToolbar to be used with a specific node type. ## \deprecated. Use "nodeToolbar:top|bottom|left|right:type" metadata instead. @classmethod def registerCreator( cls, nodeClassOrTypeId, toolbarCreator ) : assert( callable( toolbarCreator ) ) if isinstance( nodeClassOrTypeId, IECore.TypeId ) : nodeTypeId = nodeClassOrTypeId else : nodeTypeId = nodeClassOrTypeId.staticTypeId() cls.__creators[nodeTypeId] = toolbarCreator
def testHash(self): c1 = Gaffer.Context() c1.setFrame(1) c2 = Gaffer.Context() c2.setFrame(2) c3 = Gaffer.Context() c3.setFrame(3.0) # Hashes that don't use the context are equivalent n = GafferDispatchTest.LoggingTaskNode() with c1: c1h = n["task"].hash() with c2: c2h = n["task"].hash() with c3: c3h = n["task"].hash() self.assertEqual(c1h, c2h) self.assertEqual(c1h, c3h) # Hashes that do use the context differ n2 = GafferDispatchTest.LoggingTaskNode() n2["frameSensitivePlug"] = Gaffer.StringPlug(defaultValue="####") with c1: c1h = n2["task"].hash() with c2: c2h = n2["task"].hash() with c3: c3h = n2["task"].hash() self.assertNotEqual(c1h, c2h) self.assertNotEqual(c1h, c3h) # Hashes match across the same node type n3 = GafferDispatchTest.LoggingTaskNode() n3["frameSensitivePlug"] = Gaffer.StringPlug(defaultValue="####") with c1: c1h2 = n3["task"].hash() with c2: c2h2 = n3["task"].hash() with c3: c3h2 = n3["task"].hash() self.assertEqual(c1h, c1h2) self.assertEqual(c2h, c2h2) self.assertEqual(c3h, c3h2) # Hashes differ across different node types class MyNode(GafferDispatchTest.LoggingTaskNode): def __init__(self): GafferDispatchTest.LoggingTaskNode.__init__(self) IECore.registerRunTimeTyped(MyNode) n4 = MyNode() n4["frameSensitivePlug"] = Gaffer.StringPlug(defaultValue="####") with c1: c1h3 = n4["task"].hash() with c2: c2h3 = n4["task"].hash() with c3: c3h3 = n4["task"].hash() self.assertNotEqual(c1h3, c1h2) self.assertNotEqual(c2h3, c2h2) self.assertNotEqual(c3h3, c3h2)
def testRequirementsOverride( self ) : class SelfRequiringNode( Gaffer.ExecutableNode ) : def __init__( self ) : Gaffer.ExecutableNode.__init__( self ) self.addChild( Gaffer.IntPlug( "multiplier", defaultValue = 1 ) ) self.preExecutionCount = 0 self.mainExecutionCount = 0 def requirements( self, context ) : if context.get( "selfExecutingNode:preExecute", None ) is None : customContext = Gaffer.Context( context ) customContext["selfExecutingNode:preExecute"] = True requirements = [ Gaffer.ExecutableNode.Task( self, customContext ) ] else : # We need to evaluate our external requirements as well, # and they need to be requirements of our preExecute task # only, since that is the topmost branch of our internal # requirement graph. We also need to use a Context which # does not contain our internal preExecute entry, incase # that has meaning for any of our external requirements. customContext = Gaffer.Context( context ) del customContext["selfExecutingNode:preExecute"] requirements = Gaffer.ExecutableNode.requirements( self, customContext ) return requirements def hash( self, context ) : h = Gaffer.ExecutableNode.hash( self, context ) h.append( context.get( "selfExecutingNode:preExecute", False ) ) h.append( self["multiplier"].hash() ) return h def execute( self ) : if Gaffer.Context.current().get( "selfExecutingNode:preExecute", False ) : self.preExecutionCount += self["multiplier"].getValue() else : self.mainExecutionCount += self["multiplier"].getValue() IECore.registerRunTimeTyped( SelfRequiringNode ) s = Gaffer.ScriptNode() s["e1"] = SelfRequiringNode() s["e2"] = SelfRequiringNode() s["e2"]["requirements"][0].setInput( s["e1"]['requirement'] ) c1 = s.context() c2 = Gaffer.Context( s.context() ) c2["selfExecutingNode:preExecute"] = True # e2 requires itself with a different context self.assertEqual( s["e2"].requirements( c1 ), [ Gaffer.ExecutableNode.Task( s["e2"], c2 ) ] ) # e2 in the other context requires e1 with the original context self.assertEqual( s["e2"].requirements( c2 ), [ Gaffer.ExecutableNode.Task( s["e1"], c1 ) ] ) # e1 requires itself with a different context self.assertEqual( s["e1"].requirements( c1 ), [ Gaffer.ExecutableNode.Task( s["e1"], c2 ) ] ) # e1 in the other context has no requirements self.assertEqual( s["e1"].requirements( c2 ), [] ) self.assertEqual( s["e1"].preExecutionCount, 0 ) self.assertEqual( s["e1"].mainExecutionCount, 0 ) self.assertEqual( s["e2"].preExecutionCount, 0 ) self.assertEqual( s["e2"].mainExecutionCount, 0 ) dispatcher = Gaffer.Dispatcher.create( "testDispatcher" ) dispatcher.dispatch( [ s["e1"] ] ) self.assertEqual( s["e1"].preExecutionCount, 1 ) self.assertEqual( s["e1"].mainExecutionCount, 1 ) self.assertEqual( s["e2"].preExecutionCount, 0 ) self.assertEqual( s["e2"].mainExecutionCount, 0 ) dispatcher.dispatch( [ s["e2"] ] ) self.assertEqual( s["e1"].preExecutionCount, 2 ) self.assertEqual( s["e1"].mainExecutionCount, 2 ) self.assertEqual( s["e2"].preExecutionCount, 1 ) self.assertEqual( s["e2"].mainExecutionCount, 1 )
def __init__(self, **kw): GafferUI.TextWidget.__init__(self, **kw) self.__context = Gaffer.Context()
def testDefaultFormat( self ) : text = GafferImage.Text() with Gaffer.Context() as c : GafferImage.FormatPlug().setDefaultFormat( c, GafferImage.Format( 100, 200, 2 ) ) self.assertEqual( text["out"]["format"].getValue(), GafferImage.Format( 100, 200, 2 ) )
def testFrameRangeMask( self ) : testSequence = IECore.FileSequence( self.temporaryDirectory() + "/incompleteSequence.####.exr" ) shutil.copyfile( self.fileName, testSequence.fileNameForFrame( 1 ) ) shutil.copyfile( self.fileName, testSequence.fileNameForFrame( 3 ) ) shutil.copyfile( self.offsetDataWindowFileName, testSequence.fileNameForFrame( 5 ) ) shutil.copyfile( self.offsetDataWindowFileName, testSequence.fileNameForFrame( 7 ) ) reader = GafferImage.ImageReader() reader["fileName"].setValue( testSequence.fileName ) reader["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Hold ) oiio = GafferImage.OpenImageIOReader() oiio["fileName"].setValue( testSequence.fileName ) oiio["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Hold ) context = Gaffer.Context() # make sure the tile we're comparing isn't black # so we can tell if BlackOutside is working. blackTile = IECore.FloatVectorData( [ 0 ] * GafferImage.ImagePlug.tileSize() * GafferImage.ImagePlug.tileSize() ) with context : for i in range( 1, 11 ) : context.setFrame( i ) self.assertNotEqual( reader["out"].channelData( "R", imath.V2i( 0 ) ), blackTile ) def assertBlack() : # format and data window still match self.assertEqual( reader["out"]["format"].getValue(), oiio["out"]["format"].getValue() ) self.assertEqual( reader["out"]["dataWindow"].getValue(), oiio["out"]["dataWindow"].getValue() ) self.assertNotEqual( GafferImage.ImageAlgo.image( reader["out"] ), GafferImage.ImageAlgo.image( oiio["out"] ) ) # the metadata and channel names are at the defaults self.assertEqual( reader["out"]["metadata"].getValue(), reader["out"]["metadata"].defaultValue() ) self.assertEqual( reader["out"]["channelNames"].getValue(), reader["out"]["channelNames"].defaultValue() ) # channel data is black self.assertEqual( reader["out"].channelData( "R", imath.V2i( 0 ) ), blackTile ) def assertMatch() : self.assertEqual( reader["out"]["format"].getValue(), oiio["out"]["format"].getValue() ) self.assertEqual( reader["out"]["dataWindow"].getValue(), oiio["out"]["dataWindow"].getValue() ) self.assertEqual( reader["out"]["metadata"].getValue(), oiio["out"]["metadata"].getValue() ) self.assertEqual( reader["out"]["channelNames"].getValue(), oiio["out"]["channelNames"].getValue() ) self.assertEqual( reader["out"].channelData( "R", imath.V2i( 0 ) ), oiio["out"].channelData( "R", imath.V2i( 0 ) ) ) self.assertImagesEqual( reader["out"], oiio["out"] ) def assertHold( holdFrame ) : context = Gaffer.Context() context.setFrame( holdFrame ) with context : holdImage = GafferImage.ImageAlgo.image( reader["out"] ) holdFormat = reader["out"]["format"].getValue() holdDataWindow = reader["out"]["dataWindow"].getValue() holdMetadata = reader["out"]["metadata"].getValue() holdChannelNames = reader["out"]["channelNames"].getValue() holdTile = reader["out"].channelData( "R", imath.V2i( 0 ) ) self.assertEqual( reader["out"]["format"].getValue(), holdFormat ) self.assertEqual( reader["out"]["dataWindow"].getValue(), holdDataWindow ) self.assertEqual( reader["out"]["metadata"].getValue(), holdMetadata ) self.assertEqual( reader["out"]["channelNames"].getValue(), holdChannelNames ) self.assertEqual( reader["out"].channelData( "R", imath.V2i( 0 ) ), holdTile ) self.assertEqual( GafferImage.ImageAlgo.image( reader["out"] ), holdImage ) reader["start"]["frame"].setValue( 4 ) reader["end"]["frame"].setValue( 7 ) # frame 0 errors, match from 1-10 reader["start"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.None ) reader["end"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.None ) with context : for i in range( 0, 11 ) : context.setFrame( i ) assertMatch() # black from 0-3, match from 4-10 reader["start"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.BlackOutside ) with context : for i in range( 0, 4 ) : context.setFrame( i ) assertBlack() for i in range( 4, 11 ) : context.setFrame( i ) assertMatch() # black from 0-3, match from 4-7, black from 8-10 reader["end"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.BlackOutside ) with context : for i in range( 0, 4 ) : context.setFrame( i ) assertBlack() for i in range( 4, 8 ) : context.setFrame( i ) assertMatch() for i in range( 8, 11 ) : context.setFrame( i ) assertBlack() # hold frame 4 from 0-3, match from 4-7, black from 8-10 reader["start"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.ClampToFrame ) with context : for i in range( 0, 4 ) : context.setFrame( i ) assertHold( 4 ) for i in range( 4, 8 ) : context.setFrame( i ) assertMatch() for i in range( 8, 11 ) : context.setFrame( i ) assertBlack() # hold frame 4 from 0-3, match from 4-7, hold frame 7 from 8-10 reader["end"]["mode"].setValue( GafferImage.ImageReader.FrameMaskMode.ClampToFrame ) with context : for i in range( 0, 4 ) : context.setFrame( i ) assertHold( 4 ) for i in range( 4, 8 ) : context.setFrame( i ) assertMatch() for i in range( 8, 11 ) : context.setFrame( i ) assertHold( 7 )
def testMissingFrameMode( self ) : testSequence = IECore.FileSequence( self.temporaryDirectory() + "/incompleteSequence.####.exr" ) shutil.copyfile( self.fileName, testSequence.fileNameForFrame( 1 ) ) shutil.copyfile( self.offsetDataWindowFileName, testSequence.fileNameForFrame( 3 ) ) reader = GafferImage.ImageReader() reader["fileName"].setValue( testSequence.fileName ) oiio = GafferImage.OpenImageIOReader() oiio["fileName"].setValue( testSequence.fileName ) def assertMatch() : self.assertEqual( reader["out"]["format"].getValue(), oiio["out"]["format"].getValue() ) self.assertEqual( reader["out"]["dataWindow"].getValue(), oiio["out"]["dataWindow"].getValue() ) self.assertEqual( reader["out"]["metadata"].getValue(), oiio["out"]["metadata"].getValue() ) self.assertEqual( reader["out"]["channelNames"].getValue(), oiio["out"]["channelNames"].getValue() ) # It is only valid to query the data inside the data window if not GafferImage.BufferAlgo.empty( reader["out"]["dataWindow"].getValue() ): self.assertEqual( reader["out"].channelData( "R", imath.V2i( 0 ) ), oiio["out"].channelData( "R", imath.V2i( 0 ) ) ) self.assertImagesEqual( reader["out"], oiio["out"] ) context = Gaffer.Context() # set to a missing frame context.setFrame( 2 ) # everything throws reader["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Error ) with context : self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["format"].getValue ) self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["dataWindow"].getValue ) self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["metadata"].getValue ) self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["channelNames"].getValue ) self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].channelData, "R", imath.V2i( 0 ) ) self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", GafferImage.ImageAlgo.image, reader["out"] ) # Hold mode matches OpenImageIOReader reader["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Hold ) oiio["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Hold ) with context : assertMatch() # Black mode matches OpenImageIOReader reader["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Black ) oiio["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black ) with context : assertMatch() # set to a different missing frame context.setFrame( 4 ) # Hold mode matches OpenImageIOReader reader["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Hold ) oiio["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Hold ) with context : assertMatch() # Black mode matches OpenImageIOReader reader["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Black ) oiio["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black ) with context : assertMatch() # set to a missing frame before the start of the sequence context.setFrame( 0 ) # Hold mode matches OpenImageIOReader reader["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Hold ) oiio["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Hold ) with context : assertMatch() # Black mode matches OpenImageIOReader reader["missingFrameMode"].setValue( GafferImage.ImageReader.MissingFrameMode.Black ) oiio["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black ) with context : assertMatch() # explicit fileNames do not support MissingFrameMode reader["fileName"].setValue( testSequence.fileNameForFrame( 0 ) ) reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Hold ) with context : self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", GafferImage.ImageAlgo.image, reader["out"] ) self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["format"].getValue ) self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["dataWindow"].getValue ) self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["metadata"].getValue ) self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["channelNames"].getValue ) self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].channelData, "R", imath.V2i( 0 ) ) reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black ) oiio["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black ) with context : self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", GafferImage.ImageAlgo.image, reader["out"] ) self.assertRaisesRegexp( RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["format"].getValue ) self.assertEqual( reader["out"]["dataWindow"].getValue(), oiio["out"]["dataWindow"].getValue() ) self.assertEqual( reader["out"]["metadata"].getValue(), oiio["out"]["metadata"].getValue() ) self.assertEqual( reader["out"]["channelNames"].getValue(), oiio["out"]["channelNames"].getValue() ) self.assertTrue( GafferImage.BufferAlgo.empty( reader["out"]['dataWindow'].getValue() ) ) self.assertTrue( GafferImage.BufferAlgo.empty( oiio["out"]['dataWindow'].getValue() ) )
class PlugLayout(GafferUI.Widget): # We use this when we can't find a ScriptNode to provide the context. __fallbackContext = Gaffer.Context() def __init__(self, parent, orientation=GafferUI.ListContainer.Orientation.Vertical, layoutName="layout", rootSection="", **kw): assert (isinstance(parent, (Gaffer.Node, Gaffer.Plug))) self.__layout = _TabLayout(orientation) if isinstance( parent, Gaffer.Node) else _CollapsibleLayout(orientation) GafferUI.Widget.__init__(self, self.__layout, **kw) self.__parent = parent self.__readOnly = False self.__layoutName = layoutName # not to be confused with __rootSection, which holds an actual _Section object self.__rootSectionName = rootSection # we need to connect to the childAdded/childRemoved signals on # the parent so we can update the ui when plugs are added and removed. self.__childAddedConnection = parent.childAddedSignal().connect( Gaffer.WeakMethod(self.__childAddedOrRemoved)) self.__childRemovedConnection = parent.childRemovedSignal().connect( Gaffer.WeakMethod(self.__childAddedOrRemoved)) # since our layout is driven by metadata, we must respond dynamically # to changes in that metadata. self.__metadataChangedConnection = Gaffer.Metadata.plugValueChangedSignal( ).connect(Gaffer.WeakMethod(self.__plugMetadataChanged)) # and since our activations are driven by plug values, we must respond # when the plugs are dirtied. self.__plugDirtiedConnection = self.__node().plugDirtiedSignal( ).connect(Gaffer.WeakMethod(self.__plugDirtied)) # frequently events that trigger a ui update come in batches, so we # perform the update lazily using a LazyMethod. the dirty variables # keep track of the work we'll need to do in the update. self.__layoutDirty = True self.__activationsDirty = True self.__summariesDirty = True # mapping from layout item to widget, where the key is either a plug or # the name of a custom widget (as returned by layoutOrder()). self.__widgets = {} self.__rootSection = _Section(self.__parent) # set up an appropriate default context in which to view the plugs. scriptNode = self.__node() if isinstance( self.__node(), Gaffer.ScriptNode) else self.__node().scriptNode() self.setContext(scriptNode.context( ) if scriptNode is not None else self.__fallbackContext) # schedule our first update, which will take place when we become # visible for the first time. self.__updateLazily() def getReadOnly(self): return self.__readOnly def setReadOnly(self, readOnly): if readOnly == self.getReadOnly(): return self.__readOnly = readOnly for widget in self.__widgets.values(): self.__applyReadOnly(widget, self.__readOnly) def getContext(self): return self.__context def setContext(self, context): self.__context = context self.__contextChangedConnection = self.__context.changedSignal( ).connect(Gaffer.WeakMethod(self.__contextChanged)) for widget in self.__widgets.values(): self.__applyContext(widget, context) ## Returns a PlugValueWidget representing the specified child plug. # Because the layout is built lazily on demand, this might return None due # to the user not having opened up the ui - in this case lazy=False may # be passed to force the creation of the ui. def plugValueWidget(self, childPlug, lazy=True): if not lazy: self.__updateLazily.flush(self) w = self.__widgets.get(childPlug, None) if w is None: return w elif isinstance(w, GafferUI.PlugValueWidget): return w else: return w.plugValueWidget() ## Returns the custom widget registered with the specified name. # Because the layout is built lazily on demand, this might return None due # to the user not having opened up the ui - in this case lazy=False may # be passed to force the creation of the ui. def customWidget(self, name, lazy=True): if not lazy: self.__updateLazily.flush(self) return self.__widgets.get(name) ## Returns the list of section names that will be used when laying # out the plugs of the specified parent. The sections are returned # in the order in which they will be created. @classmethod def layoutSections(cls, parent, includeCustomWidgets=False, layoutName="layout"): d = collections.OrderedDict() for item in cls.layoutOrder(parent, includeCustomWidgets, layoutName=layoutName): sectionPath = cls.__staticSectionPath(item, parent, layoutName) sectionName = ".".join(sectionPath) d[sectionName] = 1 return d.keys() ## Returns the child plugs of the parent in the order in which they # will be laid out, based on "<layoutName>:index" Metadata entries. If # includeCustomWidgets is True, then the positions of custom widgets # are represented by the appearance of the names of the widgets as # strings within the list. If a section name is specified, then the # result will be filtered to include only items in that section. @classmethod def layoutOrder(cls, parent, includeCustomWidgets=False, section=None, layoutName="layout", rootSection=""): items = parent.children(Gaffer.Plug) items = [plug for plug in items if not plug.getName().startswith("__")] if includeCustomWidgets: for name in Gaffer.Metadata.registeredValues(parent): m = re.match(layoutName + ":customWidget:(.+):widgetType", name) if m and cls.__metadataValue(parent, name): items.append(m.group(1)) itemsAndIndices = [list(x) for x in enumerate(items)] for itemAndIndex in itemsAndIndices: index = cls.__staticItemMetadataValue(itemAndIndex[1], "index", parent, layoutName) if index is not None: index = index if index >= 0 else sys.maxint + index itemAndIndex[0] = index itemsAndIndices.sort(key=lambda x: x[0]) if section is not None: sectionPath = section.split(".") if section else [] itemsAndIndices = [ x for x in itemsAndIndices if cls.__staticSectionPath( x[1], parent, layoutName) == sectionPath ] if rootSection: rootSectionPath = rootSection.split("." if rootSection else []) itemsAndIndices = [ x for x in itemsAndIndices if cls.__staticSectionPath(x[1], parent, layoutName) [:len(rootSectionPath)] == rootSectionPath ] return [x[1] for x in itemsAndIndices] @GafferUI.LazyMethod() def __updateLazily(self): self.__update() def __update(self): if self.__layoutDirty: self.__updateLayout() self.__layoutDirty = False if self.__activationsDirty: self.__updateActivations() self.__activationsDirty = False if self.__summariesDirty: self.__updateSummariesWalk(self.__rootSection) self.__summariesDirty = False # delegate to our layout class to create a concrete # layout from the section definitions. self.__layout.update(self.__rootSection) def __updateLayout(self): # get the items to lay out - these are a combination # of plugs and strings representing custom widgets. items = self.layoutOrder(self.__parent, includeCustomWidgets=True, layoutName=self.__layoutName, rootSection=self.__rootSectionName) # ditch widgets we don't need any more itemsSet = set(items) self.__widgets = { k: v for k, v in self.__widgets.items() if k in itemsSet } # ditch widgets whose metadata type has changed - we must recreate these. self.__widgets = { k: v for k, v in self.__widgets.items() if isinstance(k, str) or v is not None and Gaffer.Metadata.value( k, "plugValueWidget:type") == v.__plugValueWidgetType } # make (or reuse existing) widgets for each item, and sort them into # sections. rootSectionDepth = self.__rootSectionName.count( ".") + 1 if self.__rootSectionName else 0 self.__rootSection.clear() for item in items: if item not in self.__widgets: if isinstance(item, Gaffer.Plug): widget = self.__createPlugWidget(item) else: widget = self.__createCustomWidget(item) self.__widgets[item] = widget else: widget = self.__widgets[item] if widget is None: continue section = self.__rootSection for sectionName in self.__sectionPath(item)[rootSectionDepth:]: section = section.subsection(sectionName) if len(section.widgets) and self.__itemMetadataValue( item, "accessory"): row = GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing=4) row.append(section.widgets[-1]) row.append(widget) section.widgets[-1] = row else: section.widgets.append(widget) if self.__itemMetadataValue(item, "divider"): section.widgets.append( GafferUI.Divider(GafferUI.Divider.Orientation.Horizontal if self.__layout.orientation() == GafferUI. ListContainer.Orientation.Vertical else GafferUI.Divider.Orientation.Vertical)) def __updateActivations(self): with self.getContext(): # Must scope the context when getting activators, because they are typically # computed from the plug values, and may therefore trigger a compute. activators = self.__metadataValue( self.__parent, self.__layoutName + ":activators") or {} activators = {k: v.value for k, v in activators.items() } # convert CompoundData of BoolData to dict of booleans def active(activatorName): result = True if activatorName: result = activators.get(activatorName) if result is None: with self.getContext(): result = self.__metadataValue( self.__parent, self.__layoutName + ":activator:" + activatorName) result = result if result is not None else False activators[activatorName] = result return result for item, widget in self.__widgets.items(): if widget is not None: widget.setEnabled( active(self.__itemMetadataValue(item, "activator"))) widget.setVisible( active( self.__itemMetadataValue(item, "visibilityActivator"))) def __updateSummariesWalk(self, section): with self.getContext(): # Must scope the context because summaries are typically # generated from plug values, and may therefore trigger # a compute. section.summary = self.__metadataValue( self.__parent, self.__layoutName + ":section:" + section.fullName + ":summary") or "" for subsection in section.subsections.values(): self.__updateSummariesWalk(subsection) def __import(self, path): path = path.split(".") result = __import__(path[0]) for n in path[1:]: result = getattr(result, n) return result def __createPlugWidget(self, plug): result = GafferUI.PlugValueWidget.create(plug) if result is None: return result if isinstance(result, GafferUI.PlugValueWidget) and not result.hasLabel( ) and self.__itemMetadataValue(plug, "label") != "": result = GafferUI.PlugWidget(result) if self.__layout.orientation( ) == GafferUI.ListContainer.Orientation.Horizontal: # undo the annoying fixed size the PlugWidget has applied # to the label. ## \todo Shift all the label size fixing out of PlugWidget and just fix the # widget here if we're in a vertical orientation. QWIDGETSIZE_MAX = 16777215 # qt #define not exposed by PyQt or PySide result.labelPlugValueWidget().label()._qtWidget( ).setFixedWidth(QWIDGETSIZE_MAX) self.__applyReadOnly(result, self.getReadOnly()) self.__applyContext(result, self.getContext()) # Store the metadata value that controlled the type created, so we can compare to it # in the future to determine if we can reuse the widget. result.__plugValueWidgetType = Gaffer.Metadata.value( plug, "plugValueWidget:type") return result def __createCustomWidget(self, name): widgetType = self.__itemMetadataValue(name, "widgetType") widgetClass = self.__import(widgetType) return widgetClass(self.__parent) def __node(self): return self.__parent if isinstance( self.__parent, Gaffer.Node) else self.__parent.node() @classmethod def __metadataValue(cls, plugOrNode, name): if isinstance(plugOrNode, Gaffer.Node): return Gaffer.Metadata.value(plugOrNode, name) else: return Gaffer.Metadata.value(plugOrNode, name) @classmethod def __staticItemMetadataValue(cls, item, name, parent, layoutName): if isinstance(item, Gaffer.Plug): v = Gaffer.Metadata.value(item, layoutName + ":" + name) if v is None and name in ("divider", "label"): # Backwards compatibility with old unprefixed metadata names. v = Gaffer.Metadata.value(item, name) return v else: return cls.__metadataValue( parent, layoutName + ":customWidget:" + item + ":" + name) def __itemMetadataValue(self, item, name): return self.__staticItemMetadataValue(item, name, parent=self.__parent, layoutName=self.__layoutName) @classmethod def __staticSectionPath(cls, item, parent, layoutName): m = None if isinstance(parent, Gaffer.Node): # Backwards compatibility with old metadata entry ## \todo Remove m = cls.__staticItemMetadataValue(item, "nodeUI:section", parent, layoutName) if m == "header": m = "" if m is None: m = cls.__staticItemMetadataValue(item, "section", parent, layoutName) return m.split(".") if m else [] def __sectionPath(self, item): return self.__staticSectionPath(item, parent=self.__parent, layoutName=self.__layoutName) def __childAddedOrRemoved(self, *unusedArgs): # typically many children are added and removed at once, so # we do a lazy update so we can batch up several changes into one. # upheaval is over. self.__layoutDirty = True self.__updateLazily() def __applyReadOnly(self, widget, readOnly): if widget is None: return if hasattr(widget, "setReadOnly"): widget.setReadOnly(readOnly) elif isinstance(widget, GafferUI.PlugWidget): widget.labelPlugValueWidget().setReadOnly(readOnly) widget.plugValueWidget().setReadOnly(readOnly) elif hasattr(widget, "plugValueWidget"): widget.plugValueWidget().setReadOnly(readOnly) def __applyContext(self, widget, context): if hasattr(widget, "setContext"): widget.setContext(context) elif isinstance(widget, GafferUI.PlugWidget): widget.labelPlugValueWidget().setContext(context) widget.plugValueWidget().setContext(context) elif hasattr(widget, "plugValueWidget"): widget.plugValueWidget().setContext(context) def __plugMetadataChanged(self, nodeTypeId, plugPath, key, plug): parentAffected = isinstance( self.__parent, Gaffer.Plug) and Gaffer.MetadataAlgo.affectedByChange( self.__parent, nodeTypeId, plugPath, plug) childAffected = Gaffer.MetadataAlgo.childAffectedByChange( self.__parent, nodeTypeId, plugPath, plug) if not parentAffected and not childAffected: return if key in ("divider", self.__layoutName + ":divider", self.__layoutName + ":index", self.__layoutName + ":section", self.__layoutName + ":accessory", "plugValueWidget:type"): # we often see sequences of several metadata changes - so # we schedule a lazy update to batch them into one ui update. self.__layoutDirty = True self.__updateLazily() elif re.match(self.__layoutName + ":section:.*:summary", key): self.__summariesDirty = True self.__updateLazily() def __plugDirtied(self, plug): if not self.visible() or plug.direction() != plug.Direction.In: return self.__activationsDirty = True self.__summariesDirty = True self.__updateLazily() def __contextChanged(self, context, name): self.__activationsDirty = True self.__summariesDirty = True self.__updateLazily()
def _run( self, args ) : self.__timers = collections.OrderedDict() self.__memory = collections.OrderedDict() self.__memory["Application"] = _Memory.maxRSS() script = Gaffer.ScriptNode() script["fileName"].setValue( os.path.abspath( args["script"].value ) ) with _Timer() as loadingTimer : script.load( continueOnError = True ) self.__timers["Loading"] = loadingTimer self.__memory["Script"] = _Memory.maxRSS() - self.__memory["Application"] if args["performanceMonitor"].value : self.__performanceMonitor = Gaffer.PerformanceMonitor() else : self.__performanceMonitor = None if args["contextMonitor"].value : contextMonitorRoot = None if args["contextMonitorRoot"].value : contextMonitorRoot = script.descendant( args["contextMonitorRoot"].value ) if contextMonitorRoot is None : IECore.msg( IECore.Msg.Level.Error, "stats", "Context monitor root \"%s\" does not exist" % args["contextMonitorRoot"].value ) return 1 self.__contextMonitor = Gaffer.ContextMonitor( contextMonitorRoot ) else : self.__contextMonitor = None self.__output = file( args["outputFile"].value, "w" ) if args["outputFile"].value else sys.stdout with Gaffer.Context( script.context() ) as context : context.setFrame( args["frame"].value ) self.__writeVersion( script ) self.__output.write( "\n" ) self.__writeArgs( args ) self.__output.write( "\n" ) self.__writeSettings( script ) self.__output.write( "\n" ) self.__writeVariables( script ) self.__output.write( "\n" ) if args["nodeSummary"].value : self.__writeNodes( script ) if args["scene"].value : self.__writeScene( script, args ) if args["image"].value : self.__writeImage( script, args ) if args["task"].value : self.__writeTask( script, args ) self.__output.write( "\n" ) self.__writeMemory() self.__output.write( "\n" ) self.__writePerformance( script, args ) self.__output.write( "\n" ) self.__writeContext( script, args ) self.__output.write( "\n" ) self.__output.close() return 0
def traverser(): try: GafferSceneTest.traverseScene(g["out"], Gaffer.Context()) except Exception, e: exceptions.append(e)
def testHistory(self): plane = GafferScene.Plane() attributesFilter = GafferScene.PathFilter() attributesFilter["paths"].setValue(IECore.StringVectorData(["/plane"])) attributes = GafferScene.StandardAttributes() attributes["in"].setInput(plane["out"]) attributes["filter"].setInput(attributesFilter["out"]) attributes["attributes"].addMember("test", 10) group = GafferScene.Group() group["in"][0].setInput(attributes["out"]) transformFilter = GafferScene.PathFilter() transformFilter["paths"].setValue( IECore.StringVectorData(["/group/plane"])) transform = GafferScene.Transform() transform["in"].setInput(group["out"]) transform["filter"].setInput(transformFilter["out"]) # Transform history with Gaffer.Context() as c: c.setFrame(10) history = GafferScene.SceneAlgo.history( transform["out"]["transform"], "/group/plane") self.assertEqual(history.scene, transform["out"]) self.assertEqual(history.context.getFrame(), 10) self.assertEqual(history.context["scene:path"], IECore.InternedStringVectorData(["group", "plane"])) self.assertEqual(len(history.predecessors), 1) history = history.predecessors[0] self.assertEqual(history.scene, group["out"]) self.assertEqual(history.context.getFrame(), 10) self.assertEqual(history.context["scene:path"], IECore.InternedStringVectorData(["group", "plane"])) self.assertEqual(len(history.predecessors), 1) history = history.predecessors[0] self.assertEqual(history.scene, plane["out"]) self.assertEqual(history.context.getFrame(), 10) self.assertEqual(history.context["scene:path"], IECore.InternedStringVectorData(["plane"])) self.assertEqual(len(history.predecessors), 0) # Attributes history with Gaffer.Context() as c: c.setFrame(20) history = GafferScene.SceneAlgo.history( transform["out"]["attributes"], "/group/plane") self.assertEqual(history.scene, group["out"]) self.assertEqual(history.context.getFrame(), 20) self.assertEqual(history.context["scene:path"], IECore.InternedStringVectorData(["group", "plane"])) self.assertEqual(len(history.predecessors), 1) history = history.predecessors[0] self.assertEqual(history.scene, attributes["out"]) self.assertEqual(history.context.getFrame(), 20) self.assertEqual(history.context["scene:path"], IECore.InternedStringVectorData(["plane"])) self.assertEqual(len(history.predecessors), 1) history = history.predecessors[0] self.assertEqual(history.scene, plane["out"]) self.assertEqual(history.context.getFrame(), 20) self.assertEqual(history.context["scene:path"], IECore.InternedStringVectorData(["plane"])) self.assertEqual(len(history.predecessors), 0)
def test(self): f1 = GafferScene.PathFilter() f1["paths"].setValue(IECore.StringVectorData([ "/a/b/c", ])) f2 = GafferScene.PathFilter() f2["paths"].setValue(IECore.StringVectorData([ "/a/b/c/e/f/g", ])) u = GafferScene.UnionFilter() self.assertEqual(u["out"].getValue(), IECore.PathMatcher.Result.NoMatch) h1 = u["out"].hash() u["in"][0].setInput(f1["out"]) h2 = u["out"].hash() self.assertNotEqual(h1, h2) for path in [ "/a", "/b", "/a/b", "/a/b/c", "/a/b/c/d", ]: with Gaffer.Context() as c: c["scene:path"] = IECore.InternedStringVectorData( path[1:].split("/")) self.assertEqual(u["out"].getValue(), f1["out"].getValue()) u["in"][1].setInput(f2["out"]) h3 = u["out"].hash() self.assertNotEqual(h2, h3) for path, result in [ ("/a", IECore.PathMatcher.Result.DescendantMatch), ("/b", IECore.PathMatcher.Result.NoMatch), ("/a/b", IECore.PathMatcher.Result.DescendantMatch), ("/a/b/c", IECore.PathMatcher.Result.DescendantMatch | IECore.PathMatcher.Result.ExactMatch), ("/a/b/c/d", IECore.PathMatcher.Result.AncestorMatch), ("/a/b/c/e", IECore.PathMatcher.Result.AncestorMatch | IECore.PathMatcher.Result.DescendantMatch), ("/a/b/c/e/f/g", IECore.PathMatcher.Result.AncestorMatch | IECore.PathMatcher.Result.ExactMatch), ("/a/b/c/e/f/g/h", IECore.PathMatcher.Result.AncestorMatch), ]: with Gaffer.Context() as c: c["scene:path"] = IECore.InternedStringVectorData( path[1:].split("/")) self.assertEqual(u["out"].getValue(), int(result)) f2["paths"].setValue(IECore.StringVectorData([ "/a/b", ])) h4 = u["out"].hash() self.assertNotEqual(h3, h4)
class PlugValueWidget(GafferUI.Widget): class MultiplePlugsError(ValueError): pass def __init__(self, topLevelWidget, plugs, **kw): GafferUI.Widget.__init__(self, topLevelWidget, **kw) self._qtWidget().setProperty("gafferPlugValueWidget", True) if isinstance(plugs, Gaffer.Plug): plugs = {plugs} elif plugs is None: plugs = set() elif not isinstance(plugs, set): plugs = set(plugs) # We don't want to call `_updateFromPlugs()` yet because the derived # classes haven't constructed. They can call it themselves # upon completing construction. self.__setPlugsInternal(plugs, callUpdateFromPlugs=False) self.__readOnly = False self.dragEnterSignal().connect(Gaffer.WeakMethod(self.__dragEnter), scoped=False) self.dragLeaveSignal().connect(Gaffer.WeakMethod(self.__dragLeave), scoped=False) self.dropSignal().connect(Gaffer.WeakMethod(self.__drop), scoped=False) Gaffer.Metadata.nodeValueChangedSignal().connect(Gaffer.WeakMethod( self.__nodeMetadataChanged), scoped=False) ## Changes the plugs displayed by this widget. May be overridden by derived classes, # but all implementations must call the base class version first. Note that it is # acceptable for `plugs` to be empty, so derived classes should be implemented with # this in mind. def setPlugs(self, plugs): if not isinstance(plugs, set): plugs = set(plugs) if self.setPlug.__code__ is not PlugValueWidget.setPlug.__code__: # Legacy subclass has overridden `setPlug()`. Implement via # that so that it can do whatever it needs to do. if len(plugs) <= 1: self.setPlug(next(iter(plugs), None)) else: raise Exception("{} does not support multiple plugs".format( self.__class__.__name__)) else: self.__setPlugsInternal(plugs, callUpdateFromPlugs=True) def getPlugs(self): return self.__plugs ## Convenience function that calls `setPlugs()`. Note that # `plug` may be `None`. def setPlug(self, plug): if self.setPlug.__code__ is not PlugValueWidget.setPlug.__code__: # Legacy subclass has overridden `setPlug()`. Do work internally # to avoid recursion. self.__setPlugsInternal({plug} if plug is not None else set(), callUpdateFromPlugs=True) else: # Implement via `setPlugs()` so that new classes may # override it. self.setPlugs({plug} if plug is not None else set()) ## Convenience function. Raises MultiplePlugsError if more than one plug is # being displayed. def getPlug(self): if len(self.__plugs) > 1: raise self.MultiplePlugsError() return next(iter(self.__plugs), None) ## By default, PlugValueWidgets operate in the main context held by the script node # for the script the plug belongs to. This function allows an alternative context # to be provided, making it possible to view a plug at a custom frame (or with any # other context modification). def setContext(self, context): assert (isinstance(context, Gaffer.Context)) if context is self.__context: return self.__context = context self.__updateContextConnection() self._updateFromPlugs() def getContext(self): return self.__context ## \deprecated def setReadOnly(self, readOnly): assert (isinstance(readOnly, bool)) if readOnly == self.__readOnly: return self.__readOnly = readOnly self._updateFromPlugs() ## \deprecated def getReadOnly(self): return self.__readOnly ## Should be reimplemented to return True if this widget includes # some sort of labelling for the plug. This is used to prevent # extra labels being created in the NodeUI when they're not necessary. def hasLabel(self): return False ## Implemented to return a tooltip containing the plug name and description. def getToolTip(self): result = GafferUI.Widget.getToolTip(self) if result: return result if not self.getPlugs(): return "" # Name if len(self.getPlugs()) == 1: result = "# " + self.getPlug().relativeName(self.getPlug().node()) else: result = "# {} plugs".format(len(self.getPlugs())) # Input if len(self.getPlugs()) == 1: input = self.getPlug().getInput() if input is not None: result += "\n\nInput : {}".format( input.relativeName(input.commonAncestor(self.getPlug()))) # Description description = sole( Gaffer.Metadata.value(p, "description") for p in self.getPlugs()) if description: result += "\n\n" + description return result ## Because Plugs may have child Plugs, so too PlugValueWidgets may # have child PlugValueWidgets to represent the children of their plug. # This method should be reimplemented to return such children, or `None` # if no appropriate child exists. def childPlugValueWidget(self, childPlug): return None __popupMenuSignal = None ## This signal is emitted whenever a popup menu for a plug is about # to be shown. This provides an opportunity to customise the menu from # external code. The signature for slots is ( menuDefinition, plugValueWidget ), # and slots should just modify the menu definition in place. @classmethod def popupMenuSignal(cls): if cls.__popupMenuSignal is None: cls.__popupMenuSignal = _PopupMenuSignal() return cls.__popupMenuSignal ## Returns a PlugValueWidget suitable for representing the specified plugs. # The type of widget returned may be customised on a per-plug basis by a # "plugValueWidget:type" metadata value, specifying the fully qualified # python type name for a widget class. To suppress the creation of a widget, # a value of "" may be registered - in this case None will be returned from # create(). If useTypeOnly is True, then the metadata will be ignored and # only the plug type will be taken into account in creating a PlugValueWidget. @classmethod def create(cls, plugs, useTypeOnly=False): if isinstance(plugs, Gaffer.Plug): creators = {cls.__creator(plugs, useTypeOnly)} else: creators = {cls.__creator(p, useTypeOnly) for p in plugs} if len(creators) > 1: raise Exception("Multiple widget creators") creator = next(iter(creators)) if creator is not None: return creator(plugs) return None ## Registers a PlugValueWidget type for a specific Plug type. @classmethod def registerType(cls, plugClassOrTypeId, creator): if isinstance(plugClassOrTypeId, IECore.TypeId): plugTypeId = plugClassOrTypeId else: plugTypeId = plugClassOrTypeId.staticTypeId() cls.__plugTypesToCreators[plugTypeId] = creator ## Ensures that the specified plug has a visible PlugValueWidget, # creating one if necessary. @classmethod def acquire(cls, plug): editor = GafferUI.NodeEditor.acquire(plug.node()) plugValueWidget = editor.nodeUI().plugValueWidget(plug) if not plugValueWidget: return None plugValueWidget.reveal() return plugValueWidget ## Must be implemented by subclasses so that the widget reflects the current # status of the plugs. To temporarily suspend calls to this function, use # `Gaffer.BlockedConnection( self._plugConnections() )`. def _updateFromPlugs(self): # Default implementation falls back to legacy update for a single plug. updateFromPlug = getattr(self, "_updateFromPlug", None) if updateFromPlug is not None: updateFromPlug() def _plugConnections(self): return (self.__plugDirtiedConnections + self.__plugInputChangedConnections + [self.__plugMetadataChangedConnection]) ## Returns True if the plug's values are editable as far as this UI is concerned # - that `plug.settable()` is True for all plugs and `self.getReadOnly()` is # False. By default, an animated plug is considered to be non-editable because # it has an input connection. Subclasses which support animation editing may pass # `canEditAnimation = True` to have animated plugs considered as editable. def _editable(self, canEditAnimation=False): if self.__readOnly or not self.getPlugs(): return False for plug in self.getPlugs(): if hasattr(plug, "settable") and not plug.settable(): if not canEditAnimation or not Gaffer.Animation.isAnimated( plug): return False if Gaffer.MetadataAlgo.readOnly(plug): return False return True ## Called to convert the specified value into something # suitable for passing to a plug.setValue() call. Returns # None if no such conversion is necessary. May be reimplemented # by derived classes to provide more complex conversions than # the standard. The base class uses this method to accept drag/drop # and copy/paste data. def _convertValue(self, value): plugValueType = sole( type(p.defaultValue()) if hasattr(p, "defaultValue") else None for p in self.getPlugs()) if plugValueType is None: return None if isinstance(value, plugValueType): return value elif isinstance(value, IECore.Data): dataValue = None if hasattr(value, "value"): dataValue = value.value else: with IECore.IgnoredExceptions(Exception): if len(value) == 1: dataValue = value[0] if dataValue is None: return None elif isinstance(dataValue, plugValueType): return dataValue else: with IECore.IgnoredExceptions(Exception): return plugValueType(dataValue) return None ## Adds a useful popup menu to the specified widget, providing useful functions that # operate on the plug. The menu is populated with the result of _popupMenuDefinition(), # and may also be customised by external code using the popupMenuSignal(). def _addPopupMenu(self, widget=None, buttons=GafferUI.ButtonEvent.Buttons.Right): if widget is None: widget = self # it's unclear under what circumstances we get given a right-click vs a context menu event, # but we try to cover all our bases by connecting to both. widget.buttonPressSignal().connect(functools.partial( Gaffer.WeakMethod(self.__buttonPress), buttonMask=buttons), scoped=False) if buttons & GafferUI.ButtonEvent.Buttons.Right: widget.contextMenuSignal().connect(functools.partial( Gaffer.WeakMethod(self.__contextMenu)), scoped=False) ## Returns a definition for the popup menu - this is called each time the menu is displayed # to allow for dynamic menus. Subclasses may override this method to customise the menu, but # should call the base class implementation first. def _popupMenuDefinition(self): menuDefinition = IECore.MenuDefinition() if all(hasattr(p, "getValue") for p in self.getPlugs()): applicationRoot = sole( p.ancestor(Gaffer.ApplicationRoot) for p in self.getPlugs()) menuDefinition.append( "/Copy Value", { "command": Gaffer.WeakMethod(self.__copyValue), "active": len(self.getPlugs()) == 1 and applicationRoot is not None }) pasteValue = None if applicationRoot is not None: pasteValue = self._convertValue( applicationRoot.getClipboardContents()) menuDefinition.append( "/Paste Value", { "command": functools.partial(Gaffer.WeakMethod(self.__setValues), pasteValue), "active": self._editable() and pasteValue is not None }) menuDefinition.append("/CopyPasteDivider", {"divider": True}) if any(p.getInput() is not None for p in self.getPlugs()): menuDefinition.append( "/Edit input...", {"command": Gaffer.WeakMethod(self.__editInputs)}) menuDefinition.append("/EditInputDivider", {"divider": True}) menuDefinition.append( "/Remove input", { "command": Gaffer.WeakMethod(self.__removeInputs), "active": not self.getReadOnly() and all( p.acceptsInput(None) and not Gaffer.MetadataAlgo.readOnly(p) for p in self.getPlugs()), }) if all( hasattr(p, "defaultValue") and p.direction() == Gaffer.Plug.Direction.In for p in self.getPlugs()): menuDefinition.append( "/Default", { "command": functools.partial( Gaffer.WeakMethod(self.__setValues), [p.defaultValue() for p in self.getPlugs()]), "active": self._editable() }) if all( Gaffer.NodeAlgo.hasUserDefault(p) and p.direction() == Gaffer.Plug.Direction.In for p in self.getPlugs()): menuDefinition.append( "/User Default", { "command": Gaffer.WeakMethod(self.__applyUserDefaults), "active": self._editable() }) with self.getContext(): if any(Gaffer.NodeAlgo.presets(p) for p in self.getPlugs()): menuDefinition.append( "/Preset", { "subMenu": Gaffer.WeakMethod(self.__presetsSubMenu), "active": self._editable() }) if len(menuDefinition.items()): menuDefinition.append("/LockDivider", {"divider": True}) readOnly = any( Gaffer.MetadataAlgo.getReadOnly(p) for p in self.getPlugs()) menuDefinition.append( "/Unlock" if readOnly else "/Lock", { "command": functools.partial(Gaffer.WeakMethod(self.__applyReadOnly), not readOnly), "active": not self.getReadOnly() and not any( Gaffer.MetadataAlgo.readOnly(p.parent()) for p in self.getPlugs()), }) self.popupMenuSignal()(menuDefinition, self) return menuDefinition def __plugDirtied(self, plug): if plug in self.__plugs: self._updateFromPlugs() def __plugInputChanged(self, plug): if plug in self.__plugs: self.__updateContextConnection() self._updateFromPlugs() def __plugMetadataChanged(self, nodeTypeId, plugPath, key, plug): for p in self.__plugs: if (Gaffer.MetadataAlgo.affectedByChange(p, nodeTypeId, plugPath, plug) or Gaffer.MetadataAlgo.readOnlyAffectedByChange( p, nodeTypeId, plugPath, key, plug)): self._updateFromPlugs() return def __nodeMetadataChanged(self, nodeTypeId, key, node): for p in self.__plugs: if Gaffer.MetadataAlgo.readOnlyAffectedByChange( p, nodeTypeId, key, node): self._updateFromPlugs() return def __contextChanged(self, context, key): self._updateFromPlugs() def __setPlugsInternal(self, plugs, callUpdateFromPlugs): assert (isinstance(plugs, set)) if len(plugs) and sole(p.__class__ for p in plugs) is None: raise ValueError("Plugs have different types") nodes = set() scriptNodes = set() for plug in plugs: nodes.add(plug.node()) scriptNodes.add(plug.ancestor(Gaffer.ScriptNode)) assert (len(scriptNodes) <= 1) self.__plugs = plugs self.__plugDirtiedConnections = [ node.plugDirtiedSignal().connect( Gaffer.WeakMethod(self.__plugDirtied)) for node in nodes ] self.__plugInputChangedConnections = [ node.plugInputChangedSignal().connect( Gaffer.WeakMethod(self.__plugInputChanged)) for node in nodes ] if self.__plugs: self.__plugMetadataChangedConnection = Gaffer.Metadata.plugValueChangedSignal( ).connect(Gaffer.WeakMethod(self.__plugMetadataChanged)) else: self.__plugMetadataChangedConnection = None scriptNode = next(iter(scriptNodes), None) self.__context = scriptNode.context( ) if scriptNode is not None else self.__fallbackContext self.__updateContextConnection() if callUpdateFromPlugs: self._updateFromPlugs() def __updateContextConnection(self): # We only want to be notified of context changes for plugs whose values are # computed. context = self.__context if all(p.source().direction() == Gaffer.Plug.Direction.In for p in self.getPlugs()): context = None if context is not None: self.__contextChangedConnection = context.changedSignal().connect( Gaffer.WeakMethod(self.__contextChanged)) else: self.__contextChangedConnection = None # we use this when the plugs being viewed doesn't have a ScriptNode ancestor # to provide a context. __fallbackContext = Gaffer.Context() def __buttonPress(self, widget, event, buttonMask): if event.buttons & buttonMask: return self.__contextMenu() return False def __contextMenu(self, *unused): if not self.getPlugs(): return False menuDefinition = self._popupMenuDefinition() if not len(menuDefinition.items()): return False if len(self.getPlugs()) == 1: title = self.getPlug().relativeName(self.getPlug().node()) title = ".".join([ IECore.CamelCase.join(IECore.CamelCase.split(x)) for x in title.split(".") ]) else: title = "{} plugs".format(len(self.getPlugs())) self.__popupMenu = GafferUI.Menu(menuDefinition, title=title) self.__popupMenu.popup(parent=self) return True def __copyValue(self): with self.getContext(): value = self.getPlug().getValue() if not isinstance(value, IECore.Object): # Trick to get Data from a simple type - put # it in a CompoundData (which will convert to # Data automatically) and then get it back out. value = IECore.CompoundData({"v": value})["v"] self.getPlug().ancestor( Gaffer.ApplicationRoot).setClipboardContents(value) def __setValues(self, values): if not isinstance(values, list): values = itertools.repeat(values, len(self.getPlugs())) with Gaffer.UndoScope( next(iter(self.getPlugs())).ancestor(Gaffer.ScriptNode)): for plug, value in zip(self.getPlugs(), values): plug.setValue(value) def __editInputs(self): # We may have multiple inputs from the same node. # Choose one input plug per node to reveal. nodesToPlugs = {} for p in self.getPlugs(): i = p.getInput() if i is not None: nodesToPlugs[i.node()] = i # Acquire a NodeEditor for each node, and reveal the # chosen plug. for node, plug in nodesToPlugs.items(): nodeEditor = GafferUI.NodeEditor.acquire(node) if nodeEditor is None: continue plugValueWidget = nodeEditor.nodeUI().plugValueWidget(plug) if plugValueWidget is not None: plugValueWidget.reveal() def __removeInputs(self): with Gaffer.UndoScope( next(iter(self.getPlugs())).ancestor(Gaffer.ScriptNode)): for p in self.getPlugs(): p.setInput(None) def __applyUserDefaults(self): with Gaffer.UndoScope( next(iter(self.getPlugs())).ancestor(Gaffer.ScriptNode)): for p in self.getPlugs(): Gaffer.NodeAlgo.applyUserDefault(p) def __presetsSubMenu(self): with self.getContext(): currentPreset = sole((Gaffer.NodeAlgo.currentPreset(p) or "" for p in self.getPlugs())) # Find the union of the presets across all plugs, # and count how many times they occur. presets = [] presetCounts = collections.Counter() for plug in self.getPlugs(): for preset in Gaffer.NodeAlgo.presets(plug): if not presetCounts[preset]: presets.append(preset) presetCounts[preset] += 1 # Build menu. We'll list every preset we found, but disable # any which aren't available for all plugs. result = IECore.MenuDefinition() for presetName in presets: menuPath = presetName if presetName.startswith( "/") else "/" + presetName result.append( menuPath, { "command": functools.partial(Gaffer.WeakMethod(self.__applyPreset), presetName), "active": self._editable() and presetCounts[preset] == len(self.getPlugs()), "checkBox": presetName == currentPreset, }) return result def __applyPreset(self, presetName, *unused): with self.getContext(): with Gaffer.UndoScope( next(iter(self.getPlugs())).ancestor(Gaffer.ScriptNode)): for p in self.getPlugs(): Gaffer.NodeAlgo.applyPreset(p, presetName) def __applyReadOnly(self, readOnly): with Gaffer.UndoScope( next(iter(self.getPlugs())).ancestor(Gaffer.ScriptNode)): for p in self.getPlugs(): Gaffer.MetadataAlgo.setReadOnly(p, readOnly) # drag and drop stuff def __dragEnter(self, widget, event): if self.getReadOnly() or any( Gaffer.MetadataAlgo.readOnly(p) for p in self.getPlugs()): return False if isinstance(event.sourceWidget, GafferUI.PlugValueWidget): sourcePlugValueWidget = event.sourceWidget else: sourcePlugValueWidget = event.sourceWidget.ancestor( GafferUI.PlugValueWidget) if sourcePlugValueWidget is not None and sourcePlugValueWidget.getPlugs( ) & self.getPlugs(): return False if isinstance(event.data, Gaffer.Plug): if all(p.direction() == Gaffer.Plug.Direction.In and p.acceptsInput(event.data) for p in self.getPlugs()): self.setHighlighted(True) return True elif all(hasattr(p, "setValue") for p in self.getPlugs()) and self._convertValue( event.data) is not None: if all(p.settable() for p in self.getPlugs()): self.setHighlighted(True) return True return False def __dragLeave(self, widget, event): self.setHighlighted(False) def __drop(self, widget, event): self.setHighlighted(False) with Gaffer.UndoScope(next(iter(self.getPlugs())).node().scriptNode()): if isinstance(event.data, Gaffer.Plug): for p in self.getPlugs(): p.setInput(event.data) else: v = self._convertValue(event.data) for p in self.getPlugs(): p.setValue(v) return True # Type registry internals @classmethod def __creator(cls, plug, useTypeOnly): # First try to create one using a creator registered for the specific plug. if not useTypeOnly: widgetType = Gaffer.Metadata.value(plug, "plugValueWidget:type") if widgetType is None: widgetType = Gaffer.Metadata.value(plug, "layout:widgetType") if widgetType is not None: warnings.warn( "The \"layout:widgetType\" metadata entry is deprecated, use \"plugValueWidget:type\" instead.", DeprecationWarning) if widgetType == "None": return None if widgetType is not None: if widgetType == "": return None path = widgetType.split(".") widgetClass = __import__(path[0]) for n in path[1:]: widgetClass = getattr(widgetClass, n) return widgetClass # If that failed, then just create something based on the type of the plug. typeId = plug.typeId() for plugTypeId in [plug.typeId()] + IECore.RunTimeTyped.baseTypeIds( plug.typeId()): if plugTypeId in cls.__plugTypesToCreators: creator = cls.__plugTypesToCreators[plugTypeId] if creator is not None: return creator else: return None return None __plugTypesToCreators = {}
def _updateFromPlug(self): with Gaffer.Context(): self.__button.setImage("soloChannel{0}.png".format( self.getPlug().getValue()))
class PlugValueWidget(GafferUI.Widget): def __init__(self, topLevelWidget, plug, **kw): GafferUI.Widget.__init__(self, topLevelWidget, **kw) # we don't want to call _updateFromPlug yet because the derived # classes haven't constructed yet. they can call it themselves # upon completing construction. self.__setPlugInternal(plug, callUpdateFromPlug=False) self.__readOnly = False self.dragEnterSignal().connect(Gaffer.WeakMethod(self.__dragEnter), scoped=False) self.dragLeaveSignal().connect(Gaffer.WeakMethod(self.__dragLeave), scoped=False) self.dropSignal().connect(Gaffer.WeakMethod(self.__drop), scoped=False) Gaffer.Metadata.nodeValueChangedSignal().connect(Gaffer.WeakMethod( self.__nodeMetadataChanged), scoped=False) ## Note that it is acceptable to pass None to setPlug() (and to the constructor) # and that derived classes should be implemented to cope with this eventuality. def setPlug(self, plug): self.__setPlugInternal(plug, callUpdateFromPlug=True) def getPlug(self): return self.__plug ## By default, PlugValueWidgets operate in the main context held by the script node # for the script the plug belongs to. This function allows an alternative context # to be provided, making it possible to view a plug at a custom frame (or with any # other context modification). def setContext(self, context): assert (isinstance(context, Gaffer.Context)) if context is self.__context: return self.__context = context self.__updateContextConnection() self._updateFromPlug() def getContext(self): return self.__context ## This method allows editing of the plug value # to be disabled for this ui. Note that even when getReadOnly() # is False, the ui may not allow editing due to the plug # itself being read only for other reasons. def setReadOnly(self, readOnly): assert (isinstance(readOnly, bool)) if readOnly == self.__readOnly: return self.__readOnly = readOnly self._updateFromPlug() def getReadOnly(self): return self.__readOnly ## Should be reimplemented to return True if this widget includes # some sort of labelling for the plug. This is used to prevent # extra labels being created in the NodeUI when they're not necessary. def hasLabel(self): return False ## Implemented to return a tooltip containing the plug name and description. def getToolTip(self): result = GafferUI.Widget.getToolTip(self) if result: return result plug = self.getPlug() if plug is None: return "" input = plug.getInput() inputText = "" if input is not None: inputText = " <- " + input.relativeName( input.commonAncestor(plug, Gaffer.GraphComponent)) result = "# " + plug.relativeName(plug.node()) + inputText description = Gaffer.Metadata.value(plug, "description") if description: result += "\n\n" + description return result ## Because Plugs may have child Plugs, so too PlugValueWidgets may # have child PlugValueWidgets to represent the children of their plug. # This method should be reimplemented to return such children. Because # UIs may be built lazily on demand, the lazy flag is provided to # determine whether or not the query should force a build in the case # that one has not been performed yet. def childPlugValueWidget(self, childPlug, lazy=True): return None ## Ensures that the specified plug has a visible PlugValueWidget, # creating one if necessary. @classmethod def acquire(cls, plug): editor = GafferUI.NodeEditor.acquire(plug.node()) plugValueWidget = editor.nodeUI().plugValueWidget(plug, lazy=False) if not plugValueWidget: return None plugValueWidget.reveal() return plugValueWidget ## Must be implemented by subclasses so that the widget reflects the current # status of the plug. To temporarily suspend calls to this function, use # Gaffer.BlockedConnection( self._plugConnections() ). def _updateFromPlug(self): raise NotImplementedError def _plugConnections(self): return [ self.__plugDirtiedConnection, self.__plugInputChangedConnection, self.__plugFlagsChangedConnection, self.__plugMetadataChangedConnection, ] ## Returns True if the plug value is editable as far as this ui is concerned # - that plug.settable() is True and self.getReadOnly() is False. By default, # an animated plug is considered to be non-editable because it has an input # connection. Subclasses which support animation editing may pass # `canEditAnimation = True` to have animated plugs considered as editable. def _editable(self, canEditAnimation=False): plug = self.getPlug() if plug is None: return False if self.__readOnly: return False if hasattr(plug, "settable") and not plug.settable(): if not canEditAnimation or not Gaffer.Animation.isAnimated(plug): return False if Gaffer.MetadataAlgo.readOnly(plug): return False return True ## Called to convert the specified value into something # suitable for passing to a plug.setValue() call. Returns # None if no such conversion is necessary. May be reimplemented # by derived classes to provide more complex conversions than # the standard. The base class uses this method to accept drag/drop # and copy/paste data. def _convertValue(self, value): if not hasattr(self.getPlug(), "defaultValue"): return None plugValueType = type(self.getPlug().defaultValue()) if isinstance(value, plugValueType): return value elif isinstance(value, IECore.Data): dataValue = None if hasattr(value, "value"): dataValue = value.value else: with IECore.IgnoredExceptions(Exception): if len(value) == 1: dataValue = value[0] if dataValue is None: return None elif isinstance(dataValue, plugValueType): return dataValue else: with IECore.IgnoredExceptions(Exception): return plugValueType(dataValue) return None ## Adds a useful popup menu to the specified widget, providing useful functions that # operate on the plug. The menu is populated with the result of _popupMenuDefinition(), # and may also be customised by external code using the popupMenuSignal(). def _addPopupMenu(self, widget=None, buttons=GafferUI.ButtonEvent.Buttons.Right): if widget is None: widget = self # it's unclear under what circumstances we get given a right-click vs a context menu event, # but we try to cover all our bases by connecting to both. widget.buttonPressSignal().connect(functools.partial( Gaffer.WeakMethod(self.__buttonPress), buttonMask=buttons), scoped=False) if buttons & GafferUI.ButtonEvent.Buttons.Right: widget.contextMenuSignal().connect(functools.partial( Gaffer.WeakMethod(self.__contextMenu)), scoped=False) ## Returns a definition for the popup menu - this is called each time the menu is displayed # to allow for dynamic menus. Subclasses may override this method to customise the menu, but # should call the base class implementation first. def _popupMenuDefinition(self): menuDefinition = IECore.MenuDefinition() if hasattr(self.getPlug(), "getValue"): applicationRoot = self.getPlug().ancestor(Gaffer.ApplicationRoot) menuDefinition.append( "/Copy Value", { "command": Gaffer.WeakMethod(self.__copyValue), "active": applicationRoot is not None }) pasteValue = None if applicationRoot is not None: pasteValue = self._convertValue( applicationRoot.getClipboardContents()) menuDefinition.append( "/Paste Value", { "command": functools.partial(Gaffer.WeakMethod(self.__setValue), pasteValue), "active": self._editable() and pasteValue is not None }) menuDefinition.append("/CopyPasteDivider", {"divider": True}) if self.getPlug().getInput() is not None: menuDefinition.append( "/Edit input...", {"command": Gaffer.WeakMethod(self.__editInput)}) menuDefinition.append("/EditInputDivider", {"divider": True}) menuDefinition.append( "/Remove input", { "command": Gaffer.WeakMethod(self.__removeInput), "active": self.getPlug().acceptsInput(None) and not self.getReadOnly() and not Gaffer.MetadataAlgo.readOnly(self.getPlug()), }) if hasattr(self.getPlug(), "defaultValue") and self.getPlug( ).direction() == Gaffer.Plug.Direction.In: menuDefinition.append( "/Default", { "command": functools.partial(Gaffer.WeakMethod(self.__setValue), self.getPlug().defaultValue()), "active": self._editable() }) if Gaffer.NodeAlgo.hasUserDefault(self.getPlug()) and self.getPlug( ).direction() == Gaffer.Plug.Direction.In: menuDefinition.append( "/User Default", { "command": Gaffer.WeakMethod(self.__applyUserDefault), "active": self._editable() }) if Gaffer.NodeAlgo.presets(self.getPlug()): menuDefinition.append( "/Preset", { "subMenu": Gaffer.WeakMethod(self.__presetsSubMenu), "active": self._editable() }) if len(menuDefinition.items()): menuDefinition.append("/LockDivider", {"divider": True}) readOnly = Gaffer.MetadataAlgo.getReadOnly(self.getPlug()) menuDefinition.append( "/Unlock" if readOnly else "/Lock", { "command": functools.partial(Gaffer.WeakMethod(self.__applyReadOnly), not readOnly), "active": not self.getReadOnly() and not Gaffer.MetadataAlgo.readOnly(self.getPlug().parent()), }) self.popupMenuSignal()(menuDefinition, self) return menuDefinition __popupMenuSignal = Gaffer.Signal2() ## This signal is emitted whenever a popup menu for a plug is about # to be shown. This provides an opportunity to customise the menu from # external code. The signature for slots is ( menuDefinition, plugValueWidget ), # and slots should just modify the menu definition in place. @classmethod def popupMenuSignal(cls): return cls.__popupMenuSignal ## Returns a PlugValueWidget suitable for representing the specified plug. # The type of plug returned may be customised on a per-widget basis by a # "plugValueWidget:type" metadata value, specifying the fully qualified # python type name for a widget class. To suppress the creation of a widget, # a value of "" may be registered - in this case None will be returned from # create(). If useTypeOnly is True, then the metadata will be ignored and # only the plug type will be taken into account in creating a PlugValueWidget. @classmethod def create(cls, plug, useTypeOnly=False): # first try to create one using a creator registered for the specific plug if not useTypeOnly: widgetType = Gaffer.Metadata.value(plug, "plugValueWidget:type") if widgetType is None: widgetType = Gaffer.Metadata.value(plug, "layout:widgetType") if widgetType is not None: warnings.warn( "The \"layout:widgetType\" metadata entry is deprecated, use \"plugValueWidget:type\" instead.", DeprecationWarning) if widgetType == "None": return None if widgetType is not None: if widgetType == "": return None path = widgetType.split(".") widgetClass = __import__(path[0]) for n in path[1:]: widgetClass = getattr(widgetClass, n) return widgetClass(plug) # if that failed, then just create something based on the type of the plug typeId = plug.typeId() for plugTypeId in [plug.typeId()] + IECore.RunTimeTyped.baseTypeIds( plug.typeId()): if plugTypeId in cls.__plugTypesToCreators: creator = cls.__plugTypesToCreators[plugTypeId] if creator is not None: return creator(plug) else: return None return None ## Registers a PlugValueWidget type for a specific Plug type. @classmethod def registerType(cls, plugClassOrTypeId, creator): if isinstance(plugClassOrTypeId, IECore.TypeId): plugTypeId = plugClassOrTypeId else: plugTypeId = plugClassOrTypeId.staticTypeId() cls.__plugTypesToCreators[plugTypeId] = creator __plugTypesToCreators = {} def __plugDirtied(self, plug): if plug.isSame(self.__plug): self._updateFromPlug() def __plugInputChanged(self, plug): if plug.isSame(self.__plug): self.__updateContextConnection() self._updateFromPlug() def __plugFlagsChanged(self, plug): if plug.isSame(self.__plug): self._updateFromPlug() def __plugMetadataChanged(self, nodeTypeId, plugPath, key, plug): if self.__plug is None: return if (Gaffer.MetadataAlgo.affectedByChange(self.__plug, nodeTypeId, plugPath, plug) or Gaffer.MetadataAlgo.readOnlyAffectedByChange( self.__plug, nodeTypeId, plugPath, key, plug)): self._updateFromPlug() def __nodeMetadataChanged(self, nodeTypeId, key, node): if self.__plug is None: return if Gaffer.MetadataAlgo.readOnlyAffectedByChange( self.__plug, nodeTypeId, key, node): self._updateFromPlug() def __contextChanged(self, context, key): self._updateFromPlug() def __setPlugInternal(self, plug, callUpdateFromPlug): self.__plug = plug context = self.__fallbackContext if self.__plug is not None: self.__plugDirtiedConnection = plug.node().plugDirtiedSignal( ).connect(Gaffer.WeakMethod(self.__plugDirtied)) self.__plugInputChangedConnection = plug.node( ).plugInputChangedSignal().connect( Gaffer.WeakMethod(self.__plugInputChanged)) self.__plugFlagsChangedConnection = plug.node( ).plugFlagsChangedSignal().connect( Gaffer.WeakMethod(self.__plugFlagsChanged)) self.__plugMetadataChangedConnection = Gaffer.Metadata.plugValueChangedSignal( ).connect(Gaffer.WeakMethod(self.__plugMetadataChanged)) scriptNode = self.__plug.ancestor(Gaffer.ScriptNode) if scriptNode is not None: context = scriptNode.context() else: self.__plugDirtiedConnection = None self.__plugInputChangedConnection = None self.__plugFlagsChangedConnection = None self.__plugMetadataChangedConnection = None self.__context = context self.__updateContextConnection() if callUpdateFromPlug: self._updateFromPlug() def __updateContextConnection(self): # we only want to be notified of context changes if we have a plug and that # plug has an incoming connection. otherwise context changes are irrelevant # and we'd just be slowing things down by asking for notifications. context = self.__context plug = self.getPlug() if plug is None or (plug.getInput() is None and plug.direction() == Gaffer.Plug.Direction.In): context = None if context is not None: self.__contextChangedConnection = context.changedSignal().connect( Gaffer.WeakMethod(self.__contextChanged)) else: self.__contextChangedConnection = None # we use this when the plug being viewed doesn't have a ScriptNode ancestor # to provide a context. __fallbackContext = Gaffer.Context() def __buttonPress(self, widget, event, buttonMask): if event.buttons & buttonMask: return self.__contextMenu() return False def __contextMenu(self, *unused): if self.getPlug() is None: return False menuDefinition = self._popupMenuDefinition() if not len(menuDefinition.items()): return False title = self.getPlug().relativeName(self.getPlug().node()) title = ".".join([ IECore.CamelCase.join(IECore.CamelCase.split(x)) for x in title.split(".") ]) self.__popupMenu = GafferUI.Menu(menuDefinition, title=title) self.__popupMenu.popup(parent=self) return True def __copyValue(self): with self.getContext(): value = self.getPlug().getValue() if not isinstance(value, IECore.Object): # Trick to get Data from a simple type - put # it in a CompoundData (which will convert to # Data automatically) and then get it back out. value = IECore.CompoundData({"v": value})["v"] self.getPlug().ancestor( Gaffer.ApplicationRoot).setClipboardContents(value) def __setValue(self, value): with Gaffer.UndoScope(self.getPlug().ancestor(Gaffer.ScriptNode)): self.getPlug().setValue(value) def __editInput(self): nodeEditor = GafferUI.NodeEditor.acquire( self.getPlug().getInput().node()) if nodeEditor is None: return plugValueWidget = nodeEditor.nodeUI().plugValueWidget( self.getPlug().getInput()) if plugValueWidget is None: return plugValueWidget.reveal() def __removeInput(self): with Gaffer.UndoScope(self.getPlug().ancestor(Gaffer.ScriptNode)): self.getPlug().setInput(None) def __applyUserDefault(self): with Gaffer.UndoScope(self.getPlug().ancestor(Gaffer.ScriptNode)): Gaffer.NodeAlgo.applyUserDefault(self.getPlug()) def __presetsSubMenu(self): with self.getContext(): currentPreset = Gaffer.NodeAlgo.currentPreset(self.getPlug()) result = IECore.MenuDefinition() for presetName in Gaffer.NodeAlgo.presets(self.getPlug()): menuPath = presetName if presetName.startswith( "/") else "/" + presetName result.append( menuPath, { "command": functools.partial(Gaffer.WeakMethod(self.__applyPreset), presetName), "active": self._editable(), "checkBox": presetName == currentPreset, }) return result def __applyPreset(self, presetName, *unused): with Gaffer.UndoScope(self.getPlug().ancestor(Gaffer.ScriptNode)): Gaffer.NodeAlgo.applyPreset(self.getPlug(), presetName) def __applyReadOnly(self, readOnly): with Gaffer.UndoScope(self.getPlug().ancestor(Gaffer.ScriptNode)): Gaffer.MetadataAlgo.setReadOnly(self.getPlug(), readOnly) # drag and drop stuff def __dragEnter(self, widget, event): if self.getReadOnly() or Gaffer.MetadataAlgo.readOnly(self.getPlug()): return False if isinstance(event.sourceWidget, GafferUI.PlugValueWidget): sourcePlugValueWidget = event.sourceWidget else: sourcePlugValueWidget = event.sourceWidget.ancestor( GafferUI.PlugValueWidget) if sourcePlugValueWidget is not None and sourcePlugValueWidget.getPlug( ).isSame(self.getPlug()): return False if isinstance(event.data, Gaffer.Plug): if self.getPlug().acceptsInput(event.data): self.setHighlighted(True) return True elif hasattr(self.getPlug(), "setValue") and self._convertValue( event.data) is not None: if self.getPlug().settable(): self.setHighlighted(True) return True return False def __dragLeave(self, widget, event): self.setHighlighted(False) def __drop(self, widget, event): self.setHighlighted(False) with Gaffer.UndoScope(self.getPlug().node().scriptNode()): if isinstance(event.data, Gaffer.Plug): self.getPlug().setInput(event.data) else: self.getPlug().setValue(self._convertValue(event.data)) return True
def testContextAffectsHash(self): # Hash should be constant if context not # accessed. n = GafferDispatch.PythonCommand() n["command"].setValue("a = 10") with Gaffer.Context() as c: h = n["task"].hash() c.setTime(2) self.assertEqual(n["task"].hash(), h) c.setTime(3) self.assertEqual(n["task"].hash(), h) c["testInt"] = 10 self.assertEqual(n["task"].hash(), h) c["testInt"] = 20 self.assertEqual(n["task"].hash(), h) # If we access the frame, then we should # be sensitive to the time, but not anything else n["command"].setValue("a = context.getFrame()") with Gaffer.Context() as c: c.setTime(1) h1 = n["task"].hash() c.setTime(2) h2 = n["task"].hash() c.setTime(3) h3 = n["task"].hash() self.assertNotEqual(h1, h) self.assertNotEqual(h2, h1) self.assertNotEqual(h3, h2) self.assertNotEqual(h3, h1) c["testInt"] = 10 self.assertEqual(n["task"].hash(), h3) c["testInt"] = 20 self.assertEqual(n["task"].hash(), h3) # The same should apply if we access the frame # via subscripting rather than the method. n["command"].setValue("a = context['frame']") with Gaffer.Context() as c: c.setTime(1) h1 = n["task"].hash() c.setTime(2) h2 = n["task"].hash() c.setTime(3) h3 = n["task"].hash() self.assertNotEqual(h1, h) self.assertNotEqual(h2, h1) self.assertNotEqual(h3, h2) self.assertNotEqual(h3, h1) c["testInt"] = 10 self.assertEqual(n["task"].hash(), h3) c["testInt"] = 20 self.assertEqual(n["task"].hash(), h3) # Likewise, accessing other variables should # affect the hash. n["command"].setValue("a = context['testInt']") with Gaffer.Context() as c: c["testInt"] = 1 h1 = n["task"].hash() c["testInt"] = 2 h2 = n["task"].hash() c["testInt"] = 3 h3 = n["task"].hash() self.assertNotEqual(h2, h1) self.assertNotEqual(h3, h2) self.assertNotEqual(h3, h1) c.setFrame(2) self.assertEqual(n["task"].hash(), h3) c.setFrame(3) self.assertEqual(n["task"].hash(), h3)
def __testMetadataDoesNotAffectPixels( self, ext, overrideMetadata = {}, metadataToIgnore = [] ) : r = GafferImage.ImageReader() r["fileName"].setValue( self.__rgbFilePath+"."+ext ) d = GafferImage.DeleteImageMetadata() d["in"].setInput( r["out"] ) m = GafferImage.ImageMetadata() m["in"].setInput( d["out"] ) # lets tell a few lies # IPTC:Creator will have the current username appended to the end of # the existing one, creating a list of creators. Blank it out for # this test d["names"].setValue( "IPTC:Creator" ) m["metadata"].addMember( "PixelAspectRatio", IECore.FloatData( 2 ) ) m["metadata"].addMember( "oiio:ColorSpace", IECore.StringData( "Rec709" ) ) m["metadata"].addMember( "oiio:BitsPerSample", IECore.IntData( 8 ) ) m["metadata"].addMember( "oiio:UnassociatedAlpha", IECore.IntData( 1 ) ) m["metadata"].addMember( "oiio:Gamma", IECore.FloatData( 0.25 ) ) testFile = self.__testFile( "metadataHasNoAffect", "RGBA", ext ) self.failIf( os.path.exists( testFile ) ) w = GafferImage.ImageWriter() w["in"].setInput( m["out"] ) w["fileName"].setValue( testFile ) w["channels"].setValue( IECore.StringVectorData( m["out"]["channelNames"].getValue() ) ) testFile2 = self.__testFile( "noNewMetadata", "RGBA", ext ) self.failIf( os.path.exists( testFile2 ) ) w2 = GafferImage.ImageWriter() w2["in"].setInput( d["out"] ) w2["fileName"].setValue( testFile2 ) w2["channels"].setValue( IECore.StringVectorData( r["out"]["channelNames"].getValue() ) ) inMetadata = w["in"]["metadata"].getValue() self.assertEqual( inMetadata["PixelAspectRatio"], IECore.FloatData( 2 ) ) self.assertEqual( inMetadata["oiio:ColorSpace"], IECore.StringData( "Rec709" ) ) self.assertEqual( inMetadata["oiio:BitsPerSample"], IECore.IntData( 8 ) ) self.assertEqual( inMetadata["oiio:UnassociatedAlpha"], IECore.IntData( 1 ) ) self.assertEqual( inMetadata["oiio:Gamma"], IECore.FloatData( 0.25 ) ) with Gaffer.Context() : w["task"].execute() w2["task"].execute() self.failUnless( os.path.exists( testFile ) ) self.failUnless( os.path.exists( testFile2 ) ) after = GafferImage.ImageReader() after["fileName"].setValue( testFile ) before = GafferImage.ImageReader() before["fileName"].setValue( testFile2 ) inImage = w["in"].image() afterImage = after["out"].image() beforeImage = before["out"].image() inImage.blindData().clear() afterImage.blindData().clear() beforeImage.blindData().clear() self.assertEqual( afterImage, inImage ) self.assertEqual( afterImage, beforeImage ) self.assertEqual( after["out"]["format"].getValue(), r["out"]["format"].getValue() ) self.assertEqual( after["out"]["format"].getValue(), before["out"]["format"].getValue() ) self.assertEqual( after["out"]["dataWindow"].getValue(), r["out"]["dataWindow"].getValue() ) self.assertEqual( after["out"]["dataWindow"].getValue(), before["out"]["dataWindow"].getValue() ) afterMetadata = after["out"]["metadata"].getValue() beforeMetadata = before["out"]["metadata"].getValue() expectedMetadata = r["out"]["metadata"].getValue() # they were written at different times so we can't expect those values to match beforeMetadata["DateTime"] = afterMetadata["DateTime"] expectedMetadata["DateTime"] = afterMetadata["DateTime"] # the writer adds several standard attributes that aren't in the original file expectedMetadata["Software"] = IECore.StringData( "Gaffer " + Gaffer.About.versionString() ) expectedMetadata["HostComputer"] = IECore.StringData( platform.node() ) expectedMetadata["Artist"] = IECore.StringData( os.getlogin() ) expectedMetadata["DocumentName"] = IECore.StringData( "untitled" ) # some formats support IPTC standards, and some of the standard metadata # is translated automatically by OpenImageIO. for key in afterMetadata.keys() : if key.startswith( "IPTC:" ) : expectedMetadata["IPTC:OriginatingProgram"] = expectedMetadata["Software"] expectedMetadata["IPTC:Creator"] = expectedMetadata["Artist"] break for key in overrideMetadata : expectedMetadata[key] = overrideMetadata[key] beforeMetadata[key] = overrideMetadata[key] for key in metadataToIgnore : if key in expectedMetadata : del expectedMetadata[key] if key in beforeMetadata : del beforeMetadata[key] if key in afterMetadata : del afterMetadata[key] for metaName in expectedMetadata.keys() : self.assertTrue( metaName in afterMetadata.keys(), "Writer Metadata missing expected key \"{}\" set to \"{}\" : {}".format(metaName, str(expectedMetadata[metaName]), ext) ) self.assertEqual( expectedMetadata[metaName], afterMetadata[metaName], "Metadata does not match for key \"{}\" : {}".format(metaName, ext) ) for metaName in beforeMetadata.keys() : self.assertTrue( metaName in afterMetadata.keys(), "Writer Metadata missing expected key \"{}\" set to \"{}\" : {}".format(metaName, str(beforeMetadata[metaName]), ext) ) self.assertEqual( beforeMetadata[metaName], afterMetadata[metaName], "Metadata does not match for key \"{}\" : {}".format(metaName, ext) )
def f(): self.failIf(c.isSame(Gaffer.Context.current())) with Gaffer.Context(): pass
def testPythonExpressionAndGIL(self): script = Gaffer.ScriptNode() script["plane"] = GafferScene.Plane() script["plane"]["divisions"].setValue(IECore.V2i(20)) script["sphere"] = GafferScene.Sphere() script["expression"] = Gaffer.Expression() script["expression"].setExpression( "parent['sphere']['radius'] = context.getFrame() + float( context['instancer:id'] )" ) script["instancer"] = GafferScene.Instancer() script["instancer"]["in"].setInput(script["plane"]["out"]) script["instancer"]["instance"].setInput(script["sphere"]["out"]) script["instancer"]["parent"].setValue("/plane") # The Instancer spawns its own threads, so if we don't release the GIL # when invoking it, and an upstream node enters Python, we'll end up # with a deadlock. Test that isn't the case. We increment the frame # between each test to ensure the expression result is not cached and # we do truly enter python. with Gaffer.Context() as c: c["scene:path"] = IECore.InternedStringVectorData(["plane"]) c.setFrame(1) script["instancer"]["out"]["globals"].getValue() c.setFrame(2) script["instancer"]["out"]["bound"].getValue() c.setFrame(3) script["instancer"]["out"]["transform"].getValue() c.setFrame(4) script["instancer"]["out"]["object"].getValue() c.setFrame(5) script["instancer"]["out"]["attributes"].getValue() c.setFrame(6) script["instancer"]["out"]["childNames"].getValue() c.setFrame(7) c.setFrame(101) script["instancer"]["out"]["globals"].hash() c.setFrame(102) script["instancer"]["out"]["bound"].hash() c.setFrame(103) script["instancer"]["out"]["transform"].hash() c.setFrame(104) script["instancer"]["out"]["object"].hash() c.setFrame(105) script["instancer"]["out"]["attributes"].hash() c.setFrame(106) script["instancer"]["out"]["childNames"].hash() c.setFrame(107) # The same applies for the higher level helper functions on ScenePlug c.setFrame(200) script["instancer"]["out"].bound("/plane") c.setFrame(201) script["instancer"]["out"].transform("/plane") c.setFrame(202) script["instancer"]["out"].fullTransform("/plane") c.setFrame(203) script["instancer"]["out"].attributes("/plane") c.setFrame(204) script["instancer"]["out"].fullAttributes("/plane") c.setFrame(205) script["instancer"]["out"].object("/plane") c.setFrame(206) script["instancer"]["out"].childNames("/plane") c.setFrame(207) c.setFrame(300) script["instancer"]["out"].boundHash("/plane") c.setFrame(301) script["instancer"]["out"].transformHash("/plane") c.setFrame(302) script["instancer"]["out"].fullTransformHash("/plane") c.setFrame(303) script["instancer"]["out"].attributesHash("/plane") c.setFrame(304) script["instancer"]["out"].fullAttributesHash("/plane") c.setFrame(305) script["instancer"]["out"].objectHash("/plane") c.setFrame(306) script["instancer"]["out"].childNamesHash("/plane") c.setFrame(307)
def testWithBlockReturnValue(self): with Gaffer.Context() as c: self.failUnless(isinstance(c, Gaffer.Context)) self.failUnless(c.isSame(Gaffer.Context.current()))
def testContextChangedAndGIL(self): script = Gaffer.ScriptNode() script["plane"] = GafferScene.Plane() script["plane"]["divisions"].setValue(IECore.V2i(20)) script["sphere"] = GafferScene.Sphere() script["expression"] = Gaffer.Expression() script["expression"].setExpression( "parent['sphere']['radius'] = context.get( 'minRadius', 0.1 ) + context.getFrame() + float( context['instancer:id'] )" ) script["instancer"] = GafferScene.Instancer() script["instancer"]["in"].setInput(script["plane"]["out"]) script["instancer"]["instance"].setInput(script["sphere"]["out"]) script["instancer"]["parent"].setValue("/plane") context = Gaffer.Context() traverseConnection = Gaffer.ScopedConnection( GafferSceneTest.connectTraverseSceneToContextChangedSignal( script["instancer"]["out"], context)) with context: context.setFrame(10) context.setFramesPerSecond(50) context.setTime(1) context.set("a", 1) context.set("a", 2.0) context.set("a", "a") context.set("a", IECore.V2i()) context.set("a", IECore.V3i()) context.set("a", IECore.V2f()) context.set("a", IECore.V3f()) context.set("a", IECore.Color3f()) context.set("a", IECore.BoolData(True)) context["b"] = 1 context["b"] = 2.0 context["b"] = "b" context["b"] = IECore.V2i() context["b"] = IECore.V3i() context["b"] = IECore.V2f() context["b"] = IECore.V3f() context["b"] = IECore.Color3f() context["b"] = IECore.BoolData(True) with Gaffer.BlockedConnection(traverseConnection): # Must add it with the connection disabled, otherwise # the addition causes a traversal, and then remove() gets # all its results from the cache. context["minRadius"] = 0.2 context.remove("minRadius") with Gaffer.BlockedConnection(traverseConnection): context["minRadius"] = 0.3 del context["minRadius"]
def testSubstituteTildeInMiddle(self): c = Gaffer.Context() self.assertEqual(c.substitute("a~b"), "a~b")
def testMissingFrameMode(self): testSequence = IECore.FileSequence(self.temporaryDirectory() + "/incompleteSequence.####.exr") shutil.copyfile(self.fileName, testSequence.fileNameForFrame(1)) shutil.copyfile(self.offsetDataWindowFileName, testSequence.fileNameForFrame(3)) reader = GafferImage.OpenImageIOReader() reader["fileName"].setValue(testSequence.fileName) context = Gaffer.Context() # get frame 1 data for comparison context.setFrame(1) with context: f1Image = reader["out"].image() f1Format = reader["out"]["format"].getValue() f1DataWindow = reader["out"]["dataWindow"].getValue() f1Metadata = reader["out"]["metadata"].getValue() f1ChannelNames = reader["out"]["channelNames"].getValue() f1Tile = reader["out"].channelData("R", imath.V2i(0)) # make sure the tile we're comparing isn't black # so we can tell if MissingFrameMode::Black is working. blackTile = IECore.FloatVectorData([0] * GafferImage.ImagePlug.tileSize() * GafferImage.ImagePlug.tileSize()) self.assertNotEqual(f1Tile, blackTile) # set to a missing frame context.setFrame(2) # everything throws reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Error) with context: self.assertRaisesRegexp(RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].image) self.assertRaisesRegexp(RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["format"].getValue) self.assertRaisesRegexp(RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["dataWindow"].getValue) self.assertRaisesRegexp(RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["metadata"].getValue) self.assertRaisesRegexp(RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["channelNames"].getValue) self.assertRaisesRegexp(RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].channelData, "R", imath.V2i(0)) # everything matches frame 1 reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Hold) with context: self.assertEqual(reader["out"].image(), f1Image) self.assertEqual(reader["out"]["format"].getValue(), f1Format) self.assertEqual(reader["out"]["dataWindow"].getValue(), f1DataWindow) self.assertEqual(reader["out"]["metadata"].getValue(), f1Metadata) self.assertEqual(reader["out"]["channelNames"].getValue(), f1ChannelNames) self.assertEqual(reader["out"].channelData("R", imath.V2i(0)), f1Tile) # the windows match frame 1, but everything else is default reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black) with context: self.assertNotEqual(reader["out"].image(), f1Image) self.assertEqual(reader["out"]["format"].getValue(), f1Format) self.assertEqual(reader["out"]["dataWindow"].getValue(), reader["out"]["dataWindow"].defaultValue()) self.assertEqual(reader["out"]["metadata"].getValue(), reader["out"]["metadata"].defaultValue()) self.assertEqual(reader["out"]["channelNames"].getValue(), reader["out"]["channelNames"].defaultValue()) self.assertEqual(reader["out"].channelData("R", imath.V2i(0)), blackTile) # get frame 3 data for comparison context.setFrame(3) with context: f3Image = reader["out"].image() f3Format = reader["out"]["format"].getValue() f3DataWindow = reader["out"]["dataWindow"].getValue() f3Metadata = reader["out"]["metadata"].getValue() f3ChannelNames = reader["out"]["channelNames"].getValue() f3Tile = reader["out"].channelData("R", imath.V2i(0)) # set to a different missing frame context.setFrame(4) # everything matches frame 3 reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Hold) with context: self.assertNotEqual(reader["out"].image(), f1Image) self.assertNotEqual(reader["out"]["format"].getValue(), f1Format) self.assertNotEqual(reader["out"]["dataWindow"].getValue(), f1DataWindow) self.assertNotEqual(reader["out"]["metadata"].getValue(), f1Metadata) # same channel names is fine self.assertEqual(reader["out"]["channelNames"].getValue(), f1ChannelNames) self.assertNotEqual(reader["out"].channelData("R", imath.V2i(0)), f1Tile) self.assertEqual(reader["out"].image(), f3Image) self.assertEqual(reader["out"]["format"].getValue(), f3Format) self.assertEqual(reader["out"]["dataWindow"].getValue(), f3DataWindow) self.assertEqual(reader["out"]["metadata"].getValue(), f3Metadata) self.assertEqual(reader["out"]["channelNames"].getValue(), f3ChannelNames) self.assertEqual(reader["out"].channelData("R", imath.V2i(0)), f3Tile) # the windows match frame 3, but everything else is default reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black) with context: self.assertNotEqual(reader["out"]["format"].getValue(), f1Format) self.assertEqual(reader["out"]["format"].getValue(), f3Format) self.assertEqual(reader["out"]["dataWindow"].getValue(), reader["out"]["dataWindow"].defaultValue()) self.assertEqual(reader["out"]["metadata"].getValue(), reader["out"]["metadata"].defaultValue()) self.assertEqual(reader["out"]["channelNames"].getValue(), reader["out"]["channelNames"].defaultValue()) self.assertEqual(reader["out"].channelData("R", imath.V2i(0)), blackTile) # set to a missing frame before the start of the sequence context.setFrame(0) # everything matches frame 1 reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Hold) with context: self.assertEqual(reader["out"].image(), f1Image) self.assertEqual(reader["out"]["format"].getValue(), f1Format) self.assertEqual(reader["out"]["dataWindow"].getValue(), f1DataWindow) self.assertEqual(reader["out"]["metadata"].getValue(), f1Metadata) self.assertEqual(reader["out"].channelData("R", imath.V2i(0)), f1Tile) # the windows match frame 1, but everything else is default reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black) with context: self.assertEqual(reader["out"]["format"].getValue(), f1Format) self.assertEqual(reader["out"]["dataWindow"].getValue(), reader["out"]["dataWindow"].defaultValue()) self.assertEqual(reader["out"]["metadata"].getValue(), reader["out"]["metadata"].defaultValue()) self.assertEqual(reader["out"]["channelNames"].getValue(), reader["out"]["channelNames"].defaultValue()) self.assertEqual(reader["out"].channelData("R", imath.V2i(0)), blackTile) # explicit fileNames do not support MissingFrameMode reader["fileName"].setValue(testSequence.fileNameForFrame(0)) reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Hold) with context: self.assertRaisesRegexp(RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].image) self.assertRaisesRegexp(RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["format"].getValue) self.assertRaisesRegexp(RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["dataWindow"].getValue) self.assertRaisesRegexp(RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["metadata"].getValue) self.assertRaisesRegexp(RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["channelNames"].getValue) self.assertRaisesRegexp(RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].channelData, "R", imath.V2i(0)) reader["missingFrameMode"].setValue( GafferImage.OpenImageIOReader.MissingFrameMode.Black) with context: self.assertRaisesRegexp(RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"].image) self.assertRaisesRegexp(RuntimeError, ".*incompleteSequence.*.exr.*", reader["out"]["format"].getValue) self.assertEqual(reader["out"]["dataWindow"].getValue(), reader["out"]["dataWindow"].defaultValue()) self.assertEqual(reader["out"]["metadata"].getValue(), reader["out"]["metadata"].defaultValue()) self.assertEqual(reader["out"]["channelNames"].getValue(), reader["out"]["channelNames"].defaultValue()) self.assertEqual(reader["out"].channelData("R", imath.V2i(0)), blackTile)
def testTaskSet(self): # A no-op TaskNode doesn't actually compute anything, so all tasks are the same c = Gaffer.Context() n = GafferDispatchTest.LoggingTaskNode() n["noOp"].setValue(True) t1 = GafferDispatch.TaskNode.Task(n, c) t2 = GafferDispatch.TaskNode.Task(n, c) self.assertEqual(t1, t2) c2 = Gaffer.Context() c2["a"] = 2 t3 = GafferDispatch.TaskNode.Task(n, c2) self.assertEqual(t1, t3) n2 = GafferDispatchTest.LoggingTaskNode() n2["noOp"].setValue(True) t4 = GafferDispatch.TaskNode.Task(n2, c2) self.assertEqual(t1, t4) t5 = GafferDispatch.TaskNode.Task(n2, c) self.assertEqual(t1, t5) s = set([t1, t2, t3, t4, t4, t4, t1, t2, t4, t3, t2]) # there should only be 1 task because they all have identical results self.assertEqual(len(s), 1) self.assertEqual(s, set([t1])) self.assertTrue(t1 in s) self.assertTrue(t2 in s) self.assertTrue(t3 in s) self.assertTrue(t4 in s) # even t5 is in there, because it's really the same task self.assertTrue(t5 in s) # MyNode.hash() depends on the context time, so tasks will vary my = GafferDispatchTest.LoggingTaskNode() my["frameSensitivePlug"] = Gaffer.StringPlug(defaultValue="####") c.setFrame(1) t1 = GafferDispatch.TaskNode.Task(my, c) t2 = GafferDispatch.TaskNode.Task(my, c) self.assertEqual(t1, t2) c2 = Gaffer.Context() c2.setFrame(2) t3 = GafferDispatch.TaskNode.Task(my, c2) self.assertNotEqual(t1, t3) my2 = GafferDispatchTest.LoggingTaskNode() my2["frameSensitivePlug"] = Gaffer.StringPlug(defaultValue="####") t4 = GafferDispatch.TaskNode.Task(my2, c2) self.assertNotEqual(t1, t4) self.assertEqual(t3, t4) t5 = GafferDispatch.TaskNode.Task(my2, c) self.assertEqual(t1, t5) self.assertNotEqual(t3, t5) s = set([t1, t2, t3, t4, t4, t4, t1, t2, t4, t3, t2]) # t1 and t3 are the only distinct tasks self.assertEqual(len(s), 2) self.assertEqual(s, set([t1, t3])) # but they still all have equivalent tasks in the set self.assertTrue(t1 in s) self.assertTrue(t2 in s) self.assertTrue(t3 in s) self.assertTrue(t4 in s) self.assertTrue(t5 in s)