From 45fd2ef940703efafc0c58432c204c135a05bb9f Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Fri, 10 Jan 2025 08:41:05 -0800 Subject: [PATCH 1/6] PathFilterUI : Allow Select Affected Objects on promoted spreadsheets --- Changes.md | 1 + python/GafferSceneUI/PathFilterUI.py | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Changes.md b/Changes.md index 4356c8f2c60..0394e22bbf3 100644 --- a/Changes.md +++ b/Changes.md @@ -7,6 +7,7 @@ Fixes - USDLayerWriter : - Fixed silent failures when unable to create the output file (#6197). - Fixed leak of `usdLayerWriter:fileName` context variable. +- PathFilter : Fixed bug preventing display of "Select Affected Objects" menu item in the row name column of promoted Spreadsheets. 1.4.15.3 (relative to 1.4.15.2) ======== diff --git a/python/GafferSceneUI/PathFilterUI.py b/python/GafferSceneUI/PathFilterUI.py index 5811eddde44..fa65434304b 100644 --- a/python/GafferSceneUI/PathFilterUI.py +++ b/python/GafferSceneUI/PathFilterUI.py @@ -199,11 +199,16 @@ def __popupMenu( menuDefinition, plugValueWidget ) : if plug is None: return - node = plug.node() + rowPlug = plug.ancestor( Gaffer.Spreadsheet.RowPlug ) + if rowPlug is None : + return - if isinstance( node, Gaffer.Spreadsheet ) : - rowPlug = plug.ancestor( Gaffer.Spreadsheet.RowPlug ) + spreadsheet = Gaffer.PlugAlgo.findDestination( + rowPlug, + lambda plug : plug.node() if isinstance( plug.node(), Gaffer.Spreadsheet ) else None + ) + if spreadsheet is not None : with plugValueWidget.getContext() : if __targetFilterPlug( plug ) is not None : cellPlug = plug.ancestor( Gaffer.Spreadsheet.CellPlug ) @@ -212,7 +217,7 @@ def __popupMenu( menuDefinition, plugValueWidget ) : selection = IECore.PathMatcher( plugValueWidget.vectorDataWidget().getData()[0] ) - elif rowPlug and plug == rowPlug["name"] and node["selector"].getValue() == "${scene:path}" : + elif rowPlug and plug == rowPlug["name"] and spreadsheet["selector"].getValue() == "${scene:path}" : selection = IECore.PathMatcher( [ plugValueWidget.getPlug().getValue() ] ) if selection is None : From 889c6e6139bcaff5e33673b9f4e4d1ff36c50eeb Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Mon, 13 Jan 2025 15:51:01 -0800 Subject: [PATCH 2/6] PathFilterUI : Find PathFilters with connections from `enabledRowNames` We also use this as an excuse to simplify `_selectAffected()` and consolidate the spreadsheet handling in `__popupMenu()`. --- Changes.md | 4 +- python/GafferSceneUI/PathFilterUI.py | 69 ++++++++++++++-------------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/Changes.md b/Changes.md index 0394e22bbf3..f777729b724 100644 --- a/Changes.md +++ b/Changes.md @@ -7,7 +7,9 @@ Fixes - USDLayerWriter : - Fixed silent failures when unable to create the output file (#6197). - Fixed leak of `usdLayerWriter:fileName` context variable. -- PathFilter : Fixed bug preventing display of "Select Affected Objects" menu item in the row name column of promoted Spreadsheets. +- PathFilter : + - Fixed bug preventing display of "Select Affected Objects" menu item in the row name column of promoted Spreadsheets. + - Fixed bug preventing use of "Select Affected Objects" menu item in the row name column of Spreadsheets with `enabledRowNames` connected to the `paths` plug of a PathFilter. 1.4.15.3 (relative to 1.4.15.2) ======== diff --git a/python/GafferSceneUI/PathFilterUI.py b/python/GafferSceneUI/PathFilterUI.py index fa65434304b..151370a111e 100644 --- a/python/GafferSceneUI/PathFilterUI.py +++ b/python/GafferSceneUI/PathFilterUI.py @@ -132,34 +132,22 @@ def __targetFilterPlug( plug ) : def _selectAffected( plug, selection ) : - inPlugs = [] - - rowPlug = plug.ancestor( Gaffer.Spreadsheet.RowPlug ) - targetPlug = __targetFilterPlug( plug ) if targetPlug is not None : - inPlugs = [ n["in"] for n in GafferScene.SceneAlgo.filteredNodes( targetPlug.node() ) ] - elif rowPlug is not None : - for output in rowPlug.node()["out"] : - targetPlug = Gaffer.PlugAlgo.findDestination( - output, - lambda plug : plug.node()["out"] if isinstance( plug.node(), GafferScene.SceneNode ) else None - ) - if targetPlug is not None : - inPlugs = [ targetPlug ] - break - - if targetPlug is None : + scenePlugs = [ n["in"] for n in GafferScene.SceneAlgo.filteredNodes( targetPlug.node() ) ] + elif isinstance( plug, GafferScene.ScenePlug ) : + scenePlugs = [ plug ] + else : return - scenes = [ s[0] if isinstance( s, Gaffer.ArrayPlug ) else s for s in inPlugs ] - result = IECore.PathMatcher() - context = targetPlug.ancestor( Gaffer.ScriptNode ).context() + context = plug.ancestor( Gaffer.ScriptNode ).context() with context : - for scene in scenes : - GafferScene.SceneAlgo.matchingPaths( selection, scene, result ) + for plug in scenePlugs : + GafferScene.SceneAlgo.matchingPaths( + selection, plug[0] if isinstance( plug, Gaffer.ArrayPlug ) else plug, result + ) GafferSceneUI.ContextAlgo.setSelectedPaths( context, result ) @@ -207,27 +195,38 @@ def __popupMenu( menuDefinition, plugValueWidget ) : rowPlug, lambda plug : plug.node() if isinstance( plug.node(), Gaffer.Spreadsheet ) else None ) + if spreadsheet is None : + return - if spreadsheet is not None : - with plugValueWidget.getContext() : - if __targetFilterPlug( plug ) is not None : - cellPlug = plug.ancestor( Gaffer.Spreadsheet.CellPlug ) - if cellPlug is None : - return - - selection = IECore.PathMatcher( plugValueWidget.vectorDataWidget().getData()[0] ) - - elif rowPlug and plug == rowPlug["name"] and spreadsheet["selector"].getValue() == "${scene:path}" : - selection = IECore.PathMatcher( [ plugValueWidget.getPlug().getValue() ] ) - - if selection is None : + with plugValueWidget.getContext() : + targetPlug = __targetFilterPlug( plug ) + if targetPlug is not None : + cellPlug = plug.ancestor( Gaffer.Spreadsheet.CellPlug ) + if cellPlug is None : + return + + selection = IECore.PathMatcher( plugValueWidget.vectorDataWidget().getData()[0] ) + + elif plug == rowPlug["name"] and spreadsheet["selector"].getValue() == "${scene:path}" : + selection = IECore.PathMatcher( [ plugValueWidget.getPlug().getValue() ] ) + targetPlug = __targetFilterPlug( spreadsheet["enabledRowNames"] ) + if targetPlug is None : + for output in spreadsheet["out"] : + targetPlug = Gaffer.PlugAlgo.findDestination( + output, + lambda plug : plug.node()["out"] if isinstance( plug.node(), GafferScene.SceneNode ) else None + ) + if targetPlug is not None : + break + + if selection is None or targetPlug is None : return menuDefinition.prepend( "/selectAffectedDivider", { "divider" : True } ) menuDefinition.prepend( "/Select Affected Objects", { - "command" : functools.partial( _selectAffected, plug, selection ) + "command" : functools.partial( _selectAffected, targetPlug, selection ) } ) From f4259cfbe3411290baae5ca33500319f8a7d1bc7 Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Tue, 14 Jan 2025 09:08:23 -0800 Subject: [PATCH 3/6] PathFilterUI : Fix Select Affected Objects on `PathFilter.paths` cells This appears to have been broken as far back as 1.2.0.0. File "/opt/gaffer-1.2.0.0-linux/python/GafferSceneUI/PathFilterUI.py", line 213, in __popupMenu selection = IECore.PathMatcher( plugValueWidget.vectorDataWidget().getData()[0] ) IndexError: list index out of range --- Changes.md | 1 + python/GafferSceneUI/PathFilterUI.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Changes.md b/Changes.md index f777729b724..fce172ff5f6 100644 --- a/Changes.md +++ b/Changes.md @@ -10,6 +10,7 @@ Fixes - PathFilter : - Fixed bug preventing display of "Select Affected Objects" menu item in the row name column of promoted Spreadsheets. - Fixed bug preventing use of "Select Affected Objects" menu item in the row name column of Spreadsheets with `enabledRowNames` connected to the `paths` plug of a PathFilter. + - Fixed error when using "Select Affected Objects" on Spreadsheet cells connected to the `paths` plug of a PathFilter. 1.4.15.3 (relative to 1.4.15.2) ======== diff --git a/python/GafferSceneUI/PathFilterUI.py b/python/GafferSceneUI/PathFilterUI.py index 151370a111e..48d9c48bfa9 100644 --- a/python/GafferSceneUI/PathFilterUI.py +++ b/python/GafferSceneUI/PathFilterUI.py @@ -205,7 +205,7 @@ def __popupMenu( menuDefinition, plugValueWidget ) : if cellPlug is None : return - selection = IECore.PathMatcher( plugValueWidget.vectorDataWidget().getData()[0] ) + selection = IECore.PathMatcher( plug.getValue() ) elif plug == rowPlug["name"] and spreadsheet["selector"].getValue() == "${scene:path}" : selection = IECore.PathMatcher( [ plugValueWidget.getPlug().getValue() ] ) From 9ec719fd7e1aa1caebb2b04b18418539b42743a8 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Thu, 16 Jan 2025 15:09:15 +0000 Subject: [PATCH 4/6] PathFilterUI : Refactor `_selectedAffected()` This was confusing for several reasons : - Several callers passed `targetPlug` to the `plug` argument, but then internally `targetPlug` was acquired again separately. - The `plug` argument could be either a StringVectorDataPlug used to find a PathFilter, or a ScenePlug, but this wasn't documented. Now, `_selectedAffected()` is much dumber, and just takes a PathMatcher and a list of ScenePlugs, and selects the matching paths. All logic about where the scenes and PathMatchers come from is now on the client side. --- python/GafferSceneUI/PathFilterUI.py | 60 +++++++++++++++------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/python/GafferSceneUI/PathFilterUI.py b/python/GafferSceneUI/PathFilterUI.py index 48d9c48bfa9..51ad305a4f3 100644 --- a/python/GafferSceneUI/PathFilterUI.py +++ b/python/GafferSceneUI/PathFilterUI.py @@ -123,33 +123,34 @@ # VectorDataPlugValueWidget customisation ########################################################################### -def __targetFilterPlug( plug ) : +def _targetFilterPlug( plug ) : return Gaffer.PlugAlgo.findDestination( plug, lambda plug : plug if isinstance( plug.parent(), GafferScene.PathFilter ) and plug.getName() == "paths" else None ) -def _selectAffected( plug, selection ) : +def _filteredScenes( filter ) : - targetPlug = __targetFilterPlug( plug ) + result = set() + for node in GafferScene.SceneAlgo.filteredNodes( filter ) : + if isinstance( node["in"], Gaffer.ArrayPlug ) : + result.add( node["in"][0] ) + else : + result.add( node["in"] ) - if targetPlug is not None : - scenePlugs = [ n["in"] for n in GafferScene.SceneAlgo.filteredNodes( targetPlug.node() ) ] - elif isinstance( plug, GafferScene.ScenePlug ) : - scenePlugs = [ plug ] - else : - return + return list( result ) + +def _selectAffected( pathMatcher, scenes ) : result = IECore.PathMatcher() - context = plug.ancestor( Gaffer.ScriptNode ).context() - with context : - for plug in scenePlugs : + for scene in scenes : + with scene.ancestor( Gaffer.ScriptNode ).context() : GafferScene.SceneAlgo.matchingPaths( - selection, plug[0] if isinstance( plug, Gaffer.ArrayPlug ) else plug, result + pathMatcher, scene, result ) - GafferSceneUI.ContextAlgo.setSelectedPaths( context, result ) + GafferSceneUI.ScriptNodeAlgo.setSelectedPaths( scenes[0].ancestor( Gaffer.ScriptNode ), result ) class _PathsPlugValueWidget( GafferUI.VectorDataPlugValueWidget ) : @@ -164,13 +165,13 @@ def __dataMenu( self, vectorDataWidget, menuDefinition ) : selectedIndices = vectorDataWidget.selectedIndices() filterData = vectorDataWidget.getData()[0] - selection = IECore.PathMatcher( [ filterData[row] for column, row in selectedIndices ] ) + pathMatcher = IECore.PathMatcher( [ filterData[row] for column, row in selectedIndices ] ) menuDefinition.append( "/selectDivider", { "divider" : True } ) menuDefinition.append( "/Select Affected Objects", { - "command" : functools.partial( _selectAffected, self.getPlug(), selection ), + "command" : functools.partial( _selectAffected, pathMatcher, _filteredScenes( _targetFilterPlug( self.getPlug() ).node() ) ), "active" : len( selectedIndices ) > 0, } ) @@ -181,8 +182,6 @@ def __dataMenu( self, vectorDataWidget, menuDefinition ) : def __popupMenu( menuDefinition, plugValueWidget ) : - selection = None - plug = plugValueWidget.getPlug() if plug is None: return @@ -198,35 +197,40 @@ def __popupMenu( menuDefinition, plugValueWidget ) : if spreadsheet is None : return + scenes = None + pathMatcher = None with plugValueWidget.getContext() : - targetPlug = __targetFilterPlug( plug ) + targetPlug = _targetFilterPlug( plug ) if targetPlug is not None : cellPlug = plug.ancestor( Gaffer.Spreadsheet.CellPlug ) if cellPlug is None : return - selection = IECore.PathMatcher( plug.getValue() ) + pathMatcher = IECore.PathMatcher( plug.getValue() ) + scenes = _filteredScenes( targetPlug.node() ) elif plug == rowPlug["name"] and spreadsheet["selector"].getValue() == "${scene:path}" : - selection = IECore.PathMatcher( [ plugValueWidget.getPlug().getValue() ] ) - targetPlug = __targetFilterPlug( spreadsheet["enabledRowNames"] ) - if targetPlug is None : + pathMatcher = IECore.PathMatcher( [ plugValueWidget.getPlug().getValue() ] ) + targetPlug = _targetFilterPlug( spreadsheet["enabledRowNames"] ) + if targetPlug is not None : + scenes = _filteredScenes( targetPlug.node() ) + else : for output in spreadsheet["out"] : - targetPlug = Gaffer.PlugAlgo.findDestination( + scene = Gaffer.PlugAlgo.findDestination( output, lambda plug : plug.node()["out"] if isinstance( plug.node(), GafferScene.SceneNode ) else None ) - if targetPlug is not None : - break + if scene is not None : + scenes = [ scene ] - if selection is None or targetPlug is None : + if pathMatcher is None or scenes is None : return menuDefinition.prepend( "/selectAffectedDivider", { "divider" : True } ) menuDefinition.prepend( "/Select Affected Objects", { - "command" : functools.partial( _selectAffected, targetPlug, selection ) + "command" : functools.partial( _selectAffected, pathMatcher, scenes ) } ) From fb4a83f53a5bee4686cb2f0f1e67ea87195a33ba Mon Sep 17 00:00:00 2001 From: John Haddon Date: Thu, 16 Jan 2025 15:23:19 +0000 Subject: [PATCH 5/6] PathFilterUI : Replace `_targetFilterPlug()` Since the refactoring of `_selectedAffected()` we are no longer using the plug, and only care about finding a PathFilter. So we replace `_targetFilterPlug()` with a more descriptive `_destinationPathFilter()`. This removes the last use of the could-mean-anything `targetPlug` variables. --- python/GafferSceneUI/PathFilterUI.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/python/GafferSceneUI/PathFilterUI.py b/python/GafferSceneUI/PathFilterUI.py index 51ad305a4f3..1866a88468e 100644 --- a/python/GafferSceneUI/PathFilterUI.py +++ b/python/GafferSceneUI/PathFilterUI.py @@ -123,11 +123,13 @@ # VectorDataPlugValueWidget customisation ########################################################################### -def _targetFilterPlug( plug ) : +# Searches for a PathFilter whose `paths` plug is eventually driven by `plug`, +# and returns it. +def _destinationPathFilter( plug ) : return Gaffer.PlugAlgo.findDestination( plug, - lambda plug : plug if isinstance( plug.parent(), GafferScene.PathFilter ) and plug.getName() == "paths" else None + lambda plug : plug.parent() if isinstance( plug.parent(), GafferScene.PathFilter ) and plug.getName() == "paths" else None ) def _filteredScenes( filter ) : @@ -171,7 +173,7 @@ def __dataMenu( self, vectorDataWidget, menuDefinition ) : menuDefinition.append( "/Select Affected Objects", { - "command" : functools.partial( _selectAffected, pathMatcher, _filteredScenes( _targetFilterPlug( self.getPlug() ).node() ) ), + "command" : functools.partial( _selectAffected, pathMatcher, _filteredScenes( _destinationPathFilter( self.getPlug() ) ) ), "active" : len( selectedIndices ) > 0, } ) @@ -200,20 +202,15 @@ def __popupMenu( menuDefinition, plugValueWidget ) : scenes = None pathMatcher = None with plugValueWidget.getContext() : - targetPlug = _targetFilterPlug( plug ) - if targetPlug is not None : - cellPlug = plug.ancestor( Gaffer.Spreadsheet.CellPlug ) - if cellPlug is None : - return - + pathFilter = _destinationPathFilter( plug ) + if pathFilter is not None : pathMatcher = IECore.PathMatcher( plug.getValue() ) - scenes = _filteredScenes( targetPlug.node() ) - + scenes = _filteredScenes( pathFilter ) elif plug == rowPlug["name"] and spreadsheet["selector"].getValue() == "${scene:path}" : - pathMatcher = IECore.PathMatcher( [ plugValueWidget.getPlug().getValue() ] ) - targetPlug = _targetFilterPlug( spreadsheet["enabledRowNames"] ) - if targetPlug is not None : - scenes = _filteredScenes( targetPlug.node() ) + pathMatcher = IECore.PathMatcher( [ plug.getValue() ] ) + pathFilter = _destinationPathFilter( spreadsheet["enabledRowNames"] ) + if pathFilter is not None : + scenes = _filteredScenes( pathFilter ) else : for output in spreadsheet["out"] : scene = Gaffer.PlugAlgo.findDestination( From fc1ee4dd170272012f9892396e32f46bcd8c734b Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:02:43 -0800 Subject: [PATCH 6/6] PathFilterUI : Fix Select Affected Objects errors on disconnected nodes --- python/GafferSceneUI/PathFilterUI.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/python/GafferSceneUI/PathFilterUI.py b/python/GafferSceneUI/PathFilterUI.py index 1866a88468e..ea61ae0a649 100644 --- a/python/GafferSceneUI/PathFilterUI.py +++ b/python/GafferSceneUI/PathFilterUI.py @@ -168,13 +168,14 @@ def __dataMenu( self, vectorDataWidget, menuDefinition ) : filterData = vectorDataWidget.getData()[0] pathMatcher = IECore.PathMatcher( [ filterData[row] for column, row in selectedIndices ] ) + scenes = _filteredScenes( _destinationPathFilter( self.getPlug() ) ) menuDefinition.append( "/selectDivider", { "divider" : True } ) menuDefinition.append( "/Select Affected Objects", { - "command" : functools.partial( _selectAffected, pathMatcher, _filteredScenes( _destinationPathFilter( self.getPlug() ) ) ), - "active" : len( selectedIndices ) > 0, + "command" : functools.partial( _selectAffected, pathMatcher, scenes ), + "active" : len( selectedIndices ) > 0 and len( scenes ) > 0, } ) @@ -199,7 +200,7 @@ def __popupMenu( menuDefinition, plugValueWidget ) : if spreadsheet is None : return - scenes = None + scenes = [] pathMatcher = None with plugValueWidget.getContext() : pathFilter = _destinationPathFilter( plug ) @@ -220,7 +221,7 @@ def __popupMenu( menuDefinition, plugValueWidget ) : if scene is not None : scenes = [ scene ] - if pathMatcher is None or scenes is None : + if pathMatcher is None or len( scenes ) == 0 : return menuDefinition.prepend( "/selectAffectedDivider", { "divider" : True } )