From 049303f702b919f8f8d9f84cce0c3389dc740a35 Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:02:39 -0700 Subject: [PATCH 1/5] EditScopePlugValueWidget : Include EditScopes in viewed SubGraphs Otherwise the experience feels a bit inconsistent when switching from viewing the output of a Box to viewing the node immediately downstream of the Box. In the first case, no EditScopes within the Box appear in the menu. --- Changes.md | 1 + python/GafferUI/EditScopeUI.py | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Changes.md b/Changes.md index 2bc2dfb4590..31df7016cd2 100644 --- a/Changes.md +++ b/Changes.md @@ -10,6 +10,7 @@ Improvements - Increased the size of the triangle indicators for the decay ranges. - The decay range indicators are now scaled by the light's `gl:visualiser:scale` attribute. - The decay range is now ignored when framing a light in the Viewer. +- EditScopePlugValueWidget : When viewing the output of a Box, the menu now displays the Edit Scopes contained within. API --- diff --git a/python/GafferUI/EditScopeUI.py b/python/GafferUI/EditScopeUI.py index c0b6de7b19d..5b5ae68faa7 100644 --- a/python/GafferUI/EditScopeUI.py +++ b/python/GafferUI/EditScopeUI.py @@ -261,7 +261,14 @@ def __menuDefinition( self ) : # (we can't start at _this_ node, as then we will visit our own input connection # which may no longer be upstream of the viewed node). if node["in"].getInput() is not None : - node = node["in"].getInput().node() + inputNode = node["in"].getInput().node() + if not isinstance( inputNode, Gaffer.EditScope ) and isinstance( inputNode, Gaffer.SubGraph ) : + # If we're starting from a SubGraph then attempt to begin the search from the + # first input of the node's output so we can find any Edit Scopes within. + output = node["in"].getInput().getInput() + node = output.node() if output and inputNode.isAncestorOf( output ) else inputNode + else : + node = inputNode else : node = None From 60e9cc4fd440b0aae0c9cf143a62ec116ef39992 Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Thu, 30 May 2024 16:17:47 -0700 Subject: [PATCH 2/5] EditScopePlugValueWidget : Support dropping of EditScope nodes To keep the experience consistent with selecting an Edit Scope from the menu, we only connect dropped Edit Scopes that are upstream of the viewed node (or are the viewed node), and popup a warning for Edit Scopes that we do not connect. --- Changes.md | 4 +- python/GafferUI/EditScopeUI.py | 104 ++++++++++++++++++++++++++------- 2 files changed, 86 insertions(+), 22 deletions(-) diff --git a/Changes.md b/Changes.md index 31df7016cd2..078170d052e 100644 --- a/Changes.md +++ b/Changes.md @@ -10,7 +10,9 @@ Improvements - Increased the size of the triangle indicators for the decay ranges. - The decay range indicators are now scaled by the light's `gl:visualiser:scale` attribute. - The decay range is now ignored when framing a light in the Viewer. -- EditScopePlugValueWidget : When viewing the output of a Box, the menu now displays the Edit Scopes contained within. +- EditScopePlugValueWidget : + - When viewing the output of a Box, the menu now displays the Edit Scopes contained within. + - Added support for dropping an Edit Scope node onto the widget to set it as the current Edit Scope. API --- diff --git a/python/GafferUI/EditScopeUI.py b/python/GafferUI/EditScopeUI.py index 5b5ae68faa7..22aa3a7d89e 100644 --- a/python/GafferUI/EditScopeUI.py +++ b/python/GafferUI/EditScopeUI.py @@ -119,10 +119,10 @@ class EditScopePlugValueWidget( GafferUI.PlugValueWidget ) : def __init__( self, plug, **kw ) : - frame = GafferUI.Frame( borderWidth = 0 ) - GafferUI.PlugValueWidget.__init__( self, frame, plug, **kw ) + self.__frame = GafferUI.Frame( borderWidth = 0 ) + GafferUI.PlugValueWidget.__init__( self, self.__frame, plug, **kw ) - with frame : + with self.__frame : with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 ) : GafferUI.Spacer( imath.V2i( 4, 1 ), imath.V2i( 4, 1 ) ) GafferUI.Label( "Edit Scope" ) @@ -139,6 +139,12 @@ def __init__( self, plug, **kw ) : ) GafferUI.Spacer( imath.V2i( 4, 1 ), imath.V2i( 4, 1 ) ) + self.dragEnterSignal().connect( Gaffer.WeakMethod( self.__dragEnter ), scoped = False ) + self.dragLeaveSignal().connect( Gaffer.WeakMethod( self.__dragLeave ), scoped = False ) + # We connect to the front, and unconditionally return True to ensure that we never + # run the default dropSignal handler from PlugValueWidget. + self.dropSignal().connectFront( Gaffer.WeakMethod( self.__drop ), scoped = False ) + def hasLabel( self ) : return True @@ -199,6 +205,27 @@ def __connectEditScope( self, editScope, *ignored ) : self.getPlug().setInput( editScope["out"] ) + def __inputNode( self ) : + + node = self.getPlug().node() + # We assume that our plug is on a node dedicated to holding settings for the + # UI, and that it has an `in` plug that is connected to the node in the graph + # that is being viewed. We start our node graph traversal at the viewed node + # (we can't start at _this_ node, as then we will visit our own input connection + # which may no longer be upstream of the viewed node). + if node["in"].getInput() is None : + return None + + inputNode = node["in"].getInput().node() + if not isinstance( inputNode, Gaffer.EditScope ) and isinstance( inputNode, Gaffer.SubGraph ) : + # If we're starting from a SubGraph then attempt to begin the search from the + # first input of the node's output so we can find any Edit Scopes within. + output = node["in"].getInput().getInput() + if output is not None and inputNode.isAncestorOf( output ) : + return output.node() + + return inputNode + def __buildMenu( self, result, path, currentEditScope ) : result = IECore.MenuDefinition() @@ -254,28 +281,11 @@ def __menuDefinition( self ) : result = IECore.MenuDefinition() - node = self.getPlug().node() - # We assume that our plug is on a node dedicated to holding settings for the - # UI, and that it has an `in` plug that is connected to the node in the graph - # that is being viewed. We start our node graph traversal at the viewed node - # (we can't start at _this_ node, as then we will visit our own input connection - # which may no longer be upstream of the viewed node). - if node["in"].getInput() is not None : - inputNode = node["in"].getInput().node() - if not isinstance( inputNode, Gaffer.EditScope ) and isinstance( inputNode, Gaffer.SubGraph ) : - # If we're starting from a SubGraph then attempt to begin the search from the - # first input of the node's output so we can find any Edit Scopes within. - output = node["in"].getInput().getInput() - node = output.node() if output and inputNode.isAncestorOf( output ) else inputNode - else : - node = inputNode - else : - node = None - currentEditScope = None if self.getPlug().getInput() is not None : currentEditScope = self.getPlug().getInput().parent() + node = self.__inputNode() if node is not None : upstream = Gaffer.NodeAlgo.findAllUpstream( node, self.__editScopePredicate ) if self.__editScopePredicate( node ) : @@ -380,6 +390,58 @@ def __userNodes( editScope ) : nodes = Gaffer.Metadata.nodesWithMetadata( editScope, "editScope:includeInNavigationMenu" ) return [ n for n in nodes if n.ancestor( Gaffer.EditScope ).isSame( editScope ) ] + def __dropNode( self, event ) : + + node = self.__inputNode() + if node is None : + return None + + if isinstance( event.data, Gaffer.EditScope ) : + return event.data + elif ( + isinstance( event.data, Gaffer.Set ) and event.data.size() == 1 and + isinstance( event.data[0], Gaffer.EditScope ) + ) : + return event.data[0] + else: + return None + + def __dragEnter( self, widget, event ) : + + if self.__dropNode( event ) : + self.__frame.setHighlighted( True ) + + return True + + def __dragLeave( self, widget, event ) : + + self.__frame.setHighlighted( False ) + + return True + + def __drop( self, widget, event ) : + + dropNode = self.__dropNode( event ) + + if dropNode : + inputNode = self.__inputNode() + upstream = Gaffer.NodeAlgo.findAllUpstream( inputNode, self.__editScopePredicate ) + if self.__editScopePredicate( inputNode ) : + upstream.insert( 0, inputNode ) + + if dropNode in upstream : + self.__connectEditScope( dropNode ) + else : + with GafferUI.PopupWindow() as self.__popup : + with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 ) : + GafferUI.Image( "warningSmall.png" ) + GafferUI.Label( "