diff --git a/Changes.md b/Changes.md index c209e5cafb2..b5327ee2417 100644 --- a/Changes.md +++ b/Changes.md @@ -10,6 +10,8 @@ Improvements - Instancer : - Improved Arnold raytracing performance for encapsulated instancers with many prototypes. All instances are now output in a single top-level procedural rather than a top-level procedural per prototype, resulting in more optimal BVH traversals in Arnold. - Reduced scene generation time for encapsulated instancers by around 20%. +- NodeEditor : Added Alt + middle-click action for showing context variable substitutions in strings. +- LightEditor, RenderPassEditor : History windows now use a context determined relative to the current focus node. Fixes ----- @@ -18,6 +20,11 @@ Fixes - Fixed partial image updates when an unrelated InteractiveRender was running (#6043). - Fixed "colour tearing", where updates to some image channels became visible before updates to others. - Fixed unnecessary texture updates when specific image tiles don't change. +- GraphEditor : + - Fixed lingering error badges (#3820). +- RenderPassEditor : + - Fixed history window to update on context changes, for example, when the current frame is changed. + - Fixed invalid `scene:path` context variables created by the history window. [^1] Breaking Changes ---------------- diff --git a/doc/examples/rendering/renderPassEditorArnold.gfr b/doc/examples/rendering/renderPassEditorArnold.gfr index 3ec1f72a47b..9114d0bc7b7 100644 --- a/doc/examples/rendering/renderPassEditorArnold.gfr +++ b/doc/examples/rendering/renderPassEditorArnold.gfr @@ -8,8 +8,8 @@ import IECore import imath Gaffer.Metadata.registerValue( parent, "serialiser:milestoneVersion", 1, persistent=False ) -Gaffer.Metadata.registerValue( parent, "serialiser:majorVersion", 4, persistent=False ) -Gaffer.Metadata.registerValue( parent, "serialiser:minorVersion", 2, persistent=False ) +Gaffer.Metadata.registerValue( parent, "serialiser:majorVersion", 5, persistent=False ) +Gaffer.Metadata.registerValue( parent, "serialiser:minorVersion", 0, persistent=False ) Gaffer.Metadata.registerValue( parent, "serialiser:patchVersion", 0, persistent=False ) __children = {} @@ -41,7 +41,7 @@ parent.addChild( __children["ArnoldOptions"] ) __children["ArnoldOptions"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["RenderPassWedge"] = GafferScene.RenderPassWedge( "RenderPassWedge" ) parent.addChild( __children["RenderPassWedge"] ) -__children["RenderPassWedge"]["preTasks"].addChild( GafferDispatch.TaskNode.TaskPlug( "preTask1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["RenderPassWedge"]["preTasks"].resize( 2 ) __children["RenderPassWedge"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["BatchOutputs"] = GafferScene.Outputs( "BatchOutputs" ) parent.addChild( __children["BatchOutputs"] ) @@ -154,9 +154,7 @@ __children["Lights"].addChild( GafferArnold.ArnoldLight( "SpotLight" ) ) __children["Lights"]["SpotLight"].loadShader( "spot_light" ) __children["Lights"]["SpotLight"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Lights"].addChild( GafferScene.Group( "Group" ) ) -__children["Lights"]["Group"]["in"].addChild( GafferScene.ScenePlug( "in1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) -__children["Lights"]["Group"]["in"].addChild( GafferScene.ScenePlug( "in2", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) -__children["Lights"]["Group"]["in"].addChild( GafferScene.ScenePlug( "in3", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Lights"]["Group"]["in"].resize( 4 ) __children["Lights"]["Group"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Lights"].addChild( GafferArnold.ArnoldLight( "QuadLight" ) ) __children["Lights"]["QuadLight"].loadShader( "quad_light" ) @@ -165,7 +163,7 @@ __children["Lights"].addChild( GafferArnold.ArnoldLight( "SpotLight1" ) ) __children["Lights"]["SpotLight1"].loadShader( "spot_light" ) __children["Lights"]["SpotLight1"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Lights"].addChild( GafferScene.Parent( "Parent" ) ) -__children["Lights"]["Parent"]["children"].addChild( GafferScene.ScenePlug( "child1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Lights"]["Parent"]["children"].resize( 2 ) __children["Lights"]["Parent"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Lights"].addChild( Gaffer.BoolPlug( "enabled", defaultValue = True, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Cameras"] = Gaffer.Box( "Cameras" ) @@ -180,11 +178,10 @@ __children["Cameras"]["BoxOut"].setup( GafferScene.ScenePlug( "in", ) ) __children["Cameras"]["BoxOut"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Cameras"].addChild( GafferScene.ScenePlug( "out", direction = Gaffer.Plug.Direction.Out, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Cameras"].addChild( GafferScene.Group( "Group" ) ) -__children["Cameras"]["Group"]["in"].addChild( GafferScene.ScenePlug( "in1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) -__children["Cameras"]["Group"]["in"].addChild( GafferScene.ScenePlug( "in2", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Cameras"]["Group"]["in"].resize( 3 ) __children["Cameras"]["Group"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Cameras"].addChild( GafferScene.Parent( "Parent" ) ) -__children["Cameras"]["Parent"]["children"].addChild( GafferScene.ScenePlug( "child1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Cameras"]["Parent"]["children"].resize( 2 ) __children["Cameras"]["Parent"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Cameras"].addChild( Gaffer.BoxIn( "BoxIn" ) ) __children["Cameras"]["BoxIn"].setup( GafferScene.ScenePlug( "out", ) ) @@ -199,9 +196,7 @@ __children["Assets"]["BoxOut"].setup( GafferScene.ScenePlug( "in", ) ) __children["Assets"]["BoxOut"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"].addChild( GafferScene.ScenePlug( "out", direction = Gaffer.Plug.Direction.Out, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"].addChild( GafferScene.Parent( "Parent" ) ) -__children["Assets"]["Parent"]["children"].addChild( GafferScene.ScenePlug( "child1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) -__children["Assets"]["Parent"]["children"].addChild( GafferScene.ScenePlug( "child2", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) -__children["Assets"]["Parent"]["children"].addChild( GafferScene.ScenePlug( "child3", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Assets"]["Parent"]["children"].resize( 4 ) __children["Assets"]["Parent"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"].addChild( Gaffer.Box( "Environment" ) ) __children["Assets"]["Environment"].addChild( GafferScene.Plane( "Ground" ) ) @@ -228,14 +223,14 @@ __children["Assets"]["Environment"]["StandardSurface"].addChild( Gaffer.V2fPlug( __children["Assets"]["Environment"].addChild( GafferScene.ShaderAssignment( "ShaderAssignment_walls" ) ) __children["Assets"]["Environment"]["ShaderAssignment_walls"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Environment"].addChild( GafferScene.Parent( "Parent" ) ) -__children["Assets"]["Environment"]["Parent"]["children"].addChild( GafferScene.ScenePlug( "child1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Assets"]["Environment"]["Parent"]["children"].resize( 2 ) __children["Assets"]["Environment"]["Parent"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Environment"].addChild( GafferScene.PathFilter( "PathFilter1" ) ) __children["Assets"]["Environment"]["PathFilter1"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Environment"].addChild( GafferScene.PathFilter( "PathFilter2" ) ) __children["Assets"]["Environment"]["PathFilter2"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Environment"].addChild( GafferScene.Group( "Group" ) ) -__children["Assets"]["Environment"]["Group"]["in"].addChild( GafferScene.ScenePlug( "in1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Assets"]["Environment"]["Group"]["in"].resize( 2 ) __children["Assets"]["Environment"]["Group"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Environment"].addChild( GafferArnold.ArnoldShader( "Noise" ) ) __children["Assets"]["Environment"]["Noise"].loadShader( "noise" ) @@ -258,9 +253,7 @@ __children["Assets"]["Props"].addChild( GafferScene.ShaderAssignment( "ShaderAss __children["Assets"]["Props"]["ShaderAssignment3"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Props"].addChild( GafferScene.ScenePlug( "out", direction = Gaffer.Plug.Direction.Out, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Props"].addChild( GafferScene.Group( "Group" ) ) -__children["Assets"]["Props"]["Group"]["in"].addChild( GafferScene.ScenePlug( "in1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) -__children["Assets"]["Props"]["Group"]["in"].addChild( GafferScene.ScenePlug( "in2", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) -__children["Assets"]["Props"]["Group"]["in"].addChild( GafferScene.ScenePlug( "in3", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Assets"]["Props"]["Group"]["in"].resize( 4 ) __children["Assets"]["Props"]["Group"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Props"].addChild( GafferScene.PathFilter( "PathFilter1" ) ) __children["Assets"]["Props"]["PathFilter1"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) @@ -270,21 +263,19 @@ __children["Assets"]["Props"]["StandardSurface4"].addChild( Gaffer.V2fPlug( "__u __children["Assets"]["Props"].addChild( GafferScene.ShaderAssignment( "ShaderAssignment4" ) ) __children["Assets"]["Props"]["ShaderAssignment4"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Props"].addChild( GafferScene.Group( "Group1" ) ) -__children["Assets"]["Props"]["Group1"]["in"].addChild( GafferScene.ScenePlug( "in1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) -__children["Assets"]["Props"]["Group1"]["in"].addChild( GafferScene.ScenePlug( "in2", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Assets"]["Props"]["Group1"]["in"].resize( 3 ) __children["Assets"]["Props"]["Group1"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Props"].addChild( GafferScene.PathFilter( "PathFilter2" ) ) __children["Assets"]["Props"]["PathFilter2"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Props"].addChild( GafferScene.Parent( "Parent3" ) ) -__children["Assets"]["Props"]["Parent3"]["children"].addChild( GafferScene.ScenePlug( "child1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Assets"]["Props"]["Parent3"]["children"].resize( 2 ) __children["Assets"]["Props"]["Parent3"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Props"].addChild( Gaffer.BoxOut( "BoxOut" ) ) __children["Assets"]["Props"]["BoxOut"].setup( GafferScene.ScenePlug( "in", ) ) __children["Assets"]["Props"]["BoxOut"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Props"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Props"].addChild( GafferScene.Group( "Group2" ) ) -__children["Assets"]["Props"]["Group2"]["in"].addChild( GafferScene.ScenePlug( "in1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) -__children["Assets"]["Props"]["Group2"]["in"].addChild( GafferScene.ScenePlug( "in2", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Assets"]["Props"]["Group2"]["in"].resize( 3 ) __children["Assets"]["Props"]["Group2"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Props"].addChild( GafferScene.Sphere( "Sphere2" ) ) __children["Assets"]["Props"]["Sphere2"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) @@ -309,7 +300,7 @@ __children["Assets"]["Fx"]["BoxOut"].setup( GafferScene.ScenePlug( "in", ) ) __children["Assets"]["Fx"]["BoxOut"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Fx"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Fx"].addChild( GafferScene.Group( "Group" ) ) -__children["Assets"]["Fx"]["Group"]["in"].addChild( GafferScene.ScenePlug( "in1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Assets"]["Fx"]["Group"]["in"].resize( 2 ) __children["Assets"]["Fx"]["Group"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Fx"].addChild( GafferScene.SceneReader( "SceneReader" ) ) __children["Assets"]["Fx"]["SceneReader"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) @@ -343,7 +334,7 @@ __children["Assets"]["Chars"]["BoxOut"].setup( GafferScene.ScenePlug( "in", ) ) __children["Assets"]["Chars"]["BoxOut"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Chars"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Chars"].addChild( GafferScene.Group( "Group" ) ) -__children["Assets"]["Chars"]["Group"]["in"].addChild( GafferScene.ScenePlug( "in1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Assets"]["Chars"]["Group"]["in"].resize( 2 ) __children["Assets"]["Chars"]["Group"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Assets"]["Chars"].addChild( GafferScene.SceneReader( "SceneReader" ) ) __children["Assets"]["Chars"]["SceneReader"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) @@ -441,7 +432,7 @@ __children["RenderPassDefinitions"]["RenderPassOptionEdits"]["edits"].addRows( 8 __children["RenderPassDefinitions"]["RenderPassOptionEdits"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["RenderPassDefinitions"].addChild( GafferScene.RenderPasses( "RenderPasses" ) ) __children["RenderPassDefinitions"]["RenderPasses"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) -parent["variables"]["imageCataloguePort"]["value"].setValue( 37295 ) +parent["variables"]["imageCataloguePort"]["value"].setValue( 38301 ) parent["variables"]["renderPass"]["value"].setValue( 'all' ) Gaffer.Metadata.registerValue( parent["variables"]["imageCataloguePort"], 'readOnly', True ) Gaffer.Metadata.registerValue( parent["variables"]["projectName"]["name"], 'readOnly', True ) @@ -1298,7 +1289,7 @@ __children["Backdrop15"]["title"].setValue( '' ) __children["Backdrop15"]["__uiBound"].setValue( imath.Box2f( imath.V2f( -18.7024384, -9.08207321 ), imath.V2f( 62.0365562, 19.2328033 ) ) ) __children["Backdrop15"]["__uiPosition"].setValue( imath.V2f( 243.806061, -105.895744 ) ) __children["Backdrop16"]["title"].setValue( 'Interactive preview' ) -__children["Backdrop16"]["description"].setValue( 'The Render Pass Editor can be used to activate a render pass for interactive preview. The active render pass is displayed within the Render Pass Editor\'s "Active" column (represented by a green dot icon). \n\nThis example begins with the "all" render pass active and it will be rendered by the InteractiveRender node. \n\nTo activate a different render pass, double-click within the "Active" column next to the render pass of your choice. The green icon will move next to that render pass to signify that it is now active. The scene will update to display the newly active render pass and the InteractiveRender node will render it.' ) +__children["Backdrop16"]["description"].setValue( 'The Render Pass Editor can be used to activate a render pass for interactive preview. The active render pass is displayed within the Render Pass Editor\'s "Active" column (represented by a yellow dot icon). \n\nThis example begins with the "all" render pass active and it will be rendered by the InteractiveRender node. \n\nTo activate a different render pass, double-click within the "Active" column next to the render pass of your choice. The yellow icon will move next to that render pass to signify that it is now active. The scene will update to display the newly active render pass and the InteractiveRender node will render it.' ) __children["Backdrop16"]["__uiBound"].setValue( imath.Box2f( imath.V2f( -3.94014359, -7.6455574 ), imath.V2f( 40, 20 ) ) ) __children["Backdrop16"]["__uiPosition"].setValue( imath.V2f( 265.371613, -106.806831 ) ) __children["Backdrop17"]["title"].setValue( '' ) diff --git a/python/GafferSceneUI/_HistoryWindow.py b/python/GafferSceneUI/_HistoryWindow.py index b4fc39f5a52..295326ac67f 100644 --- a/python/GafferSceneUI/_HistoryWindow.py +++ b/python/GafferSceneUI/_HistoryWindow.py @@ -79,21 +79,17 @@ def headerData( self, canceller = None ) : class _NodeNameColumn( GafferUI.PathColumn ) : - def __init__( self, title, property, scriptNode ) : + def __init__( self, title, property ) : GafferUI.PathColumn.__init__( self ) self.__title = title self.__property = property - self.__scriptNode = scriptNode def cellData( self, path, canceller = None ) : - cellValue = path.property( self.__property ) - - data = self.CellData( cellValue.relativeName( self.__scriptNode ) ) - - return data + node = path.property( self.__property ) + return self.CellData( node.relativeName( node.scriptNode() ) ) def headerData( self, canceller = None ) : @@ -136,9 +132,7 @@ def headerData( self, canceller = None ) : class _HistoryWindow( GafferUI.Window ) : - def __init__( self, inspector, scenePath, context, scriptNode, title=None, **kw ) : - - assert( isinstance( scriptNode, Gaffer.ScriptNode ) ) + def __init__( self, inspector, inspectionPath, title=None, **kw ) : if title is None : title = "History" @@ -146,14 +140,13 @@ def __init__( self, inspector, scenePath, context, scriptNode, title=None, **kw GafferUI.Window.__init__( self, title, **kw ) self.__inspector = inspector - self.__scenePath = scenePath - self.__scriptNode = scriptNode + self.__inspectionPath = inspectionPath with self : self.__pathListingWidget = GafferUI.PathListingWidget( Gaffer.DictPath( {}, "/" ), columns = ( - _NodeNameColumn( "Node", "history:node", self.__scriptNode ), + _NodeNameColumn( "Node", "history:node" ), _ValueColumn( "Value", "history:value", "history:fallbackValue" ), _OperationIconColumn( "Operation", "history:operation" ), ), @@ -175,17 +168,19 @@ def __init__( self, inspector, scenePath, context, scriptNode, title=None, **kw inspector.dirtiedSignal().connect( Gaffer.WeakMethod( self.__inspectorDirtied ) ) - context.changedSignal().connect( Gaffer.WeakMethod( self.__contextChanged ) ) - - self.__updatePath( context ) + ## \todo We want to make the inspection framework scene-agnostic. We could add an `Inspector::plug()` method + # to provide a scene-agnostic way of querying what is being inspected, and use it here. + self.__contextTracker = GafferUI.ContextTracker.acquireForFocus( self.__inspectionPath.getScene() ) + self.__contextTracker.changedSignal().connect( Gaffer.WeakMethod( self.__contextChanged ) ) + self.__updatePath() - def __updatePath( self, newContext ) : + def __updatePath( self ) : - with Gaffer.Context( newContext ) as context : - context["scene:path"] = GafferScene.ScenePlug.stringToPath( self.__scenePath ) + self.__inspectionPath.setContext( self.__contextTracker.context( self.__inspectionPath.getScene() ) ) + with self.__inspectionPath.inspectionContext() : self.__path = self.__inspector.historyPath() - self.__pathChangedConnection = self.__path.pathChangedSignal().connect( Gaffer.WeakMethod( self.__pathChanged ), scoped = True ) + self.__pathChangedConnection = self.__path.pathChangedSignal().connect( Gaffer.WeakMethod( self.__pathChanged ), scoped = True ) self.__pathListingWidget.setPath( self.__path ) def __pathChanged( self, path ) : @@ -277,9 +272,9 @@ def __inspectorDirtied( self, inspector ) : self.__path._emitPathChanged() - def __contextChanged( self, context, key ) : + def __contextChanged( self, contextTracker ) : - self.__updatePath( context ) + self.__updatePath() def __updateFinished( self, pathListing ) : @@ -291,7 +286,7 @@ def __updateFinished( self, pathListing ) : # The node and all of its parents up to the script node # contribute to the path name. - while node is not self.__scriptNode and node is not None : + while node is not None and not isinstance( node, Gaffer.ScriptNode ) : self.__nodeNameChangedSignals.append( node.nameChangedSignal().connect( Gaffer.WeakMethod( self.__nodeNameChanged ), diff --git a/python/GafferSceneUI/_InspectorColumn.py b/python/GafferSceneUI/_InspectorColumn.py index 490b954f9f0..5ffc8425880 100644 --- a/python/GafferSceneUI/_InspectorColumn.py +++ b/python/GafferSceneUI/_InspectorColumn.py @@ -329,20 +329,16 @@ def __showHistory( pathListing ) : columns = pathListing.getColumns() selection = pathListing.getSelection() - path = pathListing.getPath().copy() for i, column in enumerate( columns ) : for pathString in selection[i].paths() : + path = pathListing.getPath().copy() path.setFromString( pathString ) - inspectionContext = path.inspectionContext() - if inspectionContext is None : + if path.inspectionContext() is None : continue - window = _HistoryWindow( column.inspector(), - pathString, - inspectionContext, - pathListing.ancestor( GafferUI.Editor ).scriptNode(), + path, "History : {} : {}".format( pathString, column.headerData().value ) ) pathListing.ancestor( GafferUI.Window ).addChildWindow( window, removeOnClose = True ) diff --git a/python/GafferUI/PathPlugValueWidget.py b/python/GafferUI/PathPlugValueWidget.py index ea2cc725a89..5fd3d19f4f2 100644 --- a/python/GafferUI/PathPlugValueWidget.py +++ b/python/GafferUI/PathPlugValueWidget.py @@ -41,6 +41,7 @@ import GafferUI from .PlugValueWidget import sole +from .StringPlugValueWidget import addSubstitutionsPopup import os @@ -67,13 +68,14 @@ def __init__( self, plug, path=None, pathChooserDialogueKeywords=None, **kw ) : self.__pathChooserDialogueKeywords = pathChooserDialogueKeywords - pathWidget = GafferUI.PathWidget( self.__path ) - self._addPopupMenu( pathWidget ) - self.__row.append( pathWidget ) + with self.__row : - button = GafferUI.Button( image = "pathChooser.png", hasFrame=False ) - button.clickedSignal().connect( Gaffer.WeakMethod( self.__buttonClicked ) ) - self.__row.append( button ) + pathWidget = GafferUI.PathWidget( self.__path ) + self._addPopupMenu( pathWidget ) + addSubstitutionsPopup( pathWidget ) + + button = GafferUI.Button( image = "pathChooser.png", hasFrame=False ) + button.clickedSignal().connect( Gaffer.WeakMethod( self.__buttonClicked ) ) pathWidget.editingFinishedSignal().connect( Gaffer.WeakMethod( self.__setPlugValue ) ) @@ -102,6 +104,7 @@ def getToolTip( self ) : result += "- Tab to autocomplete path component\n" result += "- Select path component (or hit ) to show path-level contents menu\n" result += "- Select all to show path hierarchy menu\n" + result += "- Alt + middle-click to show context variable substitutions\n" return result diff --git a/python/GafferUI/StringPlugValueWidget.py b/python/GafferUI/StringPlugValueWidget.py index 4c7afaeb163..b04fbba6213 100644 --- a/python/GafferUI/StringPlugValueWidget.py +++ b/python/GafferUI/StringPlugValueWidget.py @@ -35,6 +35,8 @@ # ########################################################################## +import functools + import IECore import Gaffer @@ -59,6 +61,7 @@ def __init__( self, plugs, **kw ) : self._addPopupMenu( self.__textWidget ) self.__textWidget.keyPressSignal().connect( Gaffer.WeakMethod( self.__keyPress ) ) + addSubstitutionsPopup( self.__textWidget ) self.__textWidget.editingFinishedSignal().connect( Gaffer.WeakMethod( self.__editingFinished ) ) self.__textChangedConnection = self.__textWidget.textChangedSignal().connect( Gaffer.WeakMethod( self.__textChanged ) ) @@ -71,6 +74,15 @@ def setHighlighted( self, highlighted ) : GafferUI.PlugValueWidget.setHighlighted( self, highlighted ) self.textWidget().setHighlighted( highlighted ) + def getToolTip( self ) : + + result = GafferUI.PlugValueWidget.getToolTip( self ) + + result += "\n## Actions\n" + result += " - Alt + middle-click to show context variable substitutions\n" + + return result + def _updateFromValues( self, values, exception ) : value = sole( values ) @@ -186,3 +198,61 @@ def __setPlugValues( self ) : self.__textWidget.clearUndo() GafferUI.PlugValueWidget.registerType( Gaffer.StringPlug, StringPlugValueWidget ) + +# Substitutions Popup +# =================== + +def addSubstitutionsPopup( widget ) : + + widget.buttonPressSignal().connect( __substitutionsButtonPress ) + widget.buttonReleaseSignal().connect( __substitutionsButtonRelease ) + +def __substitutionsCopyClicked( widget, text ) : + + application = widget.ancestor( GafferUI.PlugValueWidget ).scriptNode().ancestor( Gaffer.ApplicationRoot ) + application.setClipboardContents( IECore.StringData( text ) ) + widget.ancestor( GafferUI.Window ).close() + return True + +def __substitutionsDragBegin( widget, event, text ) : + + GafferUI.Pointer.setCurrent( "values" ) + return text + +def __substitutionsDragEnd( widget, event ) : + + GafferUI.Pointer.setCurrent( None ) + return True + +def __substitutionsButtonPress( widget, event ) : + + if event.buttons != event.Buttons.Middle or event.modifiers != event.Modifiers.Alt : + return False + + # Alt + Middle click + + plugValueWidget = widget.ancestor( GafferUI.PlugValueWidget ) + substitutions = sole( p.substitutions() for p in plugValueWidget.getPlugs() ) + if substitutions is None : + return True + + text = plugValueWidget.context().substitute( widget.getText(), substitutions ) + if text == widget.getText() : + return True + + with GafferUI.PopupWindow() as widget.__substitutionsPopupWindow : + with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, 4 ) : + label = GafferUI.Label( f"{text}" ) + label.buttonPressSignal().connect( lambda widget, event : True ) + label.dragBeginSignal().connect( functools.partial( __substitutionsDragBegin, text = text ) ) + label.dragEndSignal().connect( __substitutionsDragEnd ) + button = GafferUI.Button( image = "duplicate.png", hasFrame = False, toolTip = "Copy Text" ) + button.clickedSignal().connect( functools.partial( __substitutionsCopyClicked, text = text ) ) + + widget.__substitutionsPopupWindow.popup( parent = widget ) + return True + +def __substitutionsButtonRelease( widget, event ) : + + # Take event to stop Alt+Middle from pasting on Linux. + return event.button == event.Buttons.Middle and event.modifiers == event.Modifiers.Alt diff --git a/resources/graphics.svg b/resources/graphics.svg index 16669f77c69..320a7fd94ea 100644 --- a/resources/graphics.svg +++ b/resources/graphics.svg @@ -6,7 +6,7 @@ height="1000" id="svg2" version="1.1" - inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" sodipodi:docname="graphics.svg" enable-background="new" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" @@ -306,7 +306,17 @@ inkscape:bbox-nodes="true" inkscape:showpageshadow="2" inkscape:pagecheckerboard="0" - inkscape:deskcolor="#4c4c4c"> + inkscape:deskcolor="#4c4c4c" + showgrid="false" + inkscape:zoom="6.984" + inkscape:cx="77.033219" + inkscape:cy="2868.7715" + inkscape:window-width="1501" + inkscape:window-height="1088" + inkscape:window-x="1547" + inkscape:window-y="231" + inkscape:window-maximized="0" + inkscape:current-layer="layer1"> @@ -10084,7 +10094,7 @@ cy="2784.9451" cx="54.179211" id="circle6245-3" - style="display:inline;vector-effect:none;fill:#54ad5e;fill-opacity:1;fill-rule:nonzero;stroke:#3c3c3c;stroke-width:1.00157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> + style="display:inline;vector-effect:none;fill:#f0dc28;fill-opacity:1;fill-rule:nonzero;stroke:#3c3c3c;stroke-width:1.00157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> + style="display:inline;vector-effect:none;fill:#f0dc28;fill-opacity:0.38041458;fill-rule:nonzero;stroke:#779cbd;stroke-width:1.00157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> diff --git a/src/GafferUI/StandardNodeGadget.cpp b/src/GafferUI/StandardNodeGadget.cpp index edb1f507b8c..e2f5b913253 100644 --- a/src/GafferUI/StandardNodeGadget.cpp +++ b/src/GafferUI/StandardNodeGadget.cpp @@ -382,6 +382,10 @@ class StandardNodeGadget::ErrorGadget : public Gadget { PlugEntry &entry = m_errors[plug]; entry.error = error; + if( auto tracker = ContextTracker::acquireForFocus( plug.get() ) ) + { + entry.trackedContext = tracker->context( plug.get() )->hash(); + } if( !entry.parentChangedConnection.connected() ) { entry.parentChangedConnection = plug->parentChangedSignal().connect( boost::bind( &ErrorGadget::plugParentChanged, this, ::_1 ) ); @@ -395,6 +399,27 @@ class StandardNodeGadget::ErrorGadget : public Gadget m_image->setVisible( m_errors.size() ); } + void removeStaleErrors( const ContextTracker *tracker ) + { + // We call `removeError()` whenever a plug is dirtied, but that can + // still leave errors lingering if the original error was a problem + // of _context_ rather than plug values. So when the tracked context + // changes, we remove any errors which occurred within a different + // tracked context. + for( auto it = m_errors.begin(); it != m_errors.end(); ) + { + if( it->second.trackedContext != tracker->context( it->first.get() )->hash() ) + { + it = m_errors.erase( it ); + } + else + { + ++it; + } + } + m_image->setVisible( m_errors.size() ); + } + std::string getToolTip( const IECore::LineSegment3f &position ) const override { std::string result = Gadget::getToolTip( position ); @@ -434,6 +459,7 @@ class StandardNodeGadget::ErrorGadget : public Gadget struct PlugEntry { std::string error; + IECore::MurmurHash trackedContext; Signals::ScopedConnection parentChangedConnection; }; @@ -786,13 +812,16 @@ void StandardNodeGadget::updateFromContextTracker( const ContextTracker *context { m_nodeEnabledInContextTracker = true; } - /// \todo Should we clear ErrorGadget when the context changes? } else { m_nodeEnabledInContextTracker = std::nullopt; } updateStrikeThroughVisibility(); + if( auto g = errorGadget( /* createIfMissing = */ false ) ) + { + g->removeStaleErrors( contextTracker ); + } } void StandardNodeGadget::updateTextDimming()