diff --git a/Changes.md b/Changes.md index b8870df0ba1..61c72c68909 100644 --- a/Changes.md +++ b/Changes.md @@ -18,6 +18,10 @@ Improvements - PythonEditor, PythonCommand, Expression, UIEditor, OSLCode : Added line numbers to code editors (#6091). - CyclesOptions : Added `denoiseDevice` plug for configuring the device used for denoising. - AttributeTweaks : Added tooltips and presets for all attribute values. +- InteractiveRenderer : + - Added Esc hotkey handling to pause an interactive render. This is in addition to the existing behavior of pausing the image viewer. + - Added Ctrl + \ hotkey handling to toggle the interactive renderer between paused and running. If the new state is `Running`, the image viewer will also be unpaused. + - Starting or unpausing the render using the viewer control play button will also unpause the image viewer. Fixes ----- diff --git a/doc/source/Interface/ControlsAndShortcuts/index.md b/doc/source/Interface/ControlsAndShortcuts/index.md index d758a346a8a..1dc15c75b7d 100644 --- a/doc/source/Interface/ControlsAndShortcuts/index.md +++ b/doc/source/Interface/ControlsAndShortcuts/index.md @@ -310,6 +310,16 @@ Duplicate current Catalogue image | {kbd}`Ctrl` + {kbd}`D` Toggle image comparison | {kbd}`Q` Toggle wipes | {kbd}`W` +### Interactive Render Viewer ### + +> Note : +> For the following controls and shortcuts, the cursor must hover over the Viewer. + +Action | Control or shortcut +-------------------------------------|-------------------- +Pause active render | {kdb}`Escape` +Toggle render running / paused | {kdb}`Ctrl` + {kbd}`\` + ### Crop window tool ### Action | Control or shortcut diff --git a/python/GafferSceneUI/InteractiveRenderUI.py b/python/GafferSceneUI/InteractiveRenderUI.py index b71777c6a6f..1cf7031855b 100644 --- a/python/GafferSceneUI/InteractiveRenderUI.py +++ b/python/GafferSceneUI/InteractiveRenderUI.py @@ -96,7 +96,7 @@ def __init__( self, view, **kwargs ) : with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 ) : GafferUI.Spacer( imath.V2i( 1, 1 ), imath.V2i( 1, 1 ) ) self.__label = GafferUI.Label( "Render" ) - self.__stateWidget = _StatePlugValueWidget( None ) + self.__stateWidget = _StatePlugValueWidget( None, view ) self.__messagesWidget = _MessageSummaryPlugValueWidget( None ) self.__view = view @@ -104,6 +104,9 @@ def __init__( self, view, **kwargs ) : if isinstance( self.__view["in"], GafferImage.ImagePlug ) : view.plugDirtiedSignal().connect( Gaffer.WeakMethod( self.__viewPlugDirtied ) ) + if isinstance( self.__view, GafferUI.View ) : + self.__view.viewportGadget().keyPressSignal().connect( Gaffer.WeakMethod( self.__keyPress ) ) + self.__update() def __update( self ) : @@ -177,7 +180,26 @@ def __viewPlugDirtied( self, plug ) : def __renderNodePlugDirtied( self, plug ) : if plug.getName() == "state" : - self.__updateLazily(); + self.__updateLazily() + + def __keyPress( self, widget, event ) : + + if event.key == "Escape" : + renderNode = self._interactiveRenderNode( self.__view ) + if renderNode is not None : + statePlug = renderNode["state"].source() + statePlug.setValue( GafferScene.InteractiveRender.State.Paused ) + elif event.key == "Backslash" and event.modifiers == event.modifiers.Control : + renderNode = self._interactiveRenderNode( self.__view ) + if renderNode is not None : + statePlug = renderNode["state"].source() + state = statePlug.getValue() + if state == GafferScene.InteractiveRender.State.Running : + statePlug.setValue( GafferScene.InteractiveRender.State.Paused ) + else : + statePlug.setValue( GafferScene.InteractiveRender.State.Running ) + if isinstance( self.__view, GafferImageUI.ImageView ) : + self.__view.imageGadget().setPaused( False ) ########################################################################## # UI for the state plug that allows setting the state through buttons @@ -185,7 +207,7 @@ def __renderNodePlugDirtied( self, plug ) : class _StatePlugValueWidget( GafferUI.PlugValueWidget ) : - def __init__( self, plug, *args, **kwargs) : + def __init__( self, plug, view = None, *args, **kwargs) : row = GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal ) GafferUI.PlugValueWidget.__init__( self, row, plug ) @@ -214,6 +236,7 @@ def __init__( self, plug, *args, **kwargs) : } self.__state = None + self.__view = view def _updateFromValues( self, values, exception ) : @@ -235,6 +258,8 @@ def __startPauseClicked( self, button ) : # Not enabling undo here is done so that users won't accidentally restart/stop their renderings. if state != GafferScene.InteractiveRender.State.Running: self.getPlug().setValue( GafferScene.InteractiveRender.State.Running ) + if isinstance( self.__view, GafferImageUI.ImageView ) : + self.__view.imageGadget().setPaused( False ) else: self.getPlug().setValue( GafferScene.InteractiveRender.State.Paused )