Skip to content

Commit

Permalink
EditScopePlugValueWidget : Filter available nodes using ContextTracker
Browse files Browse the repository at this point in the history
  • Loading branch information
johnhaddon committed Jul 24, 2024
1 parent 6bcdc6d commit 608216c
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 34 deletions.
1 change: 1 addition & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Improvements

- LightEditor : Values of inherited attributes are now displayed in the Light Editor. These are presented as dimmed "fallback" values.
- LightEditor, RenderPassEditor : Fallback values shown in the history window are displayed with the same dimmed text colour used for fallback values in editor columns.
- EditScope : Filtered the EditScope menu to show only nodes that are active in the relevant context.

Fixes
-----
Expand Down
138 changes: 104 additions & 34 deletions python/GafferUI/EditScopeUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,17 +150,38 @@ def __init__( self, plug, **kw ) :
# run the default dropSignal handler from PlugValueWidget.
self.dropSignal().connectFront( Gaffer.WeakMethod( self.__drop ), scoped = False )

self.__updatePlugInputChangedConnection()
self.__acquireContextTracker()

def hasLabel( self ) :

return True

def setPlugs( self, plugs ) :

GafferUI.PlugValueWidget.setPlugs( self, plugs )
self.__updatePlugInputChangedConnection()
self.__acquireContextTracker()

def getToolTip( self ) :

editScope = self.__editScope()
if editScope is None :
return "Edits will be made using the last relevant node, including nodes not in any EditScope."

unusableReason = self.__unusableReason( editScope )
if unusableReason :
return unusableReason
else :
return "Edits will be made in {}.".format( editScope.getName() )

# We don't actually display values, but this is also called whenever the
# input changes, which is when we need to update.
def _updateFromValues( self, values, exception ) :

editScope = self.__editScope()
editScopeActive = editScope is not None
self.__updateMenuButton( editScope )
self.__updateMenuButton()
self.__navigationMenuButton.setEnabled( editScopeActive )
if editScopeActive :
self.__editScopeNameChangedConnection = editScope.nameChangedSignal().connect(
Expand All @@ -177,19 +198,55 @@ def _updateFromValues( self, values, exception ) :
self._qtWidget().setProperty( "editScopeActive", GafferUI._Variant.toVariant( editScopeActive ) )
self._repolish()

def __updateMenuButton( self, editScope ) :
def __updatePlugInputChangedConnection( self ) :

self.__plugInputChangedConnection = self.getPlug().node().plugInputChangedSignal().connect(
Gaffer.WeakMethod( self.__plugInputChanged ), scoped = True
)

def __plugInputChanged( self, plug ) :

if plug.getName() == "in" and plug.parent() == self.getPlug().node() :
# The result of `__inputNode()` will have changed.
self.__acquireContextTracker()

def __acquireContextTracker( self ) :

self.__contextTracker = GafferUI.ContextTracker.acquire( self.__inputNode() )
self.__contextTrackerChangedConnection = self.__contextTracker.changedSignal().connect(
Gaffer.WeakMethod( self.__contextTrackerChanged ), scoped = True
)

if not self.__contextTracker.updatePending() :
self.__updateMenuButton()
else :
# We'll update later in `__contextTrackerChanged()`.
pass

def __updateMenuButton( self ) :

editScope = self.__editScope()
self.__menuButton.setText( editScope.getName() if editScope is not None else "None" )
self.__menuButton.setImage( self.__editScopeSwatch( editScope ) if editScope is not None else None )

if editScope is not None :
self.__menuButton.setImage(
self.__editScopeSwatch( editScope ) if not self.__unusableReason( editScope ) else "warningSmall.png"
)
else :
self.__menuButton.setImage( None )

def __editScopeNameChanged( self, editScope, oldName ) :

self.__updateMenuButton( editScope )
self.__updateMenuButton()

def __editScopeMetadataChanged( self, editScope, key, reason ) :

if key == "nodeGadget:color" :
self.__updateMenuButton( editScope )
self.__updateMenuButton()

def __contextTrackerChanged( self, contextTracker ) :

self.__updateMenuButton()

def __editScope( self ) :

Expand Down Expand Up @@ -231,6 +288,20 @@ def __inputNode( self ) :

return inputNode

def __activeEditScopes( self ) :

node = self.__inputNode()
if node is None :
return []

result = Gaffer.NodeAlgo.findAllUpstream( node, self.__editScopePredicate )
if self.__editScopePredicate( node ) :
result.insert( 0, node )

result = [ n for n in result if self.__contextTracker.isTracked( n ) ]

return result

def __buildMenu( self, result, path, currentEditScope ) :

result = IECore.MenuDefinition()
Expand Down Expand Up @@ -269,6 +340,8 @@ def __buildMenu( self, result, path, currentEditScope ) :
"label" : itemName,
"checkBox" : editScope == currentEditScope,
"icon" : self.__editScopeSwatch( editScope ),
"active" : not self.__unusableReason( editScope ),
"description" : self.__unusableReason( editScope ),
}
)
else :
Expand All @@ -290,16 +363,10 @@ def __menuDefinition( self ) :
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 ) :
upstream.insert( 0, node )
activeEditScopes = self.__activeEditScopes()

