diff --git a/include/Gaffer/ScriptNode.h b/include/Gaffer/ScriptNode.h index f639424af07..511b48169e2 100644 --- a/include/Gaffer/ScriptNode.h +++ b/include/Gaffer/ScriptNode.h @@ -215,16 +215,38 @@ class GAFFER_API ScriptNode : public Node //@} //! @name Computation context - /// The ScriptNode provides a default context for computations to be - /// performed in, and allows the user to define custom variables in - /// it via a plug. It also maps the value of fileNamePlug() into - /// the script:name variable. + /// + /// The ScriptNode provides a default context that is + /// driven by plug values, so that it is serialised + /// with the script. This allows the user to : + /// + /// - Set the frame and framesPerSecond variables + /// - Add arbitrary variables of their own + /// - Use a "script:name" variable generated from + /// the filename. + /// + /// It is expected that all computations will use a context + /// derived from this default context, but note that this does + /// _not_ imply that there is a single global "current time". + /// Derived contexts may have their own frame and even framesPerSecond + /// values, and can be used in parallel with the default context + /// or any other context. This allows features like TimeWarp nodes + /// and UI elements which view a different frame than the default. //////////////////////////////////////////////////////////////////// //@{ /// The default context - all computations should be performed - /// with this context, or one which inherits its variables. + /// with this context, or one derived from it. Context *context(); const Context *context() const; + /// Drives the frame variable in the context. + /// + /// > Warning : This exists primarily as a convenience for the + /// > user, so that the "current frame" is saved within the + /// > script file. To perform a computation at a particular time, + /// > create your own context rather than change the value of + /// > this plug. + FloatPlug *framePlug(); + const FloatPlug *framePlug() const; /// Drives the framesPerSecond variable in the context. FloatPlug *framesPerSecondPlug(); const FloatPlug *framesPerSecondPlug() const; @@ -236,6 +258,8 @@ class GAFFER_API ScriptNode : public Node //! @name Frame range /// The ScriptNode defines the valid frame range using two numeric plugs. + /// \todo Perhaps these should also drive context variables? It might + /// be useful to use the frame range in expressions etc. //////////////////////////////////////////////////////////////////// //@{ IntPlug *frameStartPlug(); @@ -302,6 +326,7 @@ class GAFFER_API ScriptNode : public Node ContextPtr m_context; void plugSet( Plug *plug ); + void contextChanged( const Context *context, const IECore::InternedString &name ); static size_t g_firstPlugIndex; diff --git a/python/GafferTest/ScriptNodeTest.py b/python/GafferTest/ScriptNodeTest.py index 16f2f5ac504..5ff73df2117 100644 --- a/python/GafferTest/ScriptNodeTest.py +++ b/python/GafferTest/ScriptNodeTest.py @@ -1198,6 +1198,23 @@ def testFramesPerSecond( self ) : self.assertEqual( s2["framesPerSecond"].getValue(), 48.0 ) self.assertEqual( s2.context().getFramesPerSecond(), 48.0 ) + def testFrame( self ) : + + s = Gaffer.ScriptNode() + self.assertEqual( s["frame"].getValue(), 1 ) + self.assertEqual( s.context().getFrame(), 1.0 ) + + s["frame"].setValue( 2.0 ) + self.assertEqual( s.context().getFrame(), 2.0 ) + + s.context().setFrame( 4.0 ) + self.assertEqual( s["frame"].getValue(), 4.0 ) + + s2 = Gaffer.ScriptNode() + s2.execute( s.serialise() ) + self.assertEqual( s2["frame"].getValue(), 4.0 ) + self.assertEqual( s2.context().getFrame(), 4.0 ) + def testLineNumberForExecutionSyntaxError( self ) : s = Gaffer.ScriptNode() diff --git a/python/GafferUI/ScriptNodeUI.py b/python/GafferUI/ScriptNodeUI.py index 3a423977e93..163723d6400 100644 --- a/python/GafferUI/ScriptNodeUI.py +++ b/python/GafferUI/ScriptNodeUI.py @@ -110,6 +110,25 @@ ), + "frame" : ( + + "description", + """ + The current frame. + + > Note : To perform a computation at a particular time, + > you should create your own context rather than change + > the value of this plug. + > + > ``` + > with Gaffer.Context( script.context() ) as c : + > c.setFrame( f ) + > ... + > ``` + """, + + ), + "framesPerSecond" : ( "description", diff --git a/src/Gaffer/ScriptNode.cpp b/src/Gaffer/ScriptNode.cpp index 21c26fad4f0..ac3cb44499f 100644 --- a/src/Gaffer/ScriptNode.cpp +++ b/src/Gaffer/ScriptNode.cpp @@ -257,6 +257,7 @@ ScriptNode::ScriptNode( const std::string &name ) frameRangePlug->addChild( frameEndPlug ); addChild( frameRangePlug ); + addChild( new FloatPlug( "frame", Plug::In, 1.0f ) ); addChild( new FloatPlug( "framesPerSecond", Plug::In, 24.0f, 0.0f ) ); addChild( new CompoundDataPlug( "variables" ) ); @@ -265,6 +266,7 @@ ScriptNode::ScriptNode( const std::string &name ) m_selection->memberAcceptanceSignal().connect( boost::bind( &ScriptNode::selectionSetAcceptor, this, ::_1, ::_2 ) ); plugSetSignal().connect( boost::bind( &ScriptNode::plugSet, this, ::_1 ) ); + m_context->changedSignal().connect( boost::bind( &ScriptNode::contextChanged, this, ::_1, ::_2 ) ); } ScriptNode::~ScriptNode() @@ -311,24 +313,34 @@ const IntPlug *ScriptNode::frameEndPlug() const return getChild( g_firstPlugIndex + 2 )->getChild( 1 ); } -FloatPlug *ScriptNode::framesPerSecondPlug() +FloatPlug *ScriptNode::framePlug() { return getChild( g_firstPlugIndex + 3 ); } -const FloatPlug *ScriptNode::framesPerSecondPlug() const +const FloatPlug *ScriptNode::framePlug() const { return getChild( g_firstPlugIndex + 3 ); } +FloatPlug *ScriptNode::framesPerSecondPlug() +{ + return getChild( g_firstPlugIndex + 4 ); +} + +const FloatPlug *ScriptNode::framesPerSecondPlug() const +{ + return getChild( g_firstPlugIndex + 4 ); +} + CompoundDataPlug *ScriptNode::variablesPlug() { - return getChild( g_firstPlugIndex + 4 ); + return getChild( g_firstPlugIndex + 5 ); } const CompoundDataPlug *ScriptNode::variablesPlug() const { - return getChild( g_firstPlugIndex + 4 ); + return getChild( g_firstPlugIndex + 5 ); } bool ScriptNode::acceptsParent( const GraphComponent *potentialParent ) const @@ -774,6 +786,10 @@ void ScriptNode::plugSet( Plug *plug ) { frameStartPlug()->setValue( std::min( frameStartPlug()->getValue(), frameEndPlug()->getValue() ) ); } + else if( plug == framePlug() ) + { + context()->setFrame( framePlug()->getValue() ); + } else if( plug == framesPerSecondPlug() ) { context()->setFramesPerSecond( framesPerSecondPlug()->getValue() ); @@ -793,3 +809,17 @@ void ScriptNode::plugSet( Plug *plug ) context()->set( "script:name", fileName.stem().string() ); } } + +void ScriptNode::contextChanged( const Context *context, const IECore::InternedString &name ) +{ + if( name == "frame" ) + { + framePlug()->setValue( context->getFrame() ); + } + else if( name == "framesPerSecond" ) + { + framesPerSecondPlug()->setValue( context->getFramesPerSecond() ); + } + /// \todo Emit a warning if manual changes are made that + /// wouldn't be preserved across save/load. +}