-
Notifications
You must be signed in to change notification settings - Fork 206
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Render Pass Menu #6177
base: 1.5_maintenance
Are you sure you want to change the base?
Render Pass Menu #6177
Changes from 1 commit
e4e5b4e
b9e8e63
27a348b
83ff385
89a4281
34f5783
d65b37a
a39941a
b40973a
9293fc3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -891,3 +896,245 @@ def __combiner( results ) : | |
) | ||
# Remove circular references that would keep the widget in limbo. | ||
e.__traceback__ = None | ||
|
||
class RenderPassChooserWidget( GafferUI.Widget ) : | ||
|
||
def __init__( self, topLevelWidget, **kw ) : | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
||
self.__scriptNode = topLevelWidget["__scriptNode"].getInput().node() | ||
self.__inputPlug = topLevelWidget["__renderPassMenuInputScene"] | ||
|
||
renderPassPlug = GafferSceneUI.ScriptNodeAlgo.acquireRenderPassPlug( self.__scriptNode ) | ||
|
||
self.__renderPassPlugValueWidget = _RenderPassPlugValueWidget( | ||
renderPassPlug["value"], | ||
topLevelWidget["__renderPassMenuAdaptedScene"]["globals"], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to pass the globals to the |
||
showLabel = True | ||
) | ||
GafferUI.Widget.__init__( self, self.__renderPassPlugValueWidget, **kw ) | ||
|
||
self.__focusChangedConnection = self.__scriptNode.focusChangedSignal().connect( | ||
Gaffer.WeakMethod( self.__focusChanged ), scoped = True | ||
) | ||
Comment on lines
+950
to
+952
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels like this shouldn't be the responsibility of a particular widget. This feels more closely associated to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we also have a bug where the menu button shows a "pass not available" warning after changing layout. I assume this is because we only update the input when focus changes, but we also need to initialise it correctly when the plug is first made. |
||
|
||
def __focusChanged( self, scriptNode, node ) : | ||
|
||
self.__updateInputPlug() | ||
|
||
def __updateInputPlug( self ) : | ||
|
||
self.__inputPlug.setInput( self.__scenePlugFromFocus() ) | ||
|
||
def __scenePlugFromFocus( self ) : | ||
|
||
focusNode = self.__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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe the fact that we support images here is a reason to go with my most vague suggestion for wording in that other comment - "provided by the focus node". |
||
( 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.RenderPassChooserWidget = RenderPassChooserWidget | ||
|
||
class _RenderPassPlugValueWidget( GafferUI.PlugValueWidget ) : | ||
|
||
def __init__( self, plug, globalsPlug = None, showLabel = False, **kw ) : | ||
|
||
self.__globalsPlug = globalsPlug | ||
|
||
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.__updateMenuButton() | ||
|
||
def getToolTip( self ) : | ||
|
||
if self.__currentRenderPass == "" : | ||
return "No render pass is active." | ||
|
||
if self.__currentRenderPass not in self.__renderPasses.get( "all", [] ) : | ||
return "{} is not in the scene output from the focus node.".format( self.__currentRenderPass ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps an alternative wording?
|
||
else : | ||
return "{} is the current render pass.".format( self.__currentRenderPass ) | ||
|
||
def _auxiliaryPlugs( self, plug ) : | ||
|
||
return [ self.__globalsPlug or self.__acquireGlobalsPlug() ] | ||
|
||
@staticmethod | ||
def _valuesForUpdate( plugs, auxiliaryPlugs ) : | ||
|
||
result = [] | ||
|
||
for plug, ( globalsPlug, ) in zip( plugs, auxiliaryPlugs ) : | ||
|
||
renderPasses = {} | ||
|
||
with Gaffer.Context( Gaffer.Context.current() ) as context : | ||
for renderPass in globalsPlug.getValue().get( "option:renderPass:names", IECore.StringVectorData() ) : | ||
renderPasses.setdefault( "all", [] ).append( renderPass ) | ||
context["renderPass"] = renderPass | ||
if globalsPlug.getValue().get( "option:renderPass:enabled", IECore.BoolData( True ) ).value : | ||
renderPasses.setdefault( "enabled", [] ).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 __acquireGlobalsPlug( self ) : | ||
|
||
scriptWindow = GafferUI.ScriptWindow.acquire( self.getPlug().node().scriptNode() ) | ||
return scriptWindow.getLayout().settings()["__renderPassMenuAdaptedScene"]["globals"] | ||
Comment on lines
+1077
to
+1078
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens in this case?
|
||
|
||
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 ), | ||
} | ||
) | ||
|
||
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" : lambda checked : self.__setDisplayGrouped( checked ), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is creating a circular reference. I've seen some of the dreaded "C++ object already deleted" warnings during testing, and this seems like the most likely explanation. |
||
"description" : "Toggle grouped display of render passes." | ||
} | ||
) | ||
|
||
result.append( | ||
"/Hide Disabled", | ||
{ | ||
"checkBox" : self.__hideDisabled, | ||
"command" : lambda checked : self.__setHideDisabled( checked ), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above - I think we need a WeakMethod here rather than a lambda. |
||
"description" : "Hide render passes disabled for rendering." | ||
} | ||
) | ||
|
||
return result | ||
|
||
def __refreshMenu( self ) : | ||
|
||
self.__busyWidget.setVisible( True ) | ||
|
||
def __setCurrentRenderPass( self, renderPass, *unused ) : | ||
|
||
for plug in self.getPlugs() : | ||
plug.setValue( renderPass ) | ||
Comment on lines
+1163
to
+1164
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This definitely feels like it should be undoable when we're editing in the Settings window. It does feel a bit less like it should be undoable when editing in the main menu though. Should we make it undoable everywhere, or fudge it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose the other precedent here is that |
||
|
||
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" | ||
else : | ||
return "disabledRenderPass.png" | ||
|
||
def __updateMenuButton( self ) : | ||
|
||
self.__menuButton.setText( self.__currentRenderPass or "None" ) | ||
self.__menuButton.setImage( self.__renderPassIcon( self.__currentRenderPass ) ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When switching between the warning icon and an active pass icon, the widget jumps slightly vertically. |
||
|
||
RenderPassEditor._RenderPassPlugValueWidget = _RenderPassPlugValueWidget |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,6 +38,8 @@ | |
|
||
import IECore | ||
import Gaffer | ||
import GafferScene | ||
import GafferUI | ||
import GafferSceneUI | ||
|
||
GafferSceneUI.RenderPassEditor.registerOption( "*", "renderPass:enabled" ) | ||
|
@@ -130,3 +132,24 @@ def __defaultPathGroupingFunction( renderPassName ) : | |
return renderPassName.split( "_" )[0] if "_" in renderPassName else "" | ||
|
||
GafferSceneUI.RenderPassEditor.registerPathGroupingFunction( __defaultPathGroupingFunction ) | ||
|
||
def __compoundEditorCreated( editor ) : | ||
|
||
settingsNode = editor.settings() | ||
settingsNode.addChild( GafferScene.ScenePlug( "__renderPassMenuInputScene" ) ) | ||
settingsNode.addChild( GafferScene.ScenePlug( "__renderPassMenuAdaptedScene" ) ) | ||
|
||
settingsNode["__renderPassMenuAdaptors"] = GafferScene.SceneAlgo.createRenderAdaptors() | ||
settingsNode["__renderPassMenuAdaptors"]["in"].setInput( settingsNode["__renderPassMenuInputScene"] ) | ||
## \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... | ||
settingsNode["__renderPassMenuAdaptors"]["client"].setValue( "RenderPassWedge" ) | ||
settingsNode["__renderPassMenuAdaptedScene"].setInput( settingsNode["__renderPassMenuAdaptors"]["out"] ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels integral to the operation of the widgets, rather than being just a configuration setting. Can we move it to RenderPassEditor.py and do it unconditionally? Would there be any harm in always making the internal nodes even if the widgets aren't used? Alternatively we could move it to RenderPassEditor and expose it as a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we also need to document quite clearly why we need to do this, and why we've chosen to host it in the top-level editor rather than anywhere else. |
||
|
||
GafferUI.CompoundEditor.instanceCreatedSignal().connect( __compoundEditorCreated ) | ||
|
||
Gaffer.Metadata.registerValue( GafferUI.CompoundEditor.Settings, "layout:customWidget:renderPassSelector:widgetType", "GafferSceneUI.RenderPassEditor.RenderPassChooserWidget" ) | ||
Gaffer.Metadata.registerValue( GafferUI.CompoundEditor.Settings, "layout:customWidget:renderPassSelector:section", "Settings" ) | ||
Gaffer.Metadata.registerValue( GafferUI.CompoundEditor.Settings, "layout:customWidget:renderPassSelector:index", 0 ) | ||
Gaffer.Metadata.registerValue( GafferUI.CompoundEditor.Settings, "layout:customWidget:renderPassSelector:width", 185 ) | ||
Comment on lines
+148
to
+151
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would add the widget to layouts in applications like Jabuka, if they were running embedded in Gaffer. I think perhaps we want to apply this as instance metadata, and only when the editor is for a ScriptNode that belongs to the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this is a bit too API-ey to use as a category here. Maybe just "Menu Bar"? We could always add an API entry to mention the RenderPassChooserWidget.
Quite a lot of froms! Maybe just "choose the current render pass from those provided by the focus node"?