From 715f125f3aa5b8030277ddbc0bf53b1841b22047 Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:24:13 -0800 Subject: [PATCH 1/4] ScriptNode : Support serialisation of ScriptNode metadata --- Changes.md | 1 + python/GafferTest/MetadataTest.py | 37 +++++++++++++++++++++++++++++++ src/Gaffer/ScriptNode.cpp | 8 +++++++ 3 files changed, 46 insertions(+) diff --git a/Changes.md b/Changes.md index b7c1a51a6aa..b1bab2f623c 100644 --- a/Changes.md +++ b/Changes.md @@ -5,6 +5,7 @@ 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 ----- diff --git a/python/GafferTest/MetadataTest.py b/python/GafferTest/MetadataTest.py index c13162a9f21..ec98346eff2 100644 --- a/python/GafferTest/MetadataTest.py +++ b/python/GafferTest/MetadataTest.py @@ -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 = [ diff --git a/src/Gaffer/ScriptNode.cpp b/src/Gaffer/ScriptNode.cpp index fa085c662b2..633c2fcc50f 100644 --- a/src/Gaffer/ScriptNode.cpp +++ b/src/Gaffer/ScriptNode.cpp @@ -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 ); } From ec51e4d94f081118d233434e11ec8664143a3da9 Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:24:35 -0800 Subject: [PATCH 2/4] VisibleSetData : Implement `save` and `load` --- Changes.md | 1 + include/GafferScene/VisibleSetData.h | 2 +- python/GafferSceneTest/VisibleSetDataTest.py | 20 +++++++++++ src/GafferScene/VisibleSetData.cpp | 37 ++++++++++++++++++-- 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/Changes.md b/Changes.md index b1bab2f623c..38bfa91fae1 100644 --- a/Changes.md +++ b/Changes.md @@ -21,6 +21,7 @@ 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()`. 1.5.3.0 (relative to 1.5.2.0) ======= diff --git a/include/GafferScene/VisibleSetData.h b/include/GafferScene/VisibleSetData.h index 220919efad5..b3758130abd 100644 --- a/include/GafferScene/VisibleSetData.h +++ b/include/GafferScene/VisibleSetData.h @@ -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 diff --git a/python/GafferSceneTest/VisibleSetDataTest.py b/python/GafferSceneTest/VisibleSetDataTest.py index 3291698558f..c420253e02d 100644 --- a/python/GafferSceneTest/VisibleSetDataTest.py +++ b/python/GafferSceneTest/VisibleSetDataTest.py @@ -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() diff --git a/src/GafferScene/VisibleSetData.cpp b/src/GafferScene/VisibleSetData.cpp index 7a4ac5b903e..b8280030a58 100644 --- a/src/GafferScene/VisibleSetData.cpp +++ b/src/GafferScene/VisibleSetData.cpp @@ -36,10 +36,12 @@ #include "GafferScene/VisibleSetData.h" +#include "GafferScene/Export.h" #include "GafferScene/TypeIds.h" #include "Gaffer/Context.h" +#include "IECore/PathMatcherData.h" #include "IECore/TypedData.h" #include "IECore/TypedData.inl" @@ -47,6 +49,11 @@ namespace { Gaffer::Context::TypeDescription g_visibleSetDataTypeDescription; +const IndexedIO::EntryID g_inclusionsEntry( "inclusions" ); +const IndexedIO::EntryID g_exclusionsEntry( "exclusions" ); +const IndexedIO::EntryID g_expansionsEntry( "expansions" ); +const InternedString g_visibleSetEntry( "visibleSet" ); +const unsigned int g_ioVersion = 0; } // namespace @@ -55,16 +62,40 @@ namespace IECore IECORE_RUNTIMETYPED_DEFINETEMPLATESPECIALISATION( GafferScene::VisibleSetData, GafferScene::VisibleSetDataTypeId ) +/// \todo Remove once Cortex has been updated to provide these. template<> +void PathMatcherData::save( SaveContext *context ) const; + +template<> +void PathMatcherData::load( LoadContextPtr context ); + +template<> GAFFERSCENE_API void VisibleSetData::save( SaveContext *context ) const { - throw IECore::NotImplementedException( "VisibleSetData::save Not implemented" ); + Data::save( context ); + IndexedIOPtr container = context->container( staticTypeName(), g_ioVersion ); + IndexedIOPtr visibleSetIO = container->subdirectory( g_visibleSetEntry, IndexedIO::CreateIfMissing ); + + IECore::PathMatcherDataPtr inclusionsData = new IECore::PathMatcherData( readable().inclusions ); + IECore::PathMatcherDataPtr exclusionsData = new IECore::PathMatcherData( readable().exclusions ); + IECore::PathMatcherDataPtr expansionsData = new IECore::PathMatcherData( readable().expansions ); + + context->save( inclusionsData.get(), visibleSetIO.get(), g_inclusionsEntry ); + context->save( exclusionsData.get(), visibleSetIO.get(), g_exclusionsEntry ); + context->save( expansionsData.get(), visibleSetIO.get(), g_expansionsEntry ); } -template<> +template<> GAFFERSCENE_API void VisibleSetData::load( LoadContextPtr context ) { - throw IECore::NotImplementedException( "VisibleSetData::load Not implemented" ); + Data::load( context ); + unsigned int v = 0; + ConstIndexedIOPtr container = context->container( staticTypeName(), v ); + ConstIndexedIOPtr visibleSetIO = container->subdirectory( g_visibleSetEntry, IndexedIO::NullIfMissing ); + + writable().inclusions = context->load( visibleSetIO.get(), g_inclusionsEntry )->readable(); + writable().exclusions = context->load( visibleSetIO.get(), g_exclusionsEntry )->readable(); + writable().expansions = context->load( visibleSetIO.get(), g_expansionsEntry )->readable(); } template class TypedData; From ad14151b96bf755fb71f77b3e7b1e36237e93fbf Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:24:59 -0800 Subject: [PATCH 3/4] ScriptNodeAlgo : Add functions for VisibleSet bookmarks --- Changes.md | 1 + include/GafferSceneUI/ScriptNodeAlgo.h | 16 ++++ .../GafferSceneUITest/ScriptNodeAlgoTest.py | 78 +++++++++++++++++++ src/GafferSceneUI/ScriptNodeAlgo.cpp | 46 +++++++++++ .../ScriptNodeAlgoBinding.cpp | 27 +++++++ 5 files changed, 168 insertions(+) diff --git a/Changes.md b/Changes.md index 38bfa91fae1..2aa5ba88059 100644 --- a/Changes.md +++ b/Changes.md @@ -22,6 +22,7 @@ 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) ======= diff --git a/include/GafferSceneUI/ScriptNodeAlgo.h b/include/GafferSceneUI/ScriptNodeAlgo.h index 7a743ca4ba3..f91aa183c0a 100644 --- a/include/GafferSceneUI/ScriptNodeAlgo.h +++ b/include/GafferSceneUI/ScriptNodeAlgo.h @@ -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 visibleSetBookmarks( const Gaffer::ScriptNode *script ); + } // namespace ScriptNodeAlgo } // namespace GafferSceneUI diff --git a/python/GafferSceneUITest/ScriptNodeAlgoTest.py b/python/GafferSceneUITest/ScriptNodeAlgoTest.py index 9cee235a184..2a6d02f7e68 100644 --- a/python/GafferSceneUITest/ScriptNodeAlgoTest.py +++ b/python/GafferSceneUITest/ScriptNodeAlgoTest.py @@ -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() diff --git a/src/GafferSceneUI/ScriptNodeAlgo.cpp b/src/GafferSceneUI/ScriptNodeAlgo.cpp index af6a22a62f6..99acd62ea8e 100644 --- a/src/GafferSceneUI/ScriptNodeAlgo.cpp +++ b/src/GafferSceneUI/ScriptNodeAlgo.cpp @@ -38,12 +38,16 @@ #include "GafferSceneUI/ContextAlgo.h" +#include "GafferScene/VisibleSetData.h" + #include "Gaffer/Context.h" +#include "Gaffer/Metadata.h" #include "Gaffer/ScriptNode.h" #include "Gaffer/NameValuePlug.h" #include "Gaffer/CompoundDataPlug.h" #include "Gaffer/MetadataAlgo.h" +#include "boost/algorithm/string/predicate.hpp" #include "boost/bind/bind.hpp" #include @@ -55,6 +59,8 @@ using namespace GafferSceneUI; namespace { +const std::string g_visibleSetBookmarkPrefix( "visibleSet:bookmark:" ); + struct ChangedSignals { ScriptNodeAlgo::ChangedSignal visibleSetChangedSignal; @@ -192,3 +198,43 @@ std::string ScriptNodeAlgo::getCurrentRenderPass( const Gaffer::ScriptNode *scri return ""; } + +// Visible Set Bookmarks +// ===================== + +void ScriptNodeAlgo::addVisibleSetBookmark( Gaffer::ScriptNode *script, const std::string &name, const GafferScene::VisibleSet &visibleSet, bool persistent ) +{ + Metadata::registerValue( script, g_visibleSetBookmarkPrefix + name, new GafferScene::VisibleSetData( visibleSet ), persistent ); +} + +GafferScene::VisibleSet ScriptNodeAlgo::getVisibleSetBookmark( const Gaffer::ScriptNode *script, const std::string &name ) +{ + if( const auto bookmarkData = Metadata::value( script, g_visibleSetBookmarkPrefix + name ) ) + { + return bookmarkData->readable(); + } + + return GafferScene::VisibleSet(); +} + +void ScriptNodeAlgo::removeVisibleSetBookmark( Gaffer::ScriptNode *script, const std::string &name ) +{ + Metadata::deregisterValue( script, g_visibleSetBookmarkPrefix + name ); +} + +std::vector ScriptNodeAlgo::visibleSetBookmarks( const Gaffer::ScriptNode *script ) +{ + std::vector keys; + Metadata::registeredValues( script, keys ); + + std::vector result; + for( const auto &key : keys ) + { + if( boost::starts_with( key.string(), g_visibleSetBookmarkPrefix ) ) + { + result.push_back( key.string().substr( g_visibleSetBookmarkPrefix.size(), key.string().size() ) ); + } + } + + return result; +} diff --git a/src/GafferSceneUIModule/ScriptNodeAlgoBinding.cpp b/src/GafferSceneUIModule/ScriptNodeAlgoBinding.cpp index ed3374d1ab1..50b6b600a58 100644 --- a/src/GafferSceneUIModule/ScriptNodeAlgoBinding.cpp +++ b/src/GafferSceneUIModule/ScriptNodeAlgoBinding.cpp @@ -115,6 +115,28 @@ std::string getCurrentRenderPassWrapper( ScriptNode &script ) return getCurrentRenderPass( &script ); } +void addVisibleSetBookmarkWrapper( ScriptNode &script, const std::string &name, const GafferScene::VisibleSet &visibleSet, bool persistent ) +{ + IECorePython::ScopedGILRelease gilRelease; + addVisibleSetBookmark( &script, name, visibleSet, persistent ); +} + +void removeVisibleSetBookmarkWrapper( ScriptNode &script, const std::string &name ) +{ + IECorePython::ScopedGILRelease gilRelease; + removeVisibleSetBookmark( &script, name ); +} + +list visibleSetBookmarksWrapper( const ScriptNode &script ) +{ + list result; + for( const auto &n : visibleSetBookmarks( &script ) ) + { + result.append( n ); + } + return result; +} + } // namespace void GafferSceneUIModule::bindScriptNodeAlgo() @@ -137,4 +159,9 @@ void GafferSceneUIModule::bindScriptNodeAlgo() def( "acquireRenderPassPlug", &acquireRenderPassPlugWrapper, ( arg( "script" ), arg( "createIfMissing" ) = true ) ); def( "setCurrentRenderPass", &setCurrentRenderPassWrapper ); def( "getCurrentRenderPass", &getCurrentRenderPassWrapper ); + + def( "addVisibleSetBookmark", &addVisibleSetBookmarkWrapper, ( arg( "script" ), arg( "name" ), arg( "visibleSet" ), arg( "persistent" ) = true ) ); + def( "getVisibleSetBookmark", getVisibleSetBookmark, ( arg( "script" ), arg( "name" ) ) ); + def( "removeVisibleSetBookmark", &removeVisibleSetBookmarkWrapper, ( arg( "script" ), arg( "name" ) ) ); + def( "visibleSetBookmarks", &visibleSetBookmarksWrapper, ( arg( "script" ) ) ); } From f0cbcdeca1c5cf44a792d2b39cd2ef481fbffc09 Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:25:08 -0800 Subject: [PATCH 4/4] HierarchyView : Add menu for VisibleSet bookmarks --- Changes.md | 5 ++ python/GafferSceneUI/HierarchyView.py | 107 ++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/Changes.md b/Changes.md index 2aa5ba88059..23f60ec58b4 100644 --- a/Changes.md +++ b/Changes.md @@ -1,6 +1,11 @@ 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 ------------ diff --git a/python/GafferSceneUI/HierarchyView.py b/python/GafferSceneUI/HierarchyView.py index 5de4b2071ea..db701a3e5ea 100644 --- a/python/GafferSceneUI/HierarchyView.py +++ b/python/GafferSceneUI/HierarchyView.py @@ -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 ) @@ -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 )