diff --git a/Changes.md b/Changes.md index 4c123db8dca..f897d2f540a 100644 --- a/Changes.md +++ b/Changes.md @@ -11,11 +11,18 @@ Features - Inference : Loads ONNX models and performance inference using an array of input tensors. - ImageToTensor : Converts images to tensors for use with the Inference node. - TensorToImage : Converts tensors back to images following inference. +- Menu Bar : Added a "Render Pass" menu to the Menu Bar that can be used to choose the current render pass from those provided by the focus node. Improvements ------------ - MergeScenes : Removed unnecessary temporary contexts. +- PlugLayout : + - A warning widget is now displayed when an invalid custom widget is registered. + - `layout:customWidget::width` and `layout:customWidget::minimumWidth` metadata registrations are now supported for custom widgets. +- RenderPassEditor : + - Render passes deleted or disabled by render adaptors registered to `client = "RenderPassWedge"` are now shown as disabled. To differentiate these from user disabled render passes, an orange dot is shown in the corner of the disabled icon and the tooltip describes them as automatically disabled. + - Changing the current render pass is now undoable. Fixes ----- @@ -26,6 +33,7 @@ API --- - PlugLayout : Activations may now depend on the presence of certain plugs, as they are now reevaluated when child plugs are added and removed. +- ScriptNodeAlgo : Added `setCurrentRenderPass()`, `getCurrentRenderPass()`, and `acquireRenderPassPlug()` methods. 1.5.1.0 (relative to 1.5.0.1) ======= diff --git a/include/GafferSceneUI/ScriptNodeAlgo.h b/include/GafferSceneUI/ScriptNodeAlgo.h index 13d57eab047..7a743ca4ba3 100644 --- a/include/GafferSceneUI/ScriptNodeAlgo.h +++ b/include/GafferSceneUI/ScriptNodeAlgo.h @@ -40,6 +40,7 @@ #include "GafferScene/VisibleSet.h" +#include "Gaffer/NameValuePlug.h" #include "Gaffer/Signals.h" #include "IECore/PathMatcher.h" @@ -112,6 +113,16 @@ GAFFERSCENEUI_API std::vector getLastSelectedPath( const /// Returns a signal emitted when either the selected paths or last selected path change for `script`. GAFFERSCENEUI_API ChangedSignal &selectedPathsChangedSignal( Gaffer::ScriptNode *script ); +/// Render Passes +/// ============= + +/// Acquires a plug used to specify the current render pass for the script. +GAFFERSCENEUI_API Gaffer::NameValuePlug *acquireRenderPassPlug( Gaffer::ScriptNode *script, bool createIfMissing = true ); +/// Sets the current render pass for the script. +GAFFERSCENEUI_API void setCurrentRenderPass( Gaffer::ScriptNode *script, std::string renderPass ); +/// Returns the current render pass for the script. +GAFFERSCENEUI_API std::string getCurrentRenderPass( const Gaffer::ScriptNode *script ); + } // namespace ScriptNodeAlgo } // namespace GafferSceneUI diff --git a/python/GafferSceneUI/RenderPassEditor.py b/python/GafferSceneUI/RenderPassEditor.py index e09423df49a..6241ac653e1 100644 --- a/python/GafferSceneUI/RenderPassEditor.py +++ b/python/GafferSceneUI/RenderPassEditor.py @@ -35,16 +35,21 @@ ########################################################################## import collections +import functools import imath +import os import traceback import IECore import Gaffer import GafferUI +import GafferImage import GafferScene import GafferSceneUI +from GafferUI.PlugValueWidget import sole + from . import _GafferSceneUI from Qt import QtWidgets @@ -62,6 +67,12 @@ def __init__( self ) : self["editScope"] = Gaffer.Plug() self["displayGrouped"] = Gaffer.BoolPlug() + self["__adaptors"] = GafferSceneUI.RenderPassEditor._createRenderAdaptors() + self["__adaptors"]["in"].setInput( self["in"] ) + + self["__adaptedIn"] = GafferScene.ScenePlug() + self["__adaptedIn"].setInput( self["__adaptors"]["out"] ) + IECore.registerRunTimeTyped( Settings, typeName = "GafferSceneUI::RenderPassEditor::Settings" ) def __init__( self, scriptNode, **kw ) : @@ -219,6 +230,34 @@ def pathGroupingFunction() : return _GafferSceneUI._RenderPassEditor.RenderPassPath.pathGroupingFunction() + @staticmethod + def _createRenderAdaptors() : + + adaptors = GafferScene.SceneProcessor() + + adaptors["__renderAdaptors"] = GafferScene.SceneAlgo.createRenderAdaptors() + ## \todo We currently masquerade as the RenderPassWedge in order to include + # adaptors that disable render passes. We may want to find a more general + # client name for this usage... + adaptors["__renderAdaptors"]["client"].setValue( "RenderPassWedge" ) + adaptors["__renderAdaptors"]["in"].setInput( adaptors["in"] ) + + adaptors["__adaptorSwitch"] = Gaffer.Switch() + adaptors["__adaptorSwitch"].setup( GafferScene.ScenePlug() ) + adaptors["__adaptorSwitch"]["in"]["in0"].setInput( adaptors["in"] ) + adaptors["__adaptorSwitch"]["in"]["in1"].setInput( adaptors["__renderAdaptors"]["out"] ) + + adaptors["__contextQuery"] = Gaffer.ContextQuery() + adaptors["__contextQuery"].addQuery( Gaffer.BoolPlug( "enableAdaptors", defaultValue = False ) ) + adaptors["__contextQuery"]["queries"][0]["name"].setValue( "renderPassEditor:enableAdaptors" ) + + adaptors["__adaptorSwitch"]["index"].setInput( adaptors["__contextQuery"]["out"][0]["value"] ) + adaptors["__adaptorSwitch"]["deleteContextVariables"].setValue( "renderPassEditor:enableAdaptors" ) + + adaptors["out"].setInput( adaptors["__adaptorSwitch"]["out"] ) + + return adaptors + def __repr__( self ) : return "GafferSceneUI.RenderPassEditor( scriptNode )" @@ -260,7 +299,7 @@ def __setPathListingPath( self ) : # control of updates ourselves in _updateFromContext(), using LazyMethod to defer the calls to this # function until we are visible and playback has stopped. contextCopy = Gaffer.Context( self.context() ) - self.__pathListing.setPath( _GafferSceneUI._RenderPassEditor.RenderPassPath( self.settings()["in"], contextCopy, "/", filter = self.__filter, grouped = self.settings()["displayGrouped"].getValue() ) ) + self.__pathListing.setPath( _GafferSceneUI._RenderPassEditor.RenderPassPath( self.settings()["__adaptedIn"], contextCopy, "/", filter = self.__filter, grouped = self.settings()["displayGrouped"].getValue() ) ) def __displayGroupedChanged( self ) : @@ -360,17 +399,11 @@ def __setActiveRenderPass( self, pathListing ) : self.__popup.popup( parent = self ) return - ## \todo Perhaps we should add `ScriptNodeAlgo.set/getCurrentRenderPass()` - # to wrap this up for general consumption? - if "renderPass" not in script["variables"] : - renderPassPlug = Gaffer.NameValuePlug( "renderPass", "", "renderPass", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) - script["variables"].addChild( renderPassPlug ) - Gaffer.MetadataAlgo.setReadOnly( renderPassPlug["name"], True ) - else : - renderPassPlug = script["variables"]["renderPass"] - - currentRenderPass = renderPassPlug["value"].getValue() - renderPassPlug["value"].setValue( selectedPassNames[0] if selectedPassNames[0] != currentRenderPass else "" ) + with Gaffer.UndoScope( script ) : + GafferSceneUI.ScriptNodeAlgo.setCurrentRenderPass( + script, + selectedPassNames[0] if selectedPassNames[0] != GafferSceneUI.ScriptNodeAlgo.getCurrentRenderPass( script ) else "" + ) def __columnContextMenuSignal( self, column, pathListing, menuDefinition ) : @@ -898,3 +931,290 @@ def __combiner( results ) : ) # Remove circular references that would keep the widget in limbo. e.__traceback__ = None + +class RenderPassChooserWidget( GafferUI.Widget ) : + + def __init__( self, settingsNode, **kw ) : + + renderPassPlug = GafferSceneUI.ScriptNodeAlgo.acquireRenderPassPlug( settingsNode["__scriptNode"].getInput().node() ) + self.__renderPassPlugValueWidget = _RenderPassPlugValueWidget( + renderPassPlug["value"], + showLabel = True + ) + GafferUI.Widget.__init__( self, self.__renderPassPlugValueWidget, **kw ) + +RenderPassEditor.RenderPassChooserWidget = RenderPassChooserWidget + +class _RenderPassPlugValueWidget( GafferUI.PlugValueWidget ) : + + ## \todo We're cheekily reusing the Editor.Settings node here + # in order to take advantage of the existing hack allowing + # BackgroundTask to find the cancellation subject via the + # "__scriptNode" plug. This should be replaced with a cleaner + # way for BackgroundTask to recover the ScriptNode. + class Settings( GafferUI.Editor.Settings ) : + + def __init__( self ) : + + GafferUI.Editor.Settings.__init__( self ) + + self["in"] = GafferScene.ScenePlug() + self["__adaptors"] = GafferSceneUI.RenderPassEditor._createRenderAdaptors() + self["__adaptors"]["in"].setInput( self["in"] ) + + IECore.registerRunTimeTyped( Settings, typeName = "GafferSceneUI::RenderPassPlugValueWidget::Settings" ) + + def __init__( self, plug, showLabel = False, **kw ) : + + self.__settings = self.Settings() + self.__settings.setName( "RenderPassPlugValueWidgetSettings" ) + self.__settings["__scriptNode"].setInput( plug.node().scriptNode()["fileName"] ) + + self.__listContainer = GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 ) + + GafferUI.PlugValueWidget.__init__( self, self.__listContainer, plug, **kw ) + + with self.__listContainer : + if showLabel : + GafferUI.Label( "Render Pass" ) + self.__busyWidget = GafferUI.BusyWidget( size = 18 ) + self.__busyWidget.setVisible( False ) + self.__menuButton = GafferUI.MenuButton( + "", + menu = GafferUI.Menu( Gaffer.WeakMethod( self.__menuDefinition ) ), + highlightOnOver = False + ) + # Ignore the width in X so MenuButton width is limited by the overall width of the widget + self.__menuButton._qtWidget().setSizePolicy( QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Fixed ) + + self.__currentRenderPass = "" + self.__renderPasses = {} + + self.__displayGrouped = False + self.__hideDisabled = False + + self.__focusChangedConnection = plug.node().scriptNode().focusChangedSignal().connect( + Gaffer.WeakMethod( self.__focusChanged ), scoped = True + ) + + self.__updateSettingsInput() + self.__updateMenuButton() + + def __del__( self ) : + + # Remove connection to ScriptNode now, on the UI thread. + # See comment in `GafferUI.Editor.__del__()` for details. + self.__settings.plugDirtiedSignal().disconnectAllSlots() + self.__settings["__scriptNode"].setInput( None ) + + def getToolTip( self ) : + + if self.__currentRenderPass == "" : + return "No render pass is active." + + if self.__currentRenderPass not in self.__renderPasses.get( "all", [] ) : + return "{} is not provided by the focus node.".format( self.__currentRenderPass ) + else : + return "{} is the current render pass.".format( self.__currentRenderPass ) + + def _auxiliaryPlugs( self, plug ) : + + return [ self.__settings["__adaptors"]["out"]["globals"] ] + + @staticmethod + def _valuesForUpdate( plugs, auxiliaryPlugs ) : + + result = [] + + for plug, ( globalsPlug, ) in zip( plugs, auxiliaryPlugs ) : + + renderPasses = {} + + with Gaffer.Context( Gaffer.Context.current() ) as context : + context["renderPassEditor:enableAdaptors"] = True + adaptedRenderPassNames = globalsPlug.getValue().get( "option:renderPass:names", IECore.StringVectorData() ) + context["renderPassEditor:enableAdaptors"] = False + for renderPass in globalsPlug.getValue().get( "option:renderPass:names", IECore.StringVectorData() ) : + renderPasses.setdefault( "all", [] ).append( renderPass ) + context["renderPass"] = renderPass + context["renderPassEditor:enableAdaptors"] = True + if renderPass not in adaptedRenderPassNames : + # The render pass has been deleted by a render adaptor so present it as disabled + renderPasses.setdefault( "adaptorDisabled", [] ).append( renderPass ) + elif globalsPlug.getValue().get( "option:renderPass:enabled", IECore.BoolData( True ) ).value : + renderPasses.setdefault( "enabled", [] ).append( renderPass ) + else : + context["renderPassEditor:enableAdaptors"] = False + if globalsPlug.getValue().get( "option:renderPass:enabled", IECore.BoolData( True ) ).value : + renderPasses.setdefault( "adaptorDisabled", [] ).append( renderPass ) + + result.append( { + "value" : plug.getValue(), + "renderPasses" : renderPasses + } ) + + return result + + def _updateFromValues( self, values, exception ) : + + self.__currentRenderPass = sole( v["value"] for v in values ) + self.__renderPasses = sole( v["renderPasses"] for v in values ) + + if self.__currentRenderPass is not None : + self.__busyWidget.setVisible( False ) + self.__updateMenuButton() + + def _updateFromEditable( self ) : + + self.__menuButton.setEnabled( self._editable() ) + + def __setDisplayGrouped( self, grouped ) : + + self.__displayGrouped = grouped + + def __setHideDisabled( self, hide ) : + + self.__hideDisabled = hide + + def __menuDefinition( self ) : + + result = IECore.MenuDefinition() + + result.append( "/__RenderPassesDivider__", { "divider" : True, "label" : "Render Passes" } ) + + renderPasses = self.__renderPasses.get( "enabled", [] ) if self.__hideDisabled else self.__renderPasses.get( "all", [] ) + + if self.__renderPasses is None : + result.append( "/Refresh", { "command" : Gaffer.WeakMethod( self.__refreshMenu ) } ) + elif len( renderPasses ) == 0 : + result.append( "/No Render Passes Available", { "active" : False } ) + else : + groupingFn = GafferSceneUI.RenderPassEditor.pathGroupingFunction() + prefixes = IECore.PathMatcher() + if self.__displayGrouped : + for name in renderPasses : + prefixes.addPath( groupingFn( name ) ) + + for name in sorted( renderPasses ) : + + prefix = "/" + if self.__displayGrouped : + if prefixes.match( name ) & IECore.PathMatcher.Result.ExactMatch : + prefix += name + else : + prefix = groupingFn( name ) + + result.append( + os.path.join( prefix, name ), + { + "command" : functools.partial( Gaffer.WeakMethod( self.__setCurrentRenderPass ), name ), + "icon" : self.__renderPassIcon( name, activeIndicator = True ), + "description" : self.__renderPassDescription( name ) + } + ) + + result.append( "/__NoneDivider__", { "divider" : True } ) + + result.append( + "/None", + { + "command" : functools.partial( Gaffer.WeakMethod( self.__setCurrentRenderPass ), "" ), + "icon" : "activeRenderPass.png" if self.__currentRenderPass == "" else None, + } + ) + + result.append( "/__OptionsDivider__", { "divider" : True, "label" : "Options" } ) + + result.append( + "/Display Grouped", + { + "checkBox" : self.__displayGrouped, + "command" : functools.partial( Gaffer.WeakMethod( self.__setDisplayGrouped ) ), + "description" : "Toggle grouped display of render passes." + } + ) + + result.append( + "/Hide Disabled", + { + "checkBox" : self.__hideDisabled, + "command" : functools.partial( Gaffer.WeakMethod( self.__setHideDisabled ) ), + "description" : "Hide render passes disabled for rendering." + } + ) + + return result + + def __refreshMenu( self ) : + + self.__busyWidget.setVisible( True ) + + def __setCurrentRenderPass( self, renderPass, *unused ) : + + with Gaffer.UndoScope( self.scriptNode() ) : + for plug in self.getPlugs() : + plug.setValue( renderPass ) + + def __renderPassDescription( self, renderPass ) : + + if renderPass == "" : + return "" + + if renderPass in self.__renderPasses.get( "adaptorDisabled", [] ) : + return "{} has been automatically disabled by a render adaptor.".format( renderPass ) + elif renderPass not in self.__renderPasses.get( "enabled", [] ) : + return "{} has been disabled.".format( renderPass ) + + return "" + + def __renderPassIcon( self, renderPass, activeIndicator = False ) : + + if renderPass == "" : + return None + + if activeIndicator and renderPass == self.__currentRenderPass : + return "activeRenderPass.png" + elif renderPass not in self.__renderPasses.get( "all", [] ) : + return "warningSmall.png" + elif renderPass in self.__renderPasses.get( "enabled", [] ) : + return "renderPass.png" + elif renderPass in self.__renderPasses.get( "adaptorDisabled", [] ) : + return "adaptorDisabledRenderPass.png" + else : + return "disabledRenderPass.png" + + def __updateMenuButton( self ) : + + self.__menuButton.setText( self.__currentRenderPass or "None" ) + self.__menuButton.setImage( self.__renderPassIcon( self.__currentRenderPass ) ) + + def __focusChanged( self, scriptNode, node ) : + + self.__updateSettingsInput() + + def __updateSettingsInput( self ) : + + self.__settings["in"].setInput( self.__scenePlugFromFocus() ) + + def __scenePlugFromFocus( self ) : + + focusNode = self.getPlug().node().scriptNode().getFocus() + + if focusNode is not None : + outputScene = next( + ( p for p in GafferScene.ScenePlug.RecursiveOutputRange( focusNode ) if not p.getName().startswith( "__" ) ), + None + ) + if outputScene is not None : + return outputScene + + outputImage = next( + ( p for p in GafferImage.ImagePlug.RecursiveOutputRange( focusNode ) if not p.getName().startswith( "__" ) ), + None + ) + if outputImage is not None : + return GafferScene.SceneAlgo.sourceScene( outputImage ) + + return None + +RenderPassEditor._RenderPassPlugValueWidget = _RenderPassPlugValueWidget diff --git a/python/GafferSceneUITest/RenderPassEditorTest.py b/python/GafferSceneUITest/RenderPassEditorTest.py index 430b2956bf6..83c7e1d97e8 100644 --- a/python/GafferSceneUITest/RenderPassEditorTest.py +++ b/python/GafferSceneUITest/RenderPassEditorTest.py @@ -167,6 +167,88 @@ def testFn( name ) : else : self.assertIsNone( inspectionContext ) + def testRenderPassPathAdaptorDisablingPasses( self ) : + + def createAdaptor() : + + node = GafferScene.SceneProcessor() + node["options"] = GafferScene.CustomOptions() + node["options"]["in"].setInput( node["in"] ) + node["options"]["options"].addChild( Gaffer.NameValuePlug( "renderPass:enabled", False ) ) + + node["switch"] = Gaffer.NameSwitch() + node["switch"].setup( node["options"]["out"] ) + node["switch"]["in"][0]["value"].setInput( node["in"] ) + node["switch"]["in"][1]["value"].setInput( node["options"]["out"] ) + node["switch"]["in"][1]["name"].setValue( "B C" ) + node["switch"]["selector"].setValue( "${renderPass}" ) + + node["out"].setInput( node["switch"]["out"]["value"] ) + + return node + + GafferScene.SceneAlgo.registerRenderAdaptor( "RenderPassEditorTest", createAdaptor, client = "RenderPassWedge" ) + self.addCleanup( GafferScene.SceneAlgo.deregisterRenderAdaptor, "RenderPassEditorTest" ) + + renderPasses = GafferScene.RenderPasses() + renderPasses["names"].setValue( IECore.StringVectorData( [ "A", "B", "C", "D" ] ) ) + + adaptors = GafferSceneUI.RenderPassEditor._createRenderAdaptors() + adaptors["in"].setInput( renderPasses["out"] ) + + context = Gaffer.Context() + path = _GafferSceneUI._RenderPassEditor.RenderPassPath( adaptors["out"], context, "/" ) + + self.assertEqual( [ str( c ) for c in path.children() ], [ "/A", "/B", "/C", "/D" ] ) + + pathCopy = path.copy() + c = _GafferSceneUI._RenderPassEditor.RenderPassNameColumn() + + for p in [ "/A", "/B", "/C", "/D" ] : + pathCopy.setFromString( p ) + cellData = c.cellData( pathCopy, None ) + if p in ( "/A", "/D" ) : + self.assertEqual( cellData.icon, "renderPass.png" ) + self.assertEqual( cellData.toolTip, None ) + else : + self.assertEqual( cellData.icon, "adaptorDisabledRenderPass.png" ) + self.assertEqual( cellData.toolTip, "Automatically disabled by a render adaptor." ) + + def testRenderPassPathAdaptorDeletingPasses( self ) : + + def createAdaptor() : + + node = GafferScene.DeleteRenderPasses() + node["names"].setValue( "B C" ) + return node + + GafferScene.SceneAlgo.registerRenderAdaptor( "RenderPassEditorTest", createAdaptor, client = "RenderPassWedge" ) + self.addCleanup( GafferScene.SceneAlgo.deregisterRenderAdaptor, "RenderPassEditorTest" ) + + renderPasses = GafferScene.RenderPasses() + renderPasses["names"].setValue( IECore.StringVectorData( [ "A", "B", "C", "D" ] ) ) + + adaptors = GafferSceneUI.RenderPassEditor._createRenderAdaptors() + adaptors["in"].setInput( renderPasses["out"] ) + + context = Gaffer.Context() + path = _GafferSceneUI._RenderPassEditor.RenderPassPath( adaptors["out"], context, "/" ) + + self.assertEqual( [ str( c ) for c in path.children() ], [ "/A", "/B", "/C", "/D" ] ) + + pathCopy = path.copy() + c = _GafferSceneUI._RenderPassEditor.RenderPassNameColumn() + + for p in [ "/A", "/B", "/C", "/D" ] : + pathCopy.setFromString( p ) + cellData = c.cellData( pathCopy, None ) + if p in ( "/A", "/D" ) : + self.assertEqual( cellData.icon, "renderPass.png" ) + self.assertEqual( cellData.toolTip, None ) + else : + self.assertEqual( cellData.icon, "adaptorDisabledRenderPass.png" ) + self.assertEqual( cellData.toolTip, "Automatically disabled by a render adaptor." ) + def testSearchFilter( self ) : renderPasses = GafferScene.RenderPasses() @@ -224,6 +306,52 @@ def testDisabledRenderPassFilter( self ) : self.assertEqual( [ str( c ) for c in path.children() ], [ "/A", "/B", "/C", "/D" ] ) + def testDisabledRenderPassFilterWithAdaptor( self ) : + + def createAdaptor() : + + node = GafferScene.SceneProcessor() + node["options"] = GafferScene.CustomOptions() + node["options"]["in"].setInput( node["in"] ) + node["options"]["options"].addChild( Gaffer.NameValuePlug( "renderPass:enabled", False ) ) + + node["switch"] = Gaffer.NameSwitch() + node["switch"].setup( node["options"]["out"] ) + node["switch"]["in"][0]["value"].setInput( node["in"] ) + node["switch"]["in"][1]["value"].setInput( node["options"]["out"] ) + node["switch"]["in"][1]["name"].setValue( "B" ) + node["switch"]["selector"].setValue( "${renderPass}" ) + + node["delete"] = GafferScene.DeleteRenderPasses() + node["delete"]["names"].setValue( "C" ) + node["delete"]["in"].setInput( node["switch"]["out"]["value"] ) + + node["out"].setInput( node["delete"]["out"] ) + + return node + + GafferScene.SceneAlgo.registerRenderAdaptor( "RenderPassEditorTest", createAdaptor, client = "RenderPassWedge" ) + self.addCleanup( GafferScene.SceneAlgo.deregisterRenderAdaptor, "RenderPassEditorTest" ) + + renderPasses = GafferScene.RenderPasses() + renderPasses["names"].setValue( IECore.StringVectorData( ["A", "B", "C", "D"] ) ) + + adaptors = GafferSceneUI.RenderPassEditor._createRenderAdaptors() + adaptors["in"].setInput( renderPasses["out"] ) + + context = Gaffer.Context() + path = _GafferSceneUI._RenderPassEditor.RenderPassPath( adaptors["out"], context, "/" ) + self.assertEqual( [ str( c ) for c in path.children() ], [ "/A", "/B", "/C", "/D" ] ) + + disabledRenderPassFilter = _GafferSceneUI._RenderPassEditor.DisabledRenderPassFilter() + path.setFilter( disabledRenderPassFilter ) + + self.assertEqual( [ str( c ) for c in path.children() ], [ "/A", "/D" ] ) + + disabledRenderPassFilter.setEnabled( False ) + + self.assertEqual( [ str( c ) for c in path.children() ], [ "/A", "/B", "/C", "/D" ] ) + def testPathGroupingFunction( self ) : renderPasses = GafferScene.RenderPasses() diff --git a/python/GafferSceneUITest/ScriptNodeAlgoTest.py b/python/GafferSceneUITest/ScriptNodeAlgoTest.py index c2cb0500f9c..9cee235a184 100644 --- a/python/GafferSceneUITest/ScriptNodeAlgoTest.py +++ b/python/GafferSceneUITest/ScriptNodeAlgoTest.py @@ -17,7 +17,7 @@ # # * Neither the name of John Haddon nor the names of # any other contributors to this software may be used to endorse or -# promote products derived from this software without specifiscript prior +# promote products derived from this software without specific prior # written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS @@ -204,5 +204,62 @@ def testVisibleSetExpansionUtilities( self ) : self.assertEqual( GafferSceneUI.ScriptNodeAlgo.getVisibleSet( script ).expansions, IECore.PathMatcher( [ "/", "/A", "/A/C" ] ) ) self.assertEqual( newLeafs, IECore.PathMatcher( [ "/A/C/G", "/A/C/F" ] ) ) + def testAcquireRenderPassPlug( self ) : + + s1 = Gaffer.ScriptNode() + s2 = Gaffer.ScriptNode() + + self.assertIsNone( GafferSceneUI.ScriptNodeAlgo.acquireRenderPassPlug( s1, createIfMissing = False ) ) + + p1A = GafferSceneUI.ScriptNodeAlgo.acquireRenderPassPlug( s1 ) + p1B = GafferSceneUI.ScriptNodeAlgo.acquireRenderPassPlug( s1 ) + + p2A = GafferSceneUI.ScriptNodeAlgo.acquireRenderPassPlug( s2 ) + p2B = GafferSceneUI.ScriptNodeAlgo.acquireRenderPassPlug( s2 ) + + self.assertIsNotNone( p1A ) + self.assertIsNotNone( p2A ) + + self.assertTrue( p1A.isSame( p1B ) ) + self.assertTrue( p2A.isSame( p2B ) ) + self.assertFalse( p1A.isSame( p2A ) ) + + def testAcquireManuallyCreatedRenderPassPlug( self ) : + + s = Gaffer.ScriptNode() + s["variables"]["renderPass"] = Gaffer.NameValuePlug( "renderPass", "", "renderPass" ) + + self.assertTrue( GafferSceneUI.ScriptNodeAlgo.acquireRenderPassPlug( s ).isSame( s["variables"]["renderPass"] ) ) + + s1 = Gaffer.ScriptNode() + s1["variables"]["renderPass"] = Gaffer.NameValuePlug( "renderPass", IECore.IntData( 0 ), "renderPass" ) + + self.assertRaises( IECore.Exception, GafferSceneUI.ScriptNodeAlgo.acquireRenderPassPlug, s1 ) + + def testSetCurrentRenderPass( self ) : + + script = Gaffer.ScriptNode() + self.assertNotIn( "renderPass", script["variables"] ) + + GafferSceneUI.ScriptNodeAlgo.setCurrentRenderPass( script, "testA" ) + self.assertIn( "renderPass", script["variables"] ) + self.assertEqual( "testA", script["variables"]["renderPass"]["value"].getValue() ) + + script2 = Gaffer.ScriptNode() + script2["variables"]["renderPass"] = Gaffer.NameValuePlug( "renderPass", 123.0, "renderPass" ) + self.assertRaises( IECore.Exception, GafferSceneUI.ScriptNodeAlgo.setCurrentRenderPass, script2, "testB" ) + + def testGetCurrentRenderPass( self ) : + + script = Gaffer.ScriptNode() + self.assertEqual( "", GafferSceneUI.ScriptNodeAlgo.getCurrentRenderPass( script ) ) + + GafferSceneUI.ScriptNodeAlgo.setCurrentRenderPass( script, "testA" ) + self.assertEqual( "testA", GafferSceneUI.ScriptNodeAlgo.getCurrentRenderPass( script ) ) + + script2 = Gaffer.ScriptNode() + script2["variables"]["renderPass"] = Gaffer.NameValuePlug( "renderPass", 123.0, "renderPass" ) + self.assertRaises( IECore.Exception, GafferSceneUI.ScriptNodeAlgo.getCurrentRenderPass, script2 ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferUI/PlugLayout.py b/python/GafferUI/PlugLayout.py index ecabda5c7e5..a600073e12c 100644 --- a/python/GafferUI/PlugLayout.py +++ b/python/GafferUI/PlugLayout.py @@ -390,22 +390,26 @@ def __import( self, path ) : return result + def __setWidthFromMetadata( self, widget, item ) : + + width = self.__itemMetadataValue( item, "width" ) + if width is not None : + widget._qtWidget().setFixedWidth( width ) + + minimumWidth = self.__itemMetadataValue( item, "minimumWidth" ) + if minimumWidth is not None : + widget._qtWidget().setMinimumWidth( minimumWidth ) + + if widget._qtWidget().layout() is not None and ( width is not None or minimumWidth is not None ) : + widget._qtWidget().layout().setSizeConstraint( QtWidgets.QLayout.SetDefaultConstraint ) + def __createPlugWidget( self, plug ) : result = GafferUI.PlugValueWidget.create( plug ) if result is None : return result - width = self.__itemMetadataValue( plug, "width" ) - if width is not None : - result._qtWidget().setFixedWidth( width ) - - minimumWidth = self.__itemMetadataValue( plug, "minimumWidth" ) - if minimumWidth is not None : - result._qtWidget().setMinimumWidth( minimumWidth ) - - if result._qtWidget().layout() is not None and ( width is not None or minimumWidth is not None ) : - result._qtWidget().layout().setSizeConstraint( QtWidgets.QLayout.SetDefaultConstraint ) + self.__setWidthFromMetadata( result, plug ) if isinstance( result, GafferUI.PlugValueWidget ) and not result.hasLabel() and self.__itemMetadataValue( plug, "label" ) != "" : result = GafferUI.PlugWidget( result ) @@ -426,9 +430,15 @@ def __createPlugWidget( self, plug ) : def __createCustomWidget( self, name ) : widgetType = self.__itemMetadataValue( name, "widgetType" ) - widgetClass = self.__import( widgetType ) + try : + widgetClass = self.__import( widgetType ) + result = widgetClass( self.__parent ) + self.__setWidthFromMetadata( result, name ) + except Exception as e : + message = "Could not create custom widget \"{}\" : {}".format( name, str( e ) ) + IECore.msg( IECore.Msg.Level.Error, "GafferUI.PlugLayout", message ) - result = widgetClass( self.__parent ) + result = _MissingCustomWidget( self.__parent, message ) return result @@ -753,3 +763,16 @@ def update( self, section ) : def __collapsibleStateChanged( self, collapsible, subsection ) : subsection.saveState( "collapsed", collapsible.getCollapsed() ) + +class _MissingCustomWidget( GafferUI.Widget ) : + + def __init__( self, parent, warning, **kw ) : + + self.__image = GafferUI.Image( "warningSmall.png" ) + self.__warning = warning + + GafferUI.Widget.__init__( self, self.__image, **kw ) + + def getToolTip( self ) : + + return self.__warning diff --git a/python/GafferUI/ScriptWindow.py b/python/GafferUI/ScriptWindow.py index 6d715cc47f5..63a2671ef8a 100644 --- a/python/GafferUI/ScriptWindow.py +++ b/python/GafferUI/ScriptWindow.py @@ -167,6 +167,7 @@ def __closed( self, widget ) : if scriptParent is not None : scriptParent.removeChild( self.__script ) + __automaticallyCreatedInstances = [] # strong references to instances made by acquire() __instances = [] # weak references to all instances - used by acquire() ## Returns the ScriptWindow for the specified script, creating one # if necessary. @@ -178,7 +179,13 @@ def acquire( script, createIfNecessary=True ) : if scriptWindow is not None and scriptWindow.scriptNode().isSame( script ) : return scriptWindow - return ScriptWindow( script ) if createIfNecessary else None + if createIfNecessary : + w = ScriptWindow( script ) + if ScriptWindow.__connected( script.ancestor( Gaffer.ApplicationRoot ) ) : + ScriptWindow.__automaticallyCreatedInstances.append( w ) + return w + + return None ## Returns an IECore.MenuDefinition which is used to define the menu bars for all ScriptWindows # created as part of the specified application. This can be edited at any time to modify subsequently @@ -208,17 +215,22 @@ def menuDefinition( applicationOrApplicationRoot ) : @classmethod def connect( cls, applicationRoot ) : - applicationRoot["scripts"].childAddedSignal().connectFront( ScriptWindow.__scriptAdded ) - applicationRoot["scripts"].childRemovedSignal().connect( ScriptWindow.__staticScriptRemoved ) + applicationRoot._scriptWindowChildAddedConnection = applicationRoot["scripts"].childAddedSignal().connect( ScriptWindow.__scriptAdded ) + applicationRoot._scriptWindowChildRemovedConnection = applicationRoot["scripts"].childRemovedSignal().connect( ScriptWindow.__staticScriptRemoved ) + + @staticmethod + def __connected( applicationRoot ) : + + childAddedConnection = getattr( applicationRoot, "_scriptWindowChildAddedConnection", None ) + childRemovedConnection = getattr( applicationRoot, "_scriptWindowChildRemovedConnection", None ) + return childAddedConnection is not None and childRemovedConnection is not None and childRemovedConnection.connected() and childRemovedConnection.connected() - __automaticallyCreatedInstances = [] # strong references to instances made by __scriptAdded() @staticmethod def __scriptAdded( scriptContainer, script ) : - w = ScriptWindow( script ) + w = ScriptWindow.acquire( script ) w.setVisible( True ) w.getLayout().restoreWindowState() - ScriptWindow.__automaticallyCreatedInstances.append( w ) @staticmethod def __staticScriptRemoved( scriptContainer, script ) : diff --git a/python/GafferUI/_StyleSheet.py b/python/GafferUI/_StyleSheet.py index fc8181e41a3..377a7f688c4 100644 --- a/python/GafferUI/_StyleSheet.py +++ b/python/GafferUI/_StyleSheet.py @@ -1307,6 +1307,12 @@ def styleColor( key ) : padding-bottom: 0px; } + *[gafferClass="GafferSceneUI.RenderPassEditor"] QTreeView::item { + min-height: 22px; + padding-top: 0px; + padding-bottom: 0px; + } + *[gafferClass="GafferSceneUI._HistoryWindow"] QTreeView::item { height: 18px; padding-top: 0px; @@ -1532,7 +1538,24 @@ def styleColor( key ) : padding: 2px; } - *[gafferClass="GafferUI.EditScopeUI.EditScopePlugValueWidget"] QPushButton[gafferWithFrame="true"][gafferMenuIndicator="true"] + *[gafferClass="GafferSceneUI.RenderPassEditor._RenderPassPlugValueWidget"] QPushButton[gafferWithFrame="true"][gafferMenuIndicator="true"] + { + min-height: 14px; + } + + #gafferMenuBarWidgetContainer QPushButton[gafferWithFrame="true"][gafferMenuIndicator="true"] + { + border: 1px solid rgb( 70, 70, 70 ); + border-top-color: rgb( 108, 108, 108 ); + border-left-color: rgb( 108, 108, 108 ); + background-color : qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 rgb( 108, 108, 108 ), stop: 0.1 rgb( 91, 91, 91 ), stop: 0.90 rgb( 81, 81, 81 )); + margin-top: 2px; + margin-bottom: 2px; + min-height: 14px; + } + + *[gafferClass="GafferUI.EditScopeUI.EditScopePlugValueWidget"] QPushButton[gafferWithFrame="true"][gafferMenuIndicator="true"], + #gafferMenuBarWidgetContainer *[gafferClass="GafferUI.EditScopeUI.EditScopePlugValueWidget"] QPushButton[gafferWithFrame="true"][gafferMenuIndicator="true"] { border: 1px solid rgb( 46, 75, 107 ); border-top-color: rgb( 75, 113, 155 ); @@ -1540,6 +1563,7 @@ def styleColor( key ) : background-color : qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 rgb( 69, 113, 161 ), stop: 0.1 rgb( 48, 99, 153 ), stop: 0.90 rgb( 54, 88, 125 )); margin-top: 2px; margin-bottom: 2px; + min-height: 14px; } *[gafferClass="GafferSceneUI.InteractiveRenderUI._ViewRenderControlUI"] QPushButton[gafferWithFrame="true"] { diff --git a/python/GafferUITest/ScriptWindowTest.py b/python/GafferUITest/ScriptWindowTest.py index eac295f0fc2..0db0c6e2087 100644 --- a/python/GafferUITest/ScriptWindowTest.py +++ b/python/GafferUITest/ScriptWindowTest.py @@ -87,6 +87,38 @@ def testAcquire( self ) : w6 = GafferUI.ScriptWindow.acquire( s3, createIfNecessary = True ) self.assertTrue( w6.scriptNode().isSame( s3 ) ) + def testLifetimeOfApplicationScriptWindows( self ) : + + class testApp( Gaffer.Application ) : + + def __init__( self ) : + + Gaffer.Application.__init__( self ) + + def __scriptAdded( scriptContainer, script ) : + + w = GafferUI.ScriptWindow.acquire( script ) + w.setTitle( "modified" ) + self.assertEqual( w.getTitle(), "modified" ) + + a = testApp().root() + GafferUI.ScriptWindow.connect( a ) + + # Acquire and modify the ScriptWindow before it is + # shown by the application to ensure that our modified + # ScriptWindow survives to be the one shown. + a["scripts"].childAddedSignal().connectFront( __scriptAdded ) + + s = Gaffer.ScriptNode() + a["scripts"]["s"] = s + + self.waitForIdle( 1000 ) + + w = GafferUI.ScriptWindow.acquire( s ) + self.assertEqual( w.getTitle(), "modified" ) + + del a["scripts"]["s"] + def testTitleChangedSignal( self ) : self.__title = "" diff --git a/resources/graphics.py b/resources/graphics.py index b3345204c5b..c73f7a6786b 100644 --- a/resources/graphics.py +++ b/resources/graphics.py @@ -461,14 +461,15 @@ "renderPassEditor" : { "options" : { - "requiredWidth" : 16, - "requiredHeight" : 16, + "requiredWidth" : 14, + "requiredHeight" : 14, "validatePixelAlignment" : True }, "ids" : [ "renderPass", "disabledRenderPass", + "adaptorDisabledRenderPass", "renderPassFolder", "activeRenderPass", "activeRenderPassFadedHighlighted", diff --git a/resources/graphics.svg b/resources/graphics.svg index f94c73565e8..98e62ff3e2a 100644 --- a/resources/graphics.svg +++ b/resources/graphics.svg @@ -3495,43 +3495,51 @@ + + + + + + ScriptNodeAlgo::getLastSelectedPath( const G ScriptNodeAlgo::ChangedSignal &ScriptNodeAlgo::selectedPathsChangedSignal( Gaffer::ScriptNode *script ) { return changedSignals( script ).selectedPathsChangedSignal; +} + +NameValuePlug *ScriptNodeAlgo::acquireRenderPassPlug( Gaffer::ScriptNode *script, bool createIfMissing ) +{ + if( const auto renderPassPlug = script->variablesPlug()->getChild( "renderPass" ) ) + { + if( renderPassPlug->valuePlug() ) + { + return renderPassPlug; + } + else + { + throw IECore::Exception( fmt::format( "Plug type of {} is {}, but must be StringPlug", renderPassPlug->valuePlug()->fullName(), renderPassPlug->valuePlug()->typeName() ) ); + } + } + + if( createIfMissing ) + { + auto renderPassPlug = new NameValuePlug( "renderPass", new StringPlug(), "renderPass", Gaffer::Plug::Flags::Default | Gaffer::Plug::Flags::Dynamic ); + MetadataAlgo::setReadOnly( renderPassPlug->namePlug(), true ); + script->variablesPlug()->addChild( renderPassPlug ); + + return renderPassPlug; + } + + return nullptr; +} + +void ScriptNodeAlgo::setCurrentRenderPass( Gaffer::ScriptNode *script, std::string renderPass ) +{ + auto renderPassPlug = acquireRenderPassPlug( script ); + renderPassPlug->valuePlug()->setValue( renderPass ); +} + +std::string ScriptNodeAlgo::getCurrentRenderPass( const Gaffer::ScriptNode *script ) +{ + if( const auto renderPassPlug = acquireRenderPassPlug( const_cast( script ), /* createIfMissing = */ false ) ) + { + return renderPassPlug->valuePlug()->getValue(); + } + return ""; } diff --git a/src/GafferSceneUIModule/RenderPassEditorBinding.cpp b/src/GafferSceneUIModule/RenderPassEditorBinding.cpp index 32bdbb185b7..67975bda4e2 100644 --- a/src/GafferSceneUIModule/RenderPassEditorBinding.cpp +++ b/src/GafferSceneUIModule/RenderPassEditorBinding.cpp @@ -192,9 +192,10 @@ PathMatcher pathMatcherCacheGetter( const PathMatcherCacheGetterKey &key, size_t } using PathMatcherCache = IECorePreview::LRUCache; -PathMatcherCache g_pathMatcherCache( pathMatcherCacheGetter, 25 ); +PathMatcherCache g_pathMatcherCache( pathMatcherCacheGetter, 50 ); const InternedString g_renderPassContextName( "renderPass" ); +const InternedString g_enableAdaptorsContextName( "renderPassEditor:enableAdaptors" ); const InternedString g_renderPassNamePropertyName( "renderPassPath:name" ); const InternedString g_renderPassEnabledPropertyName( "renderPassPath:enabled" ); const InternedString g_renderPassNamesOption( "option:renderPass:names" ); @@ -452,9 +453,12 @@ RenderPassPath::Ptr constructor2( ScenePlug &scene, Context &context, const std: // RenderPassNameColumn ////////////////////////////////////////////////////////////////////////// +ConstStringDataPtr g_adaptorDisabledRenderPassIcon = new StringData( "adaptorDisabledRenderPass.png" ); ConstStringDataPtr g_disabledRenderPassIcon = new StringData( "disabledRenderPass.png" ); ConstStringDataPtr g_renderPassIcon = new StringData( "renderPass.png" ); ConstStringDataPtr g_renderPassFolderIcon = new StringData( "renderPassFolder.png" ); +ConstStringDataPtr g_disabledToolTip = new StringData( "Disabled." ); +ConstStringDataPtr g_adaptorDisabledToolTip = new StringData( "Automatically disabled by a render adaptor."); const Color4fDataPtr g_dimmedForegroundColor = new Color4fData( Imath::Color4f( 152, 152, 152, 255 ) / 255.0f ); class RenderPassNameColumn : public StandardPathColumn @@ -473,23 +477,48 @@ class RenderPassNameColumn : public StandardPathColumn { CellData result = StandardPathColumn::cellData( path, canceller ); - const auto renderPassName = runTimeCast( path.property( g_renderPassNamePropertyName, canceller ) ); - if( !renderPassName ) + if( !runTimeCast( path.property( g_renderPassNamePropertyName, canceller ) ) ) { result.icon = g_renderPassFolderIcon; + return result; } - else + + // Enable render adaptors as they may have disabled or deleted render passes. + auto pathCopy = runTimeCast( path.copy() ); + if( !pathCopy ) { - if( const auto renderPassEnabled = runTimeCast( path.property( g_renderPassEnabledPropertyName, canceller ) ) ) - { - result.icon = renderPassEnabled->readable() ? g_renderPassIcon : g_disabledRenderPassIcon; - result.foreground = renderPassEnabled->readable() ? nullptr : g_dimmedForegroundColor; - } - else - { - result.icon = g_renderPassIcon; - } + return result; } + ContextPtr adaptorEnabledContext = new Context( *pathCopy->getContext() ); + adaptorEnabledContext->set( g_enableAdaptorsContextName, true ); + pathCopy->setContext( adaptorEnabledContext ); + + bool enabled = true; + if( !runTimeCast( pathCopy->property( g_renderPassNamePropertyName, canceller ) ) ) + { + // The render pass has been deleted by a render adaptor, so present it to the user as disabled. + enabled = false; + } + else if( const auto enabledData = runTimeCast( pathCopy->property( g_renderPassEnabledPropertyName, canceller ) ) ) + { + enabled = enabledData->readable(); + } + + if( enabled ) + { + result.icon = g_renderPassIcon; + return result; + } + + // Check `renderPass:enabled` without render adaptors enabled + // to determine whether the render pass was disabled upstream + // or by a render adaptor. + const auto enabledData = runTimeCast( path.property( g_renderPassEnabledPropertyName, canceller ) ); + enabled = !enabledData || enabledData->readable(); + + result.icon = enabled ? g_adaptorDisabledRenderPassIcon : g_disabledRenderPassIcon; + result.toolTip = enabled ? g_adaptorDisabledToolTip : g_disabledToolTip; + result.foreground = g_dimmedForegroundColor; return result; } @@ -692,10 +721,21 @@ class DisabledRenderPassFilter : public Gaffer::PathFilter leaf = std::all_of( c.begin(), c.end(), [this, canceller] ( const auto &p ) { return remove( p, canceller ); } ); } + // Enable render adaptors so we remove any render passes + // that have been disabled or deleted by them. + auto pathCopy = runTimeCast( path->copy() ); + if( !pathCopy ) + { + return true; + } + ContextPtr adaptorEnabledContext = new Context( *pathCopy->getContext() ); + adaptorEnabledContext->set( g_enableAdaptorsContextName, true ); + pathCopy->setContext( adaptorEnabledContext ); + bool enabled = false; - if( runTimeCast( path->property( g_renderPassNamePropertyName, canceller ) ) ) + if( runTimeCast( pathCopy->property( g_renderPassNamePropertyName, canceller ) ) ) { - if( const auto enabledData = IECore::runTimeCast( path->property( g_renderPassEnabledPropertyName, canceller ) ) ) + if( const auto enabledData = IECore::runTimeCast( pathCopy->property( g_renderPassEnabledPropertyName, canceller ) ) ) { enabled = enabledData->readable(); } diff --git a/src/GafferSceneUIModule/ScriptNodeAlgoBinding.cpp b/src/GafferSceneUIModule/ScriptNodeAlgoBinding.cpp index 6f0f79ddad7..ed3374d1ab1 100644 --- a/src/GafferSceneUIModule/ScriptNodeAlgoBinding.cpp +++ b/src/GafferSceneUIModule/ScriptNodeAlgoBinding.cpp @@ -98,6 +98,23 @@ std::string getLastSelectedPathWrapper( const ScriptNode &script ) return result; } +NameValuePlugPtr acquireRenderPassPlugWrapper( Gaffer::ScriptNode &script, bool createIfMissing ) +{ + IECorePython::ScopedGILRelease gilRelease; + return acquireRenderPassPlug( &script, createIfMissing ); +} + +void setCurrentRenderPassWrapper( ScriptNode &script, const std::string &renderPass ) +{ + IECorePython::ScopedGILRelease gilRelease; + setCurrentRenderPass( &script, renderPass ); +} + +std::string getCurrentRenderPassWrapper( ScriptNode &script ) +{ + return getCurrentRenderPass( &script ); +} + } // namespace void GafferSceneUIModule::bindScriptNodeAlgo() @@ -117,4 +134,7 @@ void GafferSceneUIModule::bindScriptNodeAlgo() def( "setLastSelectedPath", &setLastSelectedPathWrapper ); def( "getLastSelectedPath", &getLastSelectedPathWrapper ); def( "selectedPathsChangedSignal", &selectedPathsChangedSignal, return_value_policy() ); + def( "acquireRenderPassPlug", &acquireRenderPassPlugWrapper, ( arg( "script" ), arg( "createIfMissing" ) = true ) ); + def( "setCurrentRenderPass", &setCurrentRenderPassWrapper ); + def( "getCurrentRenderPass", &getCurrentRenderPassWrapper ); } diff --git a/startup/gui/project.py b/startup/gui/project.py index da3f6e00987..dac015353e5 100644 --- a/startup/gui/project.py +++ b/startup/gui/project.py @@ -45,6 +45,8 @@ import GafferDispatch import GafferTractor +import GafferSceneUI + ########################################################################## # Note this file is shared with the `dispatch` app. We need to ensure any # changes here have the desired behaviour in both applications. @@ -67,6 +69,9 @@ def __scriptAdded( container, script ) : GafferImage.FormatPlug.acquireDefaultFormatPlug( script ) + renderPassPlug = GafferSceneUI.ScriptNodeAlgo.acquireRenderPassPlug( script ) + Gaffer.Metadata.registerValue( renderPassPlug["value"], "plugValueWidget:type", "GafferSceneUI.RenderPassEditor._RenderPassPlugValueWidget" ) + application.root()["scripts"].childAddedSignal().connect( __scriptAdded ) ########################################################################## diff --git a/startup/gui/renderPassEditor.py b/startup/gui/renderPassEditor.py index ea2278851bb..97855d566b4 100644 --- a/startup/gui/renderPassEditor.py +++ b/startup/gui/renderPassEditor.py @@ -38,6 +38,8 @@ import IECore import Gaffer +import GafferScene +import GafferUI import GafferSceneUI GafferSceneUI.RenderPassEditor.registerOption( "*", "renderPass:enabled" ) @@ -130,3 +132,14 @@ def __defaultPathGroupingFunction( renderPassName ) : return renderPassName.split( "_" )[0] if "_" in renderPassName else "" GafferSceneUI.RenderPassEditor.registerPathGroupingFunction( __defaultPathGroupingFunction ) + +def __compoundEditorCreated( editor ) : + + if editor.scriptNode().ancestor( Gaffer.ApplicationRoot ).getName() == "gui" : + + Gaffer.Metadata.registerValue( editor.settings(), "layout:customWidget:renderPassSelector:widgetType", "GafferSceneUI.RenderPassEditor.RenderPassChooserWidget" ) + Gaffer.Metadata.registerValue( editor.settings(), "layout:customWidget:renderPassSelector:section", "Settings" ) + Gaffer.Metadata.registerValue( editor.settings(), "layout:customWidget:renderPassSelector:index", 0 ) + Gaffer.Metadata.registerValue( editor.settings(), "layout:customWidget:renderPassSelector:width", 185 ) + +GafferUI.CompoundEditor.instanceCreatedSignal().connect( __compoundEditorCreated )