Skip to content

Commit

Permalink
BackgroundTask/Editor : Coordinate to find cancellation subject
Browse files Browse the repository at this point in the history
This hack allows us to find the cancellation subject for the internal plugs of the new ImageInspector. Although it's a tiny bit hacky, I'm reasonably content with it because it's pretty isolated and the new "Use Editor.Settings to store your settings" guidance moves us closer to where we want to be with regard to Editors being Nodes eventually. I suspect that when we get there we'll be able to replace it with a single cleaner mechanism for BackgroundTask to recover the ScriptNode from a UI element.
  • Loading branch information
johnhaddon committed Jan 4, 2024
1 parent 225c0db commit 830de76
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 29 deletions.
1 change: 1 addition & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ API
- Added `shuffleWithExtraSources()` method.
- ShufflePlugValueWidget : Widgets for the `source` and `destination` plugs can now be customised using standard `plugValueWidget:type` metadata.
- ImageTestCase : in `assertImageEqual` function, maxDifference may now be a tuple, to specify an asymmetric range.
- Editor : Added `Settings` class, which should be used to store settings for subclasses. See LightEditor and ImageInspector for examples.

Breaking Changes
----------------
Expand Down
14 changes: 6 additions & 8 deletions python/GafferImageUI/ImageInspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ class ImageInspector( GafferUI.NodeSetEditor ) :
# for the ImageInspector.
## \todo Eventually we want `GafferUI.Editor` to derive from Node itself,
# in which case we wouldn't need a separate settings object.
class Settings( Gaffer.Node ) :
class Settings( GafferUI.Editor.Settings ) :

def __init__( self ) :
def __init__( self, script ) :

Gaffer.Node.__init__( self, "ImageInspectorSettings" )
GafferUI.Editor.Settings.__init__( self, "ImageInspectorSettings", script )

self["in"] = GafferImage.ImagePlug()
self["view"] = Gaffer.StringPlug( defaultValue = "default" )
Expand Down Expand Up @@ -85,15 +85,15 @@ def __init__( self ) :
self["__sampleStats"]["in"].setInput( self["__sampleCounts"]["out"] )
self["__sampleStats"]["area"].setInput( self["__formatQuery"]["format"]["displayWindow"] )

IECore.registerRunTimeTyped( Settings, typeName = "ImageInspector::Settings" )
IECore.registerRunTimeTyped( Settings, typeName = "GafferImageUI::ImageInspector::Settings" )

def __init__( self, scriptNode, **kw ) :

column = GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Vertical, borderWidth = 4, spacing = 4 )

GafferUI.NodeSetEditor.__init__( self, column, scriptNode, nodeSet = scriptNode.focusSet(), **kw )

self.__settingsNode = self.Settings()
self.__settingsNode = self.Settings( scriptNode )
Gaffer.NodeAlgo.applyUserDefaults( self.__settingsNode )

with column :
Expand Down Expand Up @@ -262,9 +262,7 @@ def copy( self ) :

def cancellationSubject( self ) :

## \todo We really just want to return `self._image` here, but we
# can't because BackgroundTask can't find the right ScriptNode from it.
return self.__scriptNode["fileName"]
return self._image

def _children( self, canceller ) :

Expand Down
25 changes: 7 additions & 18 deletions python/GafferSceneUI/LightEditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,37 +60,26 @@
# with HierarchyView.
class LightEditor( GafferUI.NodeSetEditor ) :

# We store our settings as plugs on a node for a few reasons :
#
# - We want to use an EditScopePlugValueWidget, and that requires it.
# - We get a bunch of useful widgets and signals for free.
# - Longer term we want to refactor all Editors to derive from Node,
# in the same way that View does already. This will let us serialise
# _all_ layout state in the same format we serialise node graphs in.
# - The `userDefault` metadata provides a convenient way of configuring
# defaults.
# - The PlugLayout we use to display the settings allows users to add
# their own widgets to the UI.
class Settings( Gaffer.Node ) :

