Skip to content

Commit

Permalink
Merge pull request #6220 from murraystevenson/visibleSetBookmarkMenu
Browse files Browse the repository at this point in the history
VisibleSet Bookmarks
  • Loading branch information
murraystevenson authored Jan 22, 2025
2 parents 8b7c846 + f0cbcde commit aed2e50
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 4 deletions.
8 changes: 8 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
1.5.x.x (relative to 1.5.3.0)
=======

Features
--------

- HierarchyView : Added ability to store and recall the Visible Set in named bookmarks that are saved with the script.

Improvements
------------

- AttributeEditor : Added "Select Affected Objects" menu item to the "Linked Lights" and Arnold "Shadow Group" columns.
- ScriptNode : Added support for serialising metadata registered on a ScriptNode.

Fixes
-----
Expand All @@ -20,6 +26,8 @@ API

- RenderPassEditor : Added optional `index` argument to `registerOption()` and `registerColumn()`. This can be used to specify the column's position in the UI.
- Metadata : Added `targetsWithMetadata()` function, returning all the string targets which match a pattern and have a specific metadata key.
- VisibleSetData : Implemented `save()` and `load()`.
- ScriptNodeAlgo : Added functions for managing VisibleSet bookmarks.

1.5.3.0 (relative to 1.5.2.0)
=======
Expand Down
2 changes: 1 addition & 1 deletion include/GafferScene/VisibleSetData.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

namespace IECore
{
IECORE_DECLARE_TYPEDDATA( VisibleSetData, GafferScene::VisibleSet, void, IECore::SimpleDataHolder );
IECORE_DECLARE_TYPEDDATA( VisibleSetData, GafferScene::VisibleSet, void, IECore::SharedDataHolder );
} // namespace IECore

namespace GafferScene
Expand Down
16 changes: 16 additions & 0 deletions include/GafferSceneUI/ScriptNodeAlgo.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,22 @@ GAFFERSCENEUI_API void setCurrentRenderPass( Gaffer::ScriptNode *script, std::st
/// Returns the current render pass for the script.
GAFFERSCENEUI_API std::string getCurrentRenderPass( const Gaffer::ScriptNode *script );

/// Visible Set Bookmarks
/// =====================

/// Visible Set bookmarks can be used to store named bookmarks containing a VisibleSet as
/// metadata on the ScriptNode, allowing users to bookmark important VisibleSets to be
/// recalled later.

/// Stores a VisibleSet as a named bookmark for the script.
GAFFERSCENEUI_API void addVisibleSetBookmark( Gaffer::ScriptNode *script, const std::string &name, const GafferScene::VisibleSet &visibleSet, bool persistent = true );
/// Returns the VisibleSet previously bookmarked as `name`.
GAFFERSCENEUI_API GafferScene::VisibleSet getVisibleSetBookmark( const Gaffer::ScriptNode *script, const std::string &name );
/// Removes the bookmark previously stored as `name`.
GAFFERSCENEUI_API void removeVisibleSetBookmark( Gaffer::ScriptNode *script, const std::string &name );
/// Returns the names of all VisibleSet bookmarks stored on `script`.
GAFFERSCENEUI_API std::vector<std::string> visibleSetBookmarks( const Gaffer::ScriptNode *script );

} // namespace ScriptNodeAlgo

} // namespace GafferSceneUI
20 changes: 20 additions & 0 deletions python/GafferSceneTest/VisibleSetDataTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,26 @@ def test( self ) :
self.assertEqual( vd2c, vd2 )
self.assertEqual( vd2c.hash(), vd2.hash() )

def testSerialisation( self ) :

v = GafferScene.VisibleSet()
v.expansions = IECore.PathMatcher( [ "/a" ] )
v.inclusions = IECore.PathMatcher( [ "/b" ] )
v.exclusions = IECore.PathMatcher( [ "/c" ] )

d = GafferScene.VisibleSetData( v )

m = IECore.MemoryIndexedIO( IECore.CharVectorData(), [], IECore.IndexedIO.OpenMode.Write )

d.save( m, "f" )

m2 = IECore.MemoryIndexedIO( m.buffer(), [], IECore.IndexedIO.OpenMode.Read )
d2 = IECore.Object.load( m2, "f" )

self.assertEqual( d2, d )
self.assertEqual( d2.hash(), d.hash() )
self.assertEqual( d2.value, v )

def testStoreInContext( self ) :

v = GafferScene.VisibleSet()
Expand Down
107 changes: 107 additions & 0 deletions python/GafferSceneUI/HierarchyView.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ def __init__( self, scriptNode, **kw ) :

with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 ) :

_VisibleSetBookmarkWidget()
GafferUI.Divider( GafferUI.Divider.Orientation.Vertical )

_SearchFilterWidget( searchFilter )
_SetFilterWidget( setFilter )

Expand Down Expand Up @@ -356,3 +359,107 @@ def _updateFromPathFilter( self ) :
def __patternEditingFinished( self, widget ) :