downstream = Gaffer.NodeAlgo.findAllDownstream( node, self.__editScopePredicate )
else :
upstream = []
downstream = []
node = self.__inputNode()
downstream = Gaffer.NodeAlgo.findAllDownstream( node, self.__editScopePredicate ) if node is not None else []

# Each child of the root will get its own section in the menu
# if it has children. The section will be preceded by a divider
Expand All @@ -321,13 +388,11 @@ def addToMenuHierarchy( editScope, root ) :
currentNode = currentNode.setdefault( n.getName(), {} )
currentNode[editScope.getName()] = editScope

if upstream :
for editScope in sorted( upstream, key = lambda e : e.relativeName( e.scriptNode() ) ) :
addToMenuHierarchy( editScope, "Upstream" )
for editScope in reversed( activeEditScopes ) :
addToMenuHierarchy( editScope, "Upstream" )

if downstream :
for editScope in sorted( downstream, key = lambda e : e.relativeName( e.scriptNode() ) ) :
addToMenuHierarchy( editScope, "Downstream" )
for editScope in sorted( downstream, key = lambda e : e.relativeName( e.scriptNode() ) ) :
addToMenuHierarchy( editScope, "Downstream" )

menuPath = Gaffer.DictPath( menuHierarchy, "/" )

Expand Down Expand Up @@ -447,32 +512,37 @@ def __dragLeave( self, widget, event ) :

def __drop( self, widget, event ) :

inputNode = self.__inputNode()
dropNode = self.__dropNode( event )
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( "<h4>The Edit Scope cannot be set while nothing is viewed</h4>" )
self.__popup.popup( parent = self )
elif dropNode :
upstream = Gaffer.NodeAlgo.findAllUpstream( inputNode, self.__editScopePredicate )
if self.__editScopePredicate( inputNode ) :
upstream.insert( 0, inputNode )

if dropNode in upstream :
if dropNode is not None :

reason = self.__unusableReason( dropNode )
if reason is None :
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( "<h4>{} cannot be used as it is not upstream of {}</h4>".format( dropNode.getName(), inputNode.getName() ) )
GafferUI.Label( f"<h4>{reason}</h4>" )
self.__popup.popup( parent = self )

self.__frame.setHighlighted( False )

return True

def __unusableReason( self, editScope ) :

name = editScope.relativeName( editScope.scriptNode() )
inputNode = self.__inputNode()
if inputNode is None :
return f"{name} cannot be used while nothing is viewed."
elif not self.__contextTracker.isTracked( editScope ) :
inputNodeName = inputNode.relativeName( inputNode.scriptNode() )
return f"{name} cannot be used as it is not upstream of {inputNodeName}."
elif not self.__contextTracker.isEnabled( editScope ) :
return f"{name} cannot be used as it is disabled."
else :
return None

# ProcessorWidget
# ===============

Expand Down

0 comments on commit 608216c

Please sign in to comment.