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( "

{} cannot be used as it is not upstream of {}

".format( dropNode.getName(), inputNode.getName() ) ) + self.__popup.popup() + + self.__frame.setHighlighted( False ) + + return True + # ProcessorWidget # =============== From d42f7dd12e0c6fb9f00e478c8443f1d0a379a971 Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Thu, 30 May 2024 17:47:48 -0700 Subject: [PATCH 3/5] EditScopePlugValueWidget : Support middle-drag of current Edit Scope --- Changes.md | 1 + python/GafferUI/EditScopeUI.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Changes.md b/Changes.md index 078170d052e..da9fa9142f7 100644 --- a/Changes.md +++ b/Changes.md @@ -13,6 +13,7 @@ Improvements - 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. + - Added support for middle-dragging from the widget to access the current Edit Scope node. API --- diff --git a/python/GafferUI/EditScopeUI.py b/python/GafferUI/EditScopeUI.py index 22aa3a7d89e..740b0a787a6 100644 --- a/python/GafferUI/EditScopeUI.py +++ b/python/GafferUI/EditScopeUI.py @@ -139,6 +139,9 @@ def __init__( self, plug, **kw ) : ) GafferUI.Spacer( imath.V2i( 4, 1 ), imath.V2i( 4, 1 ) ) + self.buttonPressSignal().connect( Gaffer.WeakMethod( self.__buttonPress ), scoped = False ) + self.dragBeginSignal().connect( Gaffer.WeakMethod( self.__dragBegin ), scoped = False ) + self.dragEndSignal().connect( Gaffer.WeakMethod( self.__dragEnd ), scoped = False ) 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 @@ -406,8 +409,33 @@ def __dropNode( self, event ) : else: return None + def __buttonPress( self, widget, event ) : + + return event.buttons == event.Buttons.Middle and self.__editScope() is not None + + def __dragBegin( self, widget, event ) : + + if event.buttons != event.Buttons.Middle : + return None + + data = self.__editScope() + if data is None : + return None + + GafferUI.Pointer.setCurrent( "nodes" ) + return data + + def __dragEnd( self, widget, event ) : + + GafferUI.Pointer.setCurrent( "" ) + + return True + def __dragEnter( self, widget, event ) : + if event.sourceWidget is self : + return False + if self.__dropNode( event ) : self.__frame.setHighlighted( True ) From 3f45625c021cd3fb0e6c2ba41b5c04b256798e1b Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Thu, 6 Jun 2024 14:10:07 -0700 Subject: [PATCH 4/5] EditScopePlugValueWidget : Warn when setting Edit Scope with nothing viewed --- python/GafferUI/EditScopeUI.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/python/GafferUI/EditScopeUI.py b/python/GafferUI/EditScopeUI.py index 740b0a787a6..61a1c5f4e84 100644 --- a/python/GafferUI/EditScopeUI.py +++ b/python/GafferUI/EditScopeUI.py @@ -395,10 +395,6 @@ def __userNodes( editScope ) : def __dropNode( self, event ) : - node = self.__inputNode() - if node is None : - return None - if isinstance( event.data, Gaffer.EditScope ) : return event.data elif ( @@ -449,10 +445,15 @@ def __dragLeave( self, widget, event ) : def __drop( self, widget, event ) : + inputNode = self.__inputNode() dropNode = self.__dropNode( event ) - - if dropNode : - inputNode = self.__inputNode() + if inputNode is None : + with GafferUI.PopupWindow() as self.__popup : + with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 ) : + GafferUI.Image( "warningSmall.png" ) + GafferUI.Label( "

The Edit Scope cannot be set while nothing is viewed

" ) + self.__popup.popup() + elif dropNode : upstream = Gaffer.NodeAlgo.findAllUpstream( inputNode, self.__editScopePredicate ) if self.__editScopePredicate( inputNode ) : upstream.insert( 0, inputNode ) From a2e77989fc238508cc6de3429f804346aafc1599 Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Thu, 6 Jun 2024 14:22:47 -0700 Subject: [PATCH 5/5] EditScopePlugValueWidget : Accept drags with either left or middle buttons --- python/GafferUI/EditScopeUI.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/GafferUI/EditScopeUI.py b/python/GafferUI/EditScopeUI.py index 61a1c5f4e84..af72c15cda7 100644 --- a/python/GafferUI/EditScopeUI.py +++ b/python/GafferUI/EditScopeUI.py @@ -407,11 +407,11 @@ def __dropNode( self, event ) : def __buttonPress( self, widget, event ) : - return event.buttons == event.Buttons.Middle and self.__editScope() is not None + return event.buttons & ( event.Buttons.Left | event.Buttons.Middle ) and self.__editScope() is not None def __dragBegin( self, widget, event ) : - if event.buttons != event.Buttons.Middle : + if not event.buttons & ( event.Buttons.Left | event.Buttons.Middle ) : return None data = self.__editScope()