self.pathFilter().setMatchPattern( self.__patternWidget.getText() )

##########################################################################
# _VisibleSetBookmarkWidget
##########################################################################

class _VisibleSetBookmarkWidget( GafferUI.Widget ) :

def __init__( self ) :

button = GafferUI.MenuButton(
image = "bookmarks.png",
hasFrame = False,
toolTip = "Visible Set Bookmarks",
menu = GafferUI.Menu( Gaffer.WeakMethod( self.__menuDefinition ), title = "Visible Set Bookmarks" )
)

GafferUI.Widget.__init__( self, button )

def __menuDefinition( self ) :

scriptNode = self.ancestor( GafferUI.Editor ).scriptNode()
readOnly = Gaffer.MetadataAlgo.readOnly( scriptNode )

menuDefinition = IECore.MenuDefinition()

bookmarks = sorted( GafferSceneUI.ScriptNodeAlgo.visibleSetBookmarks( scriptNode ) )
if bookmarks :
for b in bookmarks :
menuDefinition.append(
"/" + b,
{
"command" : functools.partial( Gaffer.WeakMethod( self.__restore ), b ),
"checkBox" : GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( scriptNode, b ) == GafferSceneUI.ScriptNodeAlgo.getVisibleSet( scriptNode ),
}
)
menuDefinition.append( "/RestoreBookmarkDivider", { "divider" : True } )

for b in bookmarks :
menuDefinition.append(
"/Save As/" + b,
{
"command" : functools.partial( Gaffer.WeakMethod( self.__saveAs ), b ),
"active" : not readOnly,
}
)
menuDefinition.append(
"/Delete/" + b,
{
"command" : functools.partial( Gaffer.WeakMethod( self.__delete ), b ),
"active" : not readOnly,
}
)
menuDefinition.append( "/Save As/Divider", { "divider" : True } )
else :
menuDefinition.append( "/No Bookmarks Available", { "active" : False } )
menuDefinition.append( "/NoBookmarksDivider", { "divider" : True } )

menuDefinition.append(
"/Save As/New Bookmark...",
{
"command" : functools.partial( Gaffer.WeakMethod( self.__save ) ),
"active" : not readOnly,
}
)

return menuDefinition

def __save( self, *unused ) :

d = GafferUI.TextInputDialogue( initialText = "", title = "Save Bookmark", confirmLabel = "Save" )
name = d.waitForText( parentWindow = self.ancestor( GafferUI.Window ) )

if not name :
return

if name in GafferSceneUI.ScriptNodeAlgo.visibleSetBookmarks( self.ancestor( GafferUI.Editor ).scriptNode() ) :
c = GafferUI.ConfirmationDialogue(
"Replace existing bookmark?",
"A bookmark named {} already exists. Do you want to replace it?".format( name ),
confirmLabel = "Replace"
)
if not c.waitForConfirmation( parentWindow = self.ancestor( GafferUI.Window ) ) :
return

self.__saveAs( name )

def __saveAs( self, name, *unused ) :

scriptNode = self.ancestor( GafferUI.Editor ).scriptNode()
visibleSet = GafferSceneUI.ScriptNodeAlgo.getVisibleSet( scriptNode )
with Gaffer.UndoScope( scriptNode ) :
GafferSceneUI.ScriptNodeAlgo.addVisibleSetBookmark( scriptNode, name, visibleSet )

def __delete( self, name, *unused ) :

scriptNode = self.ancestor( GafferUI.Editor ).scriptNode()
with Gaffer.UndoScope( scriptNode ) :
GafferSceneUI.ScriptNodeAlgo.removeVisibleSetBookmark( scriptNode, name )

def __restore( self, name, *unused ) :

scriptNode = self.ancestor( GafferUI.Editor ).scriptNode()
visibleSet = GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( scriptNode, name )
GafferSceneUI.ScriptNodeAlgo.setVisibleSet( scriptNode, visibleSet )
78 changes: 78 additions & 0 deletions python/GafferSceneUITest/ScriptNodeAlgoTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,5 +261,83 @@ def testGetCurrentRenderPass( self ) :
script2["variables"]["renderPass"] = Gaffer.NameValuePlug( "renderPass", 123.0, "renderPass" )
self.assertRaises( IECore.Exception, GafferSceneUI.ScriptNodeAlgo.getCurrentRenderPass, script2 )

def testVisibleSetBookmarks( self ) :

s = Gaffer.ScriptNode()
self.assertEqual( GafferSceneUI.ScriptNodeAlgo.visibleSetBookmarks( s ), [] )
self.assertEqual(
GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( s, "test" ),
GafferScene.VisibleSet()
)

v = GafferScene.VisibleSet(
inclusions = IECore.PathMatcher( [ "/a" ] ),
exclusions = IECore.PathMatcher( [ "/b" ] ),
expansions = IECore.PathMatcher( [ "/c" ] )
)

cs = GafferTest.CapturingSlot( Gaffer.Metadata.nodeValueChangedSignal( s ) )
GafferSceneUI.ScriptNodeAlgo.addVisibleSetBookmark( s, "test", v )
self.assertTrue( len( cs ) )

