From 0f2b6dd72b96843e5a573121a45f1d399abccebd Mon Sep 17 00:00:00 2001 From: John Haddon Date: Fri, 13 Sep 2024 12:52:53 +0100 Subject: [PATCH] ScenePathPlugValueWidget : Improve search for ScenePlug We now accept a space-separated list of names of potential ScenePlugs, and use the first one with an input connection. We declare the ScenePlugs as auxiliary plugs, using the PlugValueWidget's existing mechanism for triggering an update when their connections change. This allows us to fix the UI for the Constraint nodes to show locations from the `targetScene` if it has a connection. --- Changes.md | 8 ++ python/GafferSceneUI/ConstraintUI.py | 1 + python/GafferSceneUI/FramingConstraintUI.py | 1 + .../GafferSceneUI/ScenePathPlugValueWidget.py | 93 +++++++++++++------ 4 files changed, 76 insertions(+), 27 deletions(-) diff --git a/Changes.md b/Changes.md index 1d7f6cf9099..cb5d0e40d44 100644 --- a/Changes.md +++ b/Changes.md @@ -1,7 +1,15 @@ 1.4.x.x (relative to 1.4.12.0) ======= +Fixes +----- + +- Constraint : The `target` browser now shows locations from the `targetScene` if it has an input connection. Before it always showed locations from the main input. + +API +--- +- ScenePathPlugValueWidget : The `scenePathPlugValueWidget:scene` metadata now accepts a space-separated list of plugs, taking the first plug which has an input connection. 1.4.12.0 (relative to 1.4.11.0) ======== diff --git a/python/GafferSceneUI/ConstraintUI.py b/python/GafferSceneUI/ConstraintUI.py index d3460f6579a..4686e20ada2 100644 --- a/python/GafferSceneUI/ConstraintUI.py +++ b/python/GafferSceneUI/ConstraintUI.py @@ -82,6 +82,7 @@ """, "plugValueWidget:type", "GafferSceneUI.ScenePathPlugValueWidget", + "scenePathPlugValueWidget:scene", "targetScene in", ], diff --git a/python/GafferSceneUI/FramingConstraintUI.py b/python/GafferSceneUI/FramingConstraintUI.py index 77949af95f1..9c277c87fc9 100644 --- a/python/GafferSceneUI/FramingConstraintUI.py +++ b/python/GafferSceneUI/FramingConstraintUI.py @@ -75,6 +75,7 @@ """, "plugValueWidget:type", "GafferSceneUI.ScenePathPlugValueWidget", + "scenePathPlugValueWidget:scene", "targetScene in", ], diff --git a/python/GafferSceneUI/ScenePathPlugValueWidget.py b/python/GafferSceneUI/ScenePathPlugValueWidget.py index cf1cf2be64a..02050b61579 100644 --- a/python/GafferSceneUI/ScenePathPlugValueWidget.py +++ b/python/GafferSceneUI/ScenePathPlugValueWidget.py @@ -39,6 +39,15 @@ import GafferImage import GafferScene +# Supported metadata : +# +# - `scenePathPlugValueWidget:scene` : The name of a plug on the same node, used to +# provide a scene browser for path selection. Also accepts a space-separated list +# of names, taking the first plug with an input connection. +# - `scenePathPlugValueWidget:setNames` : Limits the scene browser to include only +# locations in the specified sets. +# - `scenePathPlugValueWidget:setsLabel` : A UI label for turning on and off the +# set filter. class ScenePathPlugValueWidget( GafferUI.PathPlugValueWidget ) : def __init__( self, plug, path = None, **kw ) : @@ -54,7 +63,7 @@ def __init__( self, plug, path = None, **kw ) : ) path = GafferScene.ScenePath( - self.__scenePlug( plug ), + None, plug.node().scriptNode().context(), "/", filter = filter @@ -62,7 +71,59 @@ def __init__( self, plug, path = None, **kw ) : GafferUI.PathPlugValueWidget.__init__( self, plug, path, **kw ) - plug.ancestor( Gaffer.ScriptNode ).focusChangedSignal().connect( Gaffer.WeakMethod( self.__focusChanged ), scoped = False ) + def _auxiliaryPlugs( self, plug ) : + + scenePlugNames = Gaffer.Metadata.value( plug, "scenePathPlugValueWidget:scene" ) or "in" + return [ + plug.node().descendant( n ) + for n in scenePlugNames.split() + if isinstance( plug.node().descendant( n ), GafferScene.ScenePlug ) + ] + + @staticmethod + def _valuesForUpdate( plugs, auxiliaryPlugs ) : + + values = GafferUI.PathPlugValueWidget._valuesForUpdate( plugs, auxiliaryPlugs ) + + result = [] + for value, scenePlugs in zip( values, auxiliaryPlugs ) : + + # Find the first ScenePlug with an input, falling back to the last + # one in the list. + scenePlug = None + for scenePlug in scenePlugs : + if scenePlug.getInput() : + break + + result.append( { + "value" : value, + "scenePlug" : scenePlug, + } ) + + return result + + def _updateFromValues( self, values, exception ) : + + GafferUI.PathPlugValueWidget._updateFromValues( + self, [ v["value"] for v in values ], exception + ) + + scenePlug = next( ( v["scenePlug"] for v in values if v["scenePlug"] is not None ), None ) + if scenePlug is not None : + self.path().setScene( scenePlug ) + self.__focusChangedConnection = None + else : + # The `_auxiliaryPlugs()` search doesn't work well for ShaderNodes, + # since they don't have ScenePlug inputs. We _could_ traverse outputs + # from the shader looking for a ShaderAssignment node to get a ScenePlug + # from. But this wouldn't be useful if the scene hierarchy was + # manipulated downstream of the ShaderAssignment as shaders need the + # final paths as seen by the renderer. So instead we use the focus node, as + # it is more likely to be pointed at the final render node. + self.path().setScene( self.__scenePlugFromFocus() ) + self.__focusChangedConnection = self.getPlug().ancestor( Gaffer.ScriptNode ).focusChangedSignal().connect( + Gaffer.WeakMethod( self.__focusChanged ), scoped = True + ) def _pathChooserDialogue( self ) : @@ -86,31 +147,9 @@ def _pathChooserDialogue( self ) : return dialogue - def __scenePlug( self, plug ) : - - # Search for a suitable ScenePlug input on the same node as this plug, - # or on the node of another plug being driven by this plug. - - def predicate( plug ) : - - scenePlugName = Gaffer.Metadata.value( plug, "scenePathPlugValueWidget:scene" ) or "in" - scenePlug = plug.node().descendant( scenePlugName ) - if scenePlug and isinstance( scenePlug, GafferScene.ScenePlug ) : - return scenePlug - - scenePlug = Gaffer.PlugAlgo.findDestination( plug, predicate ) - if scenePlug is not None : - return scenePlug - - # The above doesn't work well for ShaderNodes, since they don't have - # ScenePlug inputs. We _could_ traverse outputs from the shader looking - # for a ShaderAssignment node to get a ScenePlug from. But this wouldn't - # be useful if the scene hierarchy was manipulated downstream of the - # ShaderAssignment as shaders need the final paths as seen by the - # renderer. So instead use the focus node, as it is more likely to - # be pointed at the final render node. + def __scenePlugFromFocus( self ) : - focusNode = plug.ancestor( Gaffer.ScriptNode ).getFocus() + focusNode = self.getPlug().ancestor( Gaffer.ScriptNode ).getFocus() if focusNode is not None : outputScene = next( GafferScene.ScenePlug.RecursiveOutputRange( focusNode ), None ) if outputScene is not None : @@ -121,6 +160,6 @@ def predicate( plug ) : def __focusChanged( self, scriptNode, node ) : - scenePlug = self.__scenePlug( self.getPlug() ) + scenePlug = self.__scenePlugFromFocus() if scenePlug is not None : self.path().setScene( scenePlug )