From dcbc8d00b12303882aa278c79794bef27164adae Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Tue, 28 Jan 2025 14:44:45 -0800 Subject: [PATCH 1/2] EditScopeAlgo : Add methods for renaming a render pass --- Changes.md | 5 + include/GafferScene/EditScopeAlgo.h | 4 + python/GafferSceneTest/EditScopeAlgoTest.py | 203 ++++++++++++++++++ src/GafferScene/EditScopeAlgo.cpp | 59 +++++ .../EditScopeAlgoBinding.cpp | 15 ++ 5 files changed, 286 insertions(+) diff --git a/Changes.md b/Changes.md index 1f2542199f7..d4a436ac161 100644 --- a/Changes.md +++ b/Changes.md @@ -33,6 +33,11 @@ Fixes - InteractiveRender : Fixed potential leak of `scene:path` context variable when computing the value for `resolvedRenderer`. - Dispatch app : Fixed poor UI layout in "Completed" dialogue state (#6244). +API +--- + +- EditScopeAlgo : Added `renameRenderPass()` and `renameRenderPassNonEditableReason()` functions. + 1.5.4.1 (relative to 1.5.4.0) ======= diff --git a/include/GafferScene/EditScopeAlgo.h b/include/GafferScene/EditScopeAlgo.h index f847baef980..7c2fadeceec 100644 --- a/include/GafferScene/EditScopeAlgo.h +++ b/include/GafferScene/EditScopeAlgo.h @@ -161,6 +161,10 @@ GAFFERSCENE_API void removeRenderPassOptionEdit( Gaffer::EditScope *scope, const GAFFERSCENE_API const Gaffer::GraphComponent *renderPassOptionEditReadOnlyReason( const Gaffer::EditScope *scope, const std::string &renderPass, const std::string &option ); GAFFERSCENE_API const Gaffer::GraphComponent *renderPassesReadOnlyReason( const Gaffer::EditScope *scope ); +/// Renames the render pass `oldName` to `newName` inside `scope`. Returns `true` if renaming occurred. +GAFFERSCENE_API bool renameRenderPass( Gaffer::EditScope *scope, const std::string &oldName, const std::string &newName ); +/// Returns the reason why a render pass could not be renamed to `newName`. +GAFFERSCENE_API std::optional renameRenderPassNonEditableReason( const Gaffer::EditScope *scope, const std::string &newName ); } // namespace EditScopeAlgo diff --git a/python/GafferSceneTest/EditScopeAlgoTest.py b/python/GafferSceneTest/EditScopeAlgoTest.py index a98bddea2f2..5623fb844fe 100644 --- a/python/GafferSceneTest/EditScopeAlgoTest.py +++ b/python/GafferSceneTest/EditScopeAlgoTest.py @@ -1726,5 +1726,208 @@ def testRenderPassesSerialisation( self ) : self.assertEqual( s2["editScope"]["out"]["globals"].getValue().get( "option:renderPass:names" ), IECore.StringVectorData( [ "renderPassA" ] ) ) + def testRenameRenderPass( self ) : + + editScope = Gaffer.EditScope() + editScope.setup( GafferScene.ScenePlug() ) + + renderPassNames = IECore.StringVectorData( [ "renderPassA", "renderPassB", "renderPassC" ] ) + renderPasses = editScope.acquireProcessor( "RenderPasses", createIfNecessary = True ) + renderPasses["names"].setValue( renderPassNames ) + + self.assertEqual( editScope["out"]["globals"].getValue().get( "option:renderPass:names" ), renderPassNames ) + + self.assertFalse( GafferScene.EditScopeAlgo.renameRenderPass( editScope, "notAPass", "notARenamedPass" ) ) + self.assertEqual( editScope["out"]["globals"].getValue().get( "option:renderPass:names" ), renderPassNames ) + + self.assertIsNone( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( editScope, "renamedPassB" ) ) + self.assertTrue( GafferScene.EditScopeAlgo.renameRenderPass( editScope, "renderPassB", "renamedPassB" ) ) + self.assertEqual( editScope["out"]["globals"].getValue().get( "option:renderPass:names" ), IECore.StringVectorData( [ "renderPassA", "renamedPassB", "renderPassC" ] ) ) + + self.assertIsNone( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( editScope, "renderPassB" ) ) + self.assertTrue( GafferScene.EditScopeAlgo.renameRenderPass( editScope, "renamedPassB", "renderPassB" ) ) + self.assertEqual( editScope["out"]["globals"].getValue().get( "option:renderPass:names" ), renderPassNames ) + + self.assertIsNotNone( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( editScope, "renderPassA" ) ) + self.assertRaises( RuntimeError, GafferScene.EditScopeAlgo.renameRenderPass, editScope, "renderPassC", "renderPassA" ) + + def testRenameRenderPassDoesNotCreateUnnecessaryProcessors( self ) : + + editScope = Gaffer.EditScope() + editScope.setup( GafferScene.ScenePlug() ) + + self.assertFalse( GafferScene.EditScopeAlgo.renameRenderPass( editScope, "notAPass", "notARenamedPass" ) ) + + self.assertIsNone( editScope.acquireProcessor( "RenderPasses", createIfNecessary = False ) ) + self.assertIsNone( editScope.acquireProcessor( "RenderPassOptionEdits", createIfNecessary = False ) ) + + renderPasses = editScope.acquireProcessor( "RenderPasses", createIfNecessary = True ) + renderPasses["names"].setValue( IECore.StringVectorData( [ "renderPassA", "renderPassB", "renderPassC" ] ) ) + self.assertTrue( GafferScene.EditScopeAlgo.renameRenderPass( editScope, "renderPassA", "renamedPassA" ) ) + + self.assertIsNone( editScope.acquireProcessor( "RenderPassOptionEdits", createIfNecessary = False ) ) + + def testRenameRenderPassWithOptionEdits( self ) : + + s = Gaffer.ScriptNode() + + s["options"] = GafferScene.CustomOptions() + s["options"]["options"].addChild( Gaffer.NameValuePlug( "test", IECore.StringData( "" ) ) ) + + s["editScope"] = Gaffer.EditScope() + s["editScope"].setup( s["options"]["out"] ) + s["editScope"]["in"].setInput( s["options"]["out"] ) + + renderPassNames = IECore.StringVectorData( [ "renderPassA", "renderPassB", "renderPassC" ] ) + renderPasses = s["editScope"].acquireProcessor( "RenderPasses", createIfNecessary = True ) + renderPasses["names"].setValue( renderPassNames ) + + for name in renderPassNames : + edit = GafferScene.EditScopeAlgo.acquireRenderPassOptionEdit( s["editScope"], name, "test" ) + edit["enabled"].setValue( True ) + edit["value"].setValue( name ) + + with Gaffer.Context() as context : + context["renderPass"] = name + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, name ) + + self.assertIsNone( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renamedPassA" ) ) + self.assertTrue( GafferScene.EditScopeAlgo.renameRenderPass( s["editScope"], "renderPassA", "renamedPassA" ) ) + self.assertEqual( s["editScope"]["out"]["globals"].getValue().get( "option:renderPass:names" ), IECore.StringVectorData( [ "renamedPassA", "renderPassB", "renderPassC" ] ) ) + + with Gaffer.Context() as context : + context["renderPass"] = "renamedPassA" + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, "renderPassA" ) + for name in renderPassNames : + context["renderPass"] = name + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, name if name != "renderPassA" else "" ) + + # Renaming renderPassB to a previously used name is permitted. + self.assertIsNone( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassA" ) ) + self.assertTrue( GafferScene.EditScopeAlgo.renameRenderPass( s["editScope"], "renderPassB", "renderPassA" ) ) + self.assertEqual( s["editScope"]["out"]["globals"].getValue().get( "option:renderPass:names" ), IECore.StringVectorData( [ "renamedPassA", "renderPassA", "renderPassC" ] ) ) + + with Gaffer.Context() as context : + context["renderPass"] = "renderPassB" + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, "" ) + + context["renderPass"] = "renderPassA" + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, "renderPassB" ) + + context["renderPass"] = "renamedPassA" + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, "renderPassA" ) + + context["renderPass"] = "renderPassC" + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, "renderPassC" ) + + # Renaming renderPassC to a currently existing name should error and not change anything. + self.assertIsNotNone( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassA" ) ) + self.assertRaises( RuntimeError, GafferScene.EditScopeAlgo.renameRenderPass, s["editScope"], "renderPassC", "renderPassA" ) + self.assertEqual( s["editScope"]["out"]["globals"].getValue().get( "option:renderPass:names" ), IECore.StringVectorData( [ "renamedPassA", "renderPassA", "renderPassC" ] ) ) + + with Gaffer.Context() as context : + context["renderPass"] = "renderPassB" + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, "" ) + + context["renderPass"] = "renderPassA" + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, "renderPassB" ) + + context["renderPass"] = "renamedPassA" + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, "renderPassA" ) + + context["renderPass"] = "renderPassC" + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, "renderPassC" ) + + # Reversing our rename steps should return things to the original state. + self.assertIsNone( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassB" ) ) + self.assertTrue( GafferScene.EditScopeAlgo.renameRenderPass( s["editScope"], "renderPassA", "renderPassB" ) ) + self.assertIsNone( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassA" ) ) + self.assertTrue( GafferScene.EditScopeAlgo.renameRenderPass( s["editScope"], "renamedPassA", "renderPassA" ) ) + + self.assertEqual( s["editScope"]["out"]["globals"].getValue().get( "option:renderPass:names" ), renderPassNames ) + with Gaffer.Context() as context : + context["renderPass"] = "renamedPassA" + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, "" ) + for name in renderPassNames : + context["renderPass"] = name + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, name ) + + def testRenameRenderPassWithOnlyOptionEdits( self ) : + + s = Gaffer.ScriptNode() + + s["options"] = GafferScene.CustomOptions() + s["options"]["options"].addChild( Gaffer.NameValuePlug( "test", IECore.StringData( "" ) ) ) + + s["editScope"] = Gaffer.EditScope() + s["editScope"].setup( s["options"]["out"] ) + s["editScope"]["in"].setInput( s["options"]["out"] ) + + for name in [ "renderPassA", "renderPassB", "renderPassC" ] : + edit = GafferScene.EditScopeAlgo.acquireRenderPassOptionEdit( s["editScope"], name, "test" ) + edit["enabled"].setValue( True ) + edit["value"].setValue( name ) + + self.assertIsNone( s["editScope"].acquireProcessor( "RenderPasses", createIfNecessary = False ) ) + + self.assertIsNone( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renamedPassA" ) ) + self.assertTrue( GafferScene.EditScopeAlgo.renameRenderPass( s["editScope"], "renderPassA", "renamedPassA" ) ) + self.assertIsNone( s["editScope"].acquireProcessor( "RenderPasses", createIfNecessary = False ) ) + + with Gaffer.Context() as context : + context["renderPass"] = "renderPassA" + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, "" ) + + for name in [ "renamedPassA", "renderPassB", "renderPassC" ] : + context["renderPass"] = name + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, "renderPassA" if name == "renamedPassA" else name ) + + self.assertIsNone( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassA" ) ) + self.assertTrue( GafferScene.EditScopeAlgo.renameRenderPass( s["editScope"], "renamedPassA", "renderPassA" ) ) + + with Gaffer.Context() as context : + context["renderPass"] = "renamedPassA" + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, "" ) + + for name in [ "renderPassA", "renderPassB", "renderPassC" ] : + context["renderPass"] = name + self.assertEqual( s["editScope"]["out"].globals()["option:test"].value, name ) + + def testRenameRenderPassNonEditableReason( self ) : + + s = Gaffer.ScriptNode() + + s["options"] = GafferScene.CustomOptions() + s["options"]["options"].addChild( Gaffer.NameValuePlug( "test", IECore.StringData( "" ) ) ) + + s["editScope"] = Gaffer.EditScope() + s["editScope"].setup( s["options"]["out"] ) + s["editScope"]["in"].setInput( s["options"]["out"] ) + + self.assertIsNone( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassA" ) ) + + edit = GafferScene.EditScopeAlgo.acquireRenderPassOptionEdit( s["editScope"], "renderPassA", "test" ) + edit["enabled"].setValue( True ) + edit["value"].setValue( "renderPassA" ) + + self.assertEqual( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassA" ), "Edits already exist for render pass \"renderPassA\" in editScope.RenderPassOptionEdits" ) + self.assertIsNone( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassB" ) ) + + renderPasses = s["editScope"].acquireProcessor( "RenderPasses", createIfNecessary = True ) + renderPasses["names"].setValue( IECore.StringVectorData( [ "renderPassA" ] ) ) + self.assertEqual( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassA" ), "A render pass named \"renderPassA\" already exists in editScope.RenderPasses" ) + self.assertIsNone( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassB" ) ) + + renderPasses["names"].setValue( IECore.StringVectorData( [ "renderPassB" ] ) ) + self.assertEqual( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassA" ), "Edits already exist for render pass \"renderPassA\" in editScope.RenderPassOptionEdits" ) + self.assertEqual( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassB" ), "A render pass named \"renderPassB\" already exists in editScope.RenderPasses" ) + self.assertIsNone( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassC" ) ) + + renderPasses["names"].setValue( IECore.StringVectorData( [ "renderPassB", "renderPassC" ] ) ) + self.assertEqual( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassA" ), "Edits already exist for render pass \"renderPassA\" in editScope.RenderPassOptionEdits" ) + self.assertEqual( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassB" ), "A render pass named \"renderPassB\" already exists in editScope.RenderPasses" ) + self.assertEqual( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassC" ), "A render pass named \"renderPassC\" already exists in editScope.RenderPasses" ) + self.assertIsNone( GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( s["editScope"], "renderPassD" ) ) + if __name__ == "__main__": unittest.main() diff --git a/src/GafferScene/EditScopeAlgo.cpp b/src/GafferScene/EditScopeAlgo.cpp index 221a3e11976..481c242c016 100644 --- a/src/GafferScene/EditScopeAlgo.cpp +++ b/src/GafferScene/EditScopeAlgo.cpp @@ -1492,3 +1492,62 @@ const Gaffer::GraphComponent *GafferScene::EditScopeAlgo::renderPassesReadOnlyRe return MetadataAlgo::readOnlyReason( scope ); } + +bool GafferScene::EditScopeAlgo::renameRenderPass( Gaffer::EditScope *scope, const std::string &oldName, const std::string &newName ) +{ + if( const auto nonEditableReason = renameRenderPassNonEditableReason( scope, newName ) ) + { + throw IECore::Exception( nonEditableReason.value() ); + } + + bool renamed = false; + if( auto renderPassesProcessor = scope->acquireProcessor( g_renderPassesProcessorName, /* createIfNecessary = */ false ) ) + { + auto namesPlug = renderPassesProcessor->getChild( "names" ); + ConstStringVectorDataPtr renderPasses = namesPlug->getValue(); + + if( std::find( renderPasses->readable().begin(), renderPasses->readable().end(), oldName ) != renderPasses->readable().end() ) + { + auto renderPassesCopy = renderPasses->copy(); + std::replace( renderPassesCopy->writable().begin(), renderPassesCopy->writable().end(), oldName, newName ); + namesPlug->setValue( renderPassesCopy ); + renamed = true; + } + } + + if( auto renderPassOptionEditsProcessor = scope->acquireProcessor( g_renderPassOptionProcessorName, /* createIfNecessary = */ false ) ) + { + auto *rows = renderPassOptionEditsProcessor->getChild( "edits" ); + if( Spreadsheet::RowPlug *row = rows->row( oldName ) ) + { + row->namePlug()->setValue( newName ); + renamed = true; + } + } + + return renamed; +} + +std::optional GafferScene::EditScopeAlgo::renameRenderPassNonEditableReason( const Gaffer::EditScope *scope, const std::string &newName ) +{ + if( auto renderPassesProcessor = const_cast( scope )->acquireProcessor( g_renderPassesProcessorName, /* createIfNecessary = */ false ) ) + { + auto namesPlug = renderPassesProcessor->getChild( "names" ); + ConstStringVectorDataPtr renderPasses = namesPlug->getValue(); + if( std::find( renderPasses->readable().begin(), renderPasses->readable().end(), newName ) != renderPasses->readable().end() ) + { + return fmt::format( "A render pass named \"{}\" already exists in {}", newName, renderPassesProcessor->relativeName( scope->parent() ) ); + } + } + + if( auto renderPassOptionEditsProcessor = const_cast( scope )->acquireProcessor( g_renderPassOptionProcessorName, /* createIfNecessary = */ false ) ) + { + auto *rows = renderPassOptionEditsProcessor->getChild( "edits" ); + if( rows->row( newName ) ) + { + return fmt::format( "Edits already exist for render pass \"{}\" in {}", newName, renderPassOptionEditsProcessor->relativeName( scope->parent() ) ); + } + } + + return std::nullopt; +} diff --git a/src/GafferSceneModule/EditScopeAlgoBinding.cpp b/src/GafferSceneModule/EditScopeAlgoBinding.cpp index 525190e4818..6f3f02c47ba 100644 --- a/src/GafferSceneModule/EditScopeAlgoBinding.cpp +++ b/src/GafferSceneModule/EditScopeAlgoBinding.cpp @@ -263,6 +263,19 @@ GraphComponentPtr renderPassesReadOnlyReasonWrapper( Gaffer::EditScope &scope ) return const_cast( EditScopeAlgo::renderPassesReadOnlyReason( &scope ) ); } +bool renameRenderPassWrapper( Gaffer::EditScope &scope, const std::string &oldName, const std::string &newName ) +{ + IECorePython::ScopedGILRelease gilRelease; + return EditScopeAlgo::renameRenderPass( &scope, oldName, newName ); +} + +object renameRenderPassNonEditableReasonWrapper( Gaffer::EditScope &scope, const std::string &newName ) +{ + IECorePython::ScopedGILRelease gilRelease; + std::optional result = EditScopeAlgo::renameRenderPassNonEditableReason( &scope, newName ); + return result ? object( result.value() ) : object(); +} + } // namespace namespace GafferSceneModule @@ -326,6 +339,8 @@ void bindEditScopeAlgo() def( "renderPassOptionEditReadOnlyReason", &renderPassOptionEditReadOnlyReasonWrapper, ( arg( "scope" ), arg( "renderPass" ), arg( "option" ) ) ); def( "renderPassesReadOnlyReason", &renderPassesReadOnlyReasonWrapper ); + def( "renameRenderPass", &renameRenderPassWrapper, ( arg( "scope" ), arg( "oldName" ), arg( "newName" ) ) ); + def( "renameRenderPassNonEditableReason", &renameRenderPassNonEditableReasonWrapper, ( arg( "scope" ), arg( "newName" ) ) ); } From 590783fe85ca8a7d302fcf2b280811a27a77b8a0 Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Mon, 27 Jan 2025 15:44:59 -0800 Subject: [PATCH 2/2] RenderPassEditor : Add Rename Render Pass menu item --- Changes.md | 1 + .../Interface/ControlsAndShortcuts/index.md | 1 + python/GafferSceneUI/RenderPassEditor.py | 92 ++++++++++++++++++- 3 files changed, 92 insertions(+), 2 deletions(-) diff --git a/Changes.md b/Changes.md index d4a436ac161..18ad0d67987 100644 --- a/Changes.md +++ b/Changes.md @@ -15,6 +15,7 @@ Features Improvements ------------ +- RenderPassEditor : Added ability to rename a render pass within the edit scope it was originally created in. A render pass can be renamed from the "Name" column via the "Rename Selected Render Pass..." menu item, double clicking on a cell or selecting a cell and pressing Enter or Return. - VisualiserTool : - Changed naming requirements for visualising primitive variables. Values in `dataName` now prefix the primitive variable name with `primitiveVariable:`. Setting `dataName` to `vertex:index` will display vertex indices. - Added `mode` plug. The available modes are : diff --git a/doc/source/Interface/ControlsAndShortcuts/index.md b/doc/source/Interface/ControlsAndShortcuts/index.md index 5b37d285bd8..65304f1b050 100644 --- a/doc/source/Interface/ControlsAndShortcuts/index.md +++ b/doc/source/Interface/ControlsAndShortcuts/index.md @@ -438,6 +438,7 @@ Toggle cell selection | {kbd}`Ctrl` + {{leftClick Edit selected cells | {kbd}`Return`
or
{kbd}`Enter` Disable edit | {kbd}`D` Toggle a render pass as active | {kbd}`Return` or {{leftClick}} {{leftClick}} a cell within the {{activeRenderPass}} column +Rename a render pass | {kbd}`Return` or {{leftClick}} {{leftClick}} a cell within the "Name" column ## Color Chooser Color Field ## diff --git a/python/GafferSceneUI/RenderPassEditor.py b/python/GafferSceneUI/RenderPassEditor.py index b331e0929cb..9f1bf445901 100644 --- a/python/GafferSceneUI/RenderPassEditor.py +++ b/python/GafferSceneUI/RenderPassEditor.py @@ -357,6 +357,11 @@ def __buttonDoubleClick( self, pathListing, event ) : if event.button == event.Buttons.Left : column = pathListing.columnAt( event.line.p0 ) + if column == self.__renderPassNameColumn : + if self.__canEditRenderPasses() and len( self.__selectedRenderPasses() ) == 1 : + self.__renameSelectedRenderPass() + return True + if column == self.__renderPassActiveColumn : self.__setActiveRenderPass( pathListing ) return True @@ -368,6 +373,11 @@ def __keyPress( self, pathListing, event ) : if event.modifiers == event.Modifiers.None_ : if event.key == "Return" or event.key == "Enter" : + selectedRenderPasses = self.__selectedRenderPasses() + if self.__canEditRenderPasses() and len( selectedRenderPasses ) == 1 : + self.__renameSelectedRenderPass() + return True + selection = pathListing.getSelection() if len( selection[1].paths() ) : self.__setActiveRenderPass( pathListing ) @@ -434,6 +444,16 @@ def __columnContextMenuSignal( self, column, pathListing, menuDefinition ) : if columnIndex == 0 : # Render pass operations + menuDefinition.append( + "Rename Selected Render Pass...", + { + "command" : Gaffer.WeakMethod( self.__renameSelectedRenderPass ), + "active" : self.__canEditRenderPasses() and len( self.__selectedRenderPasses() ) == 1 + } + ) + + menuDefinition.append( "__DeleteDivider__", { "divider" : True } ) + menuDefinition.append( "Delete Selected Render Passes", { @@ -489,6 +509,63 @@ def __addRenderPass( self, renderPass, editScope ) : with Gaffer.UndoScope( editScope.ancestor( Gaffer.ScriptNode ) ) : renderPassesProcessor["names"].setValue( renderPasses ) + def __warningPopup( self, title, message ) : + + with GafferUI.PopupWindow() as self.__popup : + with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Vertical, spacing = 4 ) : + with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 ) : + GafferUI.Image( "warningSmall.png" ) + GafferUI.Label( "

{}

".format( title ) ) + GafferUI.Label( message ) + + self.__popup.popup( parent = self ) + + def __renameSelectedRenderPass( self ) : + + selectedRenderPasses = self.__selectedRenderPasses() + if len( selectedRenderPasses ) != 1 : + return + + editScope = self.editScope() + if editScope is None : + return + + renderPassesProcessor = editScope.acquireProcessor( "RenderPasses", createIfNecessary = False ) + if renderPassesProcessor is None or selectedRenderPasses[0] not in renderPassesProcessor["names"].getValue() : + self.__warningPopup( "Unable to rename", "Pass was not created in {}.".format( editScope.relativeName( self.scriptNode() ) ) ) + return + + dialogue = _RenderPassCreationDialogue( + existingNames = [ x for x in self.__renderPassNames( self.settings()["in"] ) if x != selectedRenderPasses[0] ], + editScope = editScope, + title = "Rename Render Pass", + confirmLabel = "Rename", + actionDescription = "Rename render pass in", + defaultName = selectedRenderPasses[0], + message = "

Renaming will only affect the current edit scope.

\nReferences elsewhere in the node graph may need to be updated manually." + ) + + renderPassName = dialogue.waitForRenderPassName( parentWindow = self.ancestor( GafferUI.Window ) ) + if renderPassName is not None and renderPassName != selectedRenderPasses[0] : + + nonEditableReason = GafferScene.EditScopeAlgo.renameRenderPassNonEditableReason( editScope, renderPassName ) + if nonEditableReason is not None : + self.__warningPopup( "Unable to rename", nonEditableReason ) + return + + with Gaffer.UndoScope( editScope.ancestor( Gaffer.ScriptNode ) ) : + GafferScene.EditScopeAlgo.renameRenderPass( editScope, selectedRenderPasses[0], renderPassName ) + + if self.settings()["displayGrouped"].getValue() : + renamedPath = GafferScene.ScenePlug.stringToPath( self.pathGroupingFunction()( renderPassName ) ) + renamedPath.append( renderPassName ) + else : + renamedPath = renderPassName + + self.__pathListing.setSelection( + [ IECore.PathMatcher( [ renamedPath ] ) if i == 0 else IECore.PathMatcher() for i in range( len( self.__pathListing.getSelection() ) ) ] + ) + def __disableRenderPasses( self, renderPasses, editScope ) : with Gaffer.UndoScope( editScope.ancestor( Gaffer.ScriptNode ) ) : @@ -863,7 +940,7 @@ def __drop( self, widget, event ) : class _RenderPassCreationDialogue( GafferUI.Dialogue ) : - def __init__( self, existingNames = [], editScope = None, title = "Add Render Pass", cancelLabel = "Cancel", confirmLabel = "Add", **kw ) : + def __init__( self, existingNames = [], editScope = None, title = "Add Render Pass", cancelLabel = "Cancel", confirmLabel = "Add", actionDescription = "Add render pass to", defaultName = "", message = "", **kw ) : GafferUI.Dialogue.__init__( self, title, sizeMode=GafferUI.Window.SizeMode.Fixed, **kw ) @@ -873,13 +950,24 @@ def __init__( self, existingNames = [], editScope = None, title = "Add Render Pa with self.__column : if editScope : with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 ) : - GafferUI.Label( "Add render pass to" ) + GafferUI.Label( actionDescription ) editScopeColor = Gaffer.Metadata.value( editScope, "nodeGadget:color" ) if editScopeColor : GafferUI.Image.createSwatch( editScopeColor ) GafferUI.Label( "

{}

".format( editScope.relativeName( editScope.ancestor( Gaffer.ScriptNode ) ) ) ) + if message != "" : + lines = message.split( "\n" ) + with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Vertical, spacing = 4 ) : + with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 ) : + GafferUI.Image( "infoSmall.png" ) + GafferUI.Label( "{}".format( lines[0] ) ) + for line in lines[1:] : + GafferUI.Label( "{}".format( line ) ) + self.__renderPassNameWidget = GafferSceneUI.RenderPassesUI.createRenderPassNameWidget() + if defaultName != "" : + self.__renderPassNameWidget.setRenderPassName( defaultName ) self._setWidget( self.__column )