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 )