self.assertEqual( GafferSceneUI.ScriptNodeAlgo.visibleSetBookmarks( s ), [ "test" ] )
self.assertEqual(
GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( s, "test" ),
v
)

v2 = GafferScene.VisibleSet( v )
v2.inclusions.addPath( "/d" )
del cs[:]
GafferSceneUI.ScriptNodeAlgo.addVisibleSetBookmark( s, "test2", v2 )
self.assertTrue( len( cs ) )

self.assertEqual( GafferSceneUI.ScriptNodeAlgo.visibleSetBookmarks( s ), [ "test", "test2" ] )
self.assertEqual(
GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( s, "test2" ),
v2
)

del cs[:]
GafferSceneUI.ScriptNodeAlgo.removeVisibleSetBookmark( s, "test" )
self.assertTrue( len( cs ) )

self.assertEqual( GafferSceneUI.ScriptNodeAlgo.visibleSetBookmarks( s ), [ "test2" ] )
self.assertEqual(
GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( s, "test" ),
GafferScene.VisibleSet()
)
self.assertEqual(
GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( s, "test2" ),
v2
)

def testVisibleSetBookmarkSerialisation( self ) :

s = Gaffer.ScriptNode()

v = GafferScene.VisibleSet(
inclusions = IECore.PathMatcher( [ "/a" ] ),
exclusions = IECore.PathMatcher( [ "/b/b" ] ),
expansions = IECore.PathMatcher( [ "/c/c/c" ] )
)
GafferSceneUI.ScriptNodeAlgo.addVisibleSetBookmark( s, "serialisationTest", v )
self.assertIn( "serialisationTest", GafferSceneUI.ScriptNodeAlgo.visibleSetBookmarks( s ) )
self.assertEqual(
GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( s, "serialisationTest" ),
v
)

se = s.serialise()

s2 = Gaffer.ScriptNode()
s2.execute( se )

self.assertIn( "serialisationTest", GafferSceneUI.ScriptNodeAlgo.visibleSetBookmarks( s2 ) )
self.assertEqual(
GafferSceneUI.ScriptNodeAlgo.getVisibleSetBookmark( s2, "serialisationTest" ),
v
)

if __name__ == "__main__":
unittest.main()
37 changes: 37 additions & 0 deletions python/GafferTest/MetadataTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,43 @@ def testSerialisation( self ) :
self.assertEqual( Gaffer.Metadata.value( s2["n"], "serialisationTest" ), 1 )
self.assertEqual( Gaffer.Metadata.value( s2["n"]["op1"], "serialisationTest" ), 2 )

def testScriptNodeSerialisation( self ) :

s = Gaffer.ScriptNode()

Gaffer.Metadata.registerValue( s, "serialisationTest", 1 )
Gaffer.Metadata.registerValue( s["variables"], "serialisationTest", 2 )

s2 = Gaffer.ScriptNode()
s2.execute( s.serialise() )

self.assertEqual( Gaffer.Metadata.value( s2, "serialisationTest" ), 1 )
self.assertEqual( Gaffer.Metadata.value( s2["variables"], "serialisationTest" ), 2 )

def testScriptNodeMetadataDoesntLeak( self ) :

a = Gaffer.ApplicationRoot()
s = Gaffer.ScriptNode()
a["scripts"].addChild( s )

s["n"] = GafferTest.AddNode()

Gaffer.Metadata.registerValue( s, "scriptNodeSerialisationTest", 1 )
Gaffer.Metadata.registerValue( s["n"], "serialisationTest", 1 )
Gaffer.Metadata.registerValue( s["n"]["op1"], "serialisationTest", 2 )

s2 = Gaffer.ScriptNode()
a["scripts"].addChild( s2 )

s.selection().add( s["n"] )
s.copy( parent = s, filter = s.selection() )

s2.paste()

self.assertNotIn( "scriptNodeSerialisationTest", Gaffer.Metadata.registeredValues( s2 ) )
self.assertEqual( Gaffer.Metadata.value( s2["n"], "serialisationTest" ), 1 )
self.assertEqual( Gaffer.Metadata.value( s2["n"]["op1"], "serialisationTest" ), 2 )

def testStringSerialisationWithNewlinesAndQuotes( self ) :

trickyStrings = [
Expand Down
8 changes: 8 additions & 0 deletions src/Gaffer/ScriptNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,14 @@ std::string ScriptNode::serialiseInternal( const Node *parent, const Set *filter
{
throw IECore::Exception( "Serialisation not available - please link to libGafferBindings." );
}

Context::EditableScope scope( Context::current() );
if( ( !parent || parent == this ) && !filter )
{
static const bool includeParentMetadata = true;
scope.set( "serialiser:includeParentMetadata", &includeParentMetadata );
}

return g_serialiseFunction( parent ? parent : this, filter );
}

Expand Down
Loading

0 comments on commit aed2e50

Please sign in to comment.