def __init__( self ) :

Gaffer.Node.__init__( self, "LightEditorSettings" )
class Settings( GafferUI.Editor.Settings ) :

def __init__( self, script ) :

GafferUI.Editor.Settings.__init__( self, "LightEditorSettings", script )

self["in"] = GafferScene.ScenePlug()
self["attribute"] = Gaffer.StringPlug( defaultValue = "light" )
self["section"] = Gaffer.StringPlug( defaultValue = "" )
self["editScope"] = Gaffer.Plug()

IECore.registerRunTimeTyped( Settings, typeName = "LightEditor::Settings" )
IECore.registerRunTimeTyped( Settings, typeName = "GafferSceneUI::LightEditor::Settings" )

def __init__( self, scriptNode, **kw ) :

column = GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Vertical, borderWidth = 4, spacing = 4 )

GafferUI.NodeSetEditor.__init__( self, column, scriptNode, nodeSet = scriptNode.focusSet(), **kw )

self.__settingsNode = self.Settings()
self.__settingsNode = self.Settings( scriptNode )
Gaffer.NodeAlgo.applyUserDefaults( self.__settingsNode )

self.__setFilter = _GafferSceneUI._HierarchyViewSetFilter()
Expand Down
28 changes: 28 additions & 0 deletions python/GafferUI/Editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,34 @@
# or its children. These make up the tabs in the UI layout.
class Editor( GafferUI.Widget ) :

## Base class used to store settings for an Editor. We store our settings
# as plugs on a node for a few reasons :
#
# - Some editors want to use an EditScopePlugValueWidget, and that requires
# it.
# - We get a bunch of useful widgets and signals for free.
# - Longer term we want to refactor all Editors to derive from Node, in the
# same way that View does already. This will let us serialise _all_ layout
# state in the same format we serialise node graphs in.
# - The `userDefault` metadata provides a convenient way of configuring
# defaults.
# - The PlugLayout we use to display the settings allows users to add their
# own widgets to the UI.
class Settings( Gaffer.Node ) :

def __init__( self, name, script ) :

Gaffer.Node.__init__( self, name )

# Hack to allow BackgroundTask to recover ScriptNode for
# cancellation support - see `BackgroundTask.cpp`.
## \todo Perhaps we can make this more natural at the point we derive
# Editor from Node?
self["__scriptNode"] = Gaffer.Plug( flags = Gaffer.Plug.Flags.Default & ~Gaffer.Plug.Flags.Serialisable )
self["__scriptNode"].setInput( script["fileName"] )

IECore.registerRunTimeTyped( Settings, typeName = "GafferUI::Editor::Settings" )

def __init__( self, topLevelWidget, scriptNode, **kw ) :

GafferUI.Widget.__init__( self, topLevelWidget, **kw )
Expand Down
13 changes: 10 additions & 3 deletions src/Gaffer/BackgroundTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ const ScriptNode *scriptNode( const GraphComponent *subject )
}
}

// The `GafferUI::View` classes house internal nodes which might not be
// directly connected to the graph. This hack recovers the ScriptNode from
// the node the view is currently connected to.
// The `GafferUI::View` and `GafferUI.Editor` classes house internal nodes
// which might not be directly connected to the graph. This hack recovers
// the ScriptNode from such classes.
while( subject )
{
if( subject->isInstanceOf( "GafferUI::View" ) )
Expand All @@ -102,6 +102,13 @@ const ScriptNode *scriptNode( const GraphComponent *subject )
return scriptNode( inPlug->getInput() );
}
}
else if( subject->isInstanceOf( "GafferUI::Editor::Settings" ) )
{
if( auto scriptPlug = subject->getChild<Plug>( "__scriptNode" ) )
{
return scriptNode( scriptPlug->getInput() );
}
}
subject = subject->parent();
}
return nullptr;
Expand Down

0 comments on commit 830de76

Please sign in to comment.