diff --git a/Changes.md b/Changes.md
index d57c39ebaae..f1815ea6369 100644
--- a/Changes.md
+++ b/Changes.md
@@ -17,6 +17,9 @@ Fixes
- StringPlugValueWidget : Fixed bug handling Esc.
- Arnold : Fixed unnecessary `opaque` attribute deprecation warnings. These are now only emitted in the case that `opaque` has been explicitly turned off.
- ShaderUI : Fixed bug causing identical but independent shaders in a shader network from being included in the shader browser.
+- Encapsulate :
+ - Fixed bug where global attributes (from the point of encapsulation) were baked into the contents of the capsule instead of being inherited naturally (from the node being rendered).
+ - Fixed motion blur so that global settings are now taken from the downstream node being rendered, not from the input to the Encapsulate node.
API
---
diff --git a/include/GafferScene/Capsule.h b/include/GafferScene/Capsule.h
index 1cb625afbf5..4856bfbc4e7 100644
--- a/include/GafferScene/Capsule.h
+++ b/include/GafferScene/Capsule.h
@@ -37,6 +37,7 @@
#pragma once
#include "GafferScene/Private/IECoreScenePreview/Procedural.h"
+#include "GafferScene/Private/RendererAlgo.h"
#include "GafferScene/ScenePlug.h"
#include "Gaffer/Context.h"
@@ -90,6 +91,11 @@ class GAFFERSCENE_API Capsule : public IECoreScenePreview::Procedural
const ScenePlug::ScenePath &root() const;
const Gaffer::Context *context() const;
+ /// Used to apply the correct render settings to the capsule before rendering it.
+ /// For internal use only.
+ void setRenderOptions( const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions );
+ std::optional getRenderOptions() const;
+
private :
void throwIfNoScene() const;
diff --git a/include/GafferScene/Private/RendererAlgo.h b/include/GafferScene/Private/RendererAlgo.h
index de0b599bb3b..11f7bf81baf 100644
--- a/include/GafferScene/Private/RendererAlgo.h
+++ b/include/GafferScene/Private/RendererAlgo.h
@@ -63,14 +63,34 @@ namespace Private
namespace RendererAlgo
{
+struct GAFFERSCENE_API RenderOptions
+{
+ RenderOptions();
+ RenderOptions( const ScenePlug *scene );
+ RenderOptions( const RenderOptions &other ) = default;
+ RenderOptions& operator=( const RenderOptions &other ) = default;
+ bool operator==( const RenderOptions &other ) const;
+ /// The globals from the scene.
+ IECore::ConstCompoundObjectPtr globals;
+ /// Convenient access to specific properties, taking into account default
+ /// values if they have not been specified in the scene.
+ bool transformBlur;
+ bool deformationBlur;
+ Imath::V2f shutter;
+ IECore::ConstStringVectorDataPtr includedPurposes;
+ /// Returns true if `includedPurposes` includes the purpose defined by
+ /// `attributes`.
+ bool purposeIncluded( const IECore::CompoundObject *attributes ) const;
+};
+
/// Creates the directories necessary to receive the outputs defined in globals.
GAFFERSCENE_API void createOutputDirectories( const IECore::CompoundObject *globals );
-/// Set the "times" to a list of times to sample the transform or deformation of a location at, based on the
-/// "motionBlur" enable coming from the options, a shutter, and location attributes. Returns a boolean for
-/// whether times has been altered ( returns false if times was already set correctly ).
-GAFFERSCENE_API bool transformMotionTimes( bool motionBlur, const Imath::V2f &shutter, const IECore::CompoundObject *attributes, std::vector × );
-GAFFERSCENE_API bool deformationMotionTimes( bool motionBlur, const Imath::V2f &shutter, const IECore::CompoundObject *attributes, std::vector × );
+/// Sets `times` to a list of times to sample the transform or deformation of a
+/// location at, based on the render options and location attributes. Returns `true`
+/// if `times` was altered and `false` if it was already set correctly.
+GAFFERSCENE_API bool transformMotionTimes( const RenderOptions &renderOptions, const IECore::CompoundObject *attributes, std::vector × );
+GAFFERSCENE_API bool deformationMotionTimes( const RenderOptions &renderOptions, const IECore::CompoundObject *attributes, std::vector × );
/// Samples the local transform from the current location in preparation for output to the renderer.
/// "samples" will be set to contain one sample for each sampleTime, unless the samples are all identical,
@@ -259,10 +279,10 @@ class GAFFERSCENE_API LightLinks : boost::noncopyable
};
-GAFFERSCENE_API void outputCameras( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, IECoreScenePreview::Renderer *renderer );
-GAFFERSCENE_API void outputLightFilters( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer );
-GAFFERSCENE_API void outputLights( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer );
-GAFFERSCENE_API void outputObjects( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, const LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer, const ScenePlug::ScenePath &root = ScenePlug::ScenePath() );
+GAFFERSCENE_API void outputCameras( const ScenePlug *scene, const RenderOptions &renderOptions, const RenderSets &renderSets, IECoreScenePreview::Renderer *renderer );
+GAFFERSCENE_API void outputLightFilters( const ScenePlug *scene, const RenderOptions &renderOptions, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer );
+GAFFERSCENE_API void outputLights( const ScenePlug *scene, const RenderOptions &renderOptions, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer );
+GAFFERSCENE_API void outputObjects( const ScenePlug *scene, const RenderOptions &renderOptions, const RenderSets &renderSets, const LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer, const ScenePlug::ScenePath &root = ScenePlug::ScenePath() );
} // namespace RendererAlgo
diff --git a/include/GafferScene/RenderController.h b/include/GafferScene/RenderController.h
index ecbed4a5bf8..c257cb10088 100644
--- a/include/GafferScene/RenderController.h
+++ b/include/GafferScene/RenderController.h
@@ -122,14 +122,15 @@ class GAFFERSCENE_API RenderController : public Gaffer::Signals::Trackable
DeformationBlurGlobalComponent = 32,
CameraShutterGlobalComponent = 64,
IncludedPurposesGlobalComponent = 128,
+ CapsuleAffectingGlobalComponents = TransformBlurGlobalComponent | DeformationBlurGlobalComponent | IncludedPurposesGlobalComponent,
AllGlobalComponents = GlobalsGlobalComponent | SetsGlobalComponent | RenderSetsGlobalComponent | CameraOptionsGlobalComponent | TransformBlurGlobalComponent | DeformationBlurGlobalComponent | IncludedPurposesGlobalComponent
};
- struct MotionBlurOptions
+ struct Unused
{
- bool transformBlur = false;
- bool deformationBlur = false;
- Imath::V2f shutter = Imath::V2f( 0 );
+ bool unused1;
+ bool unused2;
+ Imath::V2f unused3;
};
void plugDirtied( const Gaffer::Plug *plug );
@@ -165,8 +166,8 @@ class GAFFERSCENE_API RenderController : public Gaffer::Signals::Trackable
std::vector > m_sceneGraphs;
unsigned m_dirtyGlobalComponents;
unsigned m_changedGlobalComponents;
- IECore::ConstCompoundObjectPtr m_globals;
- MotionBlurOptions m_motionBlurOptions;
+ std::unique_ptr m_renderOptions;
+ Unused m_unused;
Private::RendererAlgo::RenderSets m_renderSets;
std::unique_ptr m_lightLinks;
IECoreScenePreview::Renderer::ObjectInterfacePtr m_defaultCamera;
diff --git a/python/GafferSceneTest/CapsuleTest.py b/python/GafferSceneTest/CapsuleTest.py
index 96d05c41562..c99934be68f 100644
--- a/python/GafferSceneTest/CapsuleTest.py
+++ b/python/GafferSceneTest/CapsuleTest.py
@@ -34,7 +34,6 @@
#
##########################################################################
-import inspect
import unittest
import IECore
@@ -72,5 +71,69 @@ def test( self ) :
self.assertEqual( capsuleCopy.bound(), sphere["out"].bound( "/" ) )
self.assertEqual( capsuleCopy.hash(), capsule.hash() )
+ def testInheritedAttributesAreNotBakedIntoCapsuleContents( self ) :
+
+ # Make a Capsule which inherits attributes from its parent
+ # and from the scene globals.
+
+ sphere = GafferScene.Sphere()
+ group = GafferScene.Group()
+ group["in"][0].setInput( sphere["out"] )
+
+ groupFilter = GafferScene.PathFilter()
+ groupFilter["paths"].setValue( IECore.StringVectorData( [ "/group" ] ) )
+
+ groupAttributes = GafferScene.CustomAttributes()
+ groupAttributes["in"].setInput( group["out"] )
+ groupAttributes["filter"].setInput( groupFilter["out"] )
+ groupAttributes["attributes"].addChild( Gaffer.NameValuePlug( "groupAttribute", 10 ) )
+
+ globalAttributes = GafferScene.CustomAttributes()
+ globalAttributes["in"].setInput( groupAttributes["out"] )
+ globalAttributes["global"].setValue( True )
+ globalAttributes["attributes"].addChild( Gaffer.NameValuePlug( "globalAttribute", 20 ) )
+
+ encapsulate = GafferScene.Encapsulate()
+ encapsulate["in"].setInput( globalAttributes["out"] )
+ encapsulate["filter"].setInput( groupFilter["out"] )
+
+ # Render it, and check that the capsule object had the inherited attributes applied to it.
+
+ renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer(
+ GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch
+ )
+ GafferScene.Private.RendererAlgo.outputObjects(
+ encapsulate["out"], GafferScene.Private.RendererAlgo.RenderOptions( encapsulate["out"] ), GafferScene.Private.RendererAlgo.RenderSets( encapsulate["out"] ), GafferScene.Private.RendererAlgo.LightLinks(),
+ renderer
+ )
+
+ capturedGroup = renderer.capturedObject( "/group" )
+ self.assertIsInstance( capturedGroup.capturedSamples()[0], GafferScene.Capsule )
+ self.assertEqual(
+ capturedGroup.capturedAttributes().attributes(),
+ IECore.CompoundObject( {
+ "groupAttribute" : IECore.IntData( 10 ),
+ "globalAttribute" : IECore.IntData( 20 ),
+ "sets" : IECore.InternedStringVectorData(),
+ } )
+ )
+
+ # Expand the capsule, and check that it didn't bake the inherited attributes onto
+ # its contents. It is the responsibity of the Renderer itself to take care of attribute
+ # inheritance, ideally doing it "live", so that changes to inherited attributes don't
+ # require re-expansion of the capsule.
+
+ renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer(
+ GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch
+ )
+ capturedGroup.capturedSamples()[0].render( renderer )
+ capturedSphere = renderer.capturedObject( "/sphere" )
+ self.assertEqual(
+ capturedSphere.capturedAttributes().attributes(),
+ IECore.CompoundObject( {
+ "sets" : IECore.InternedStringVectorData(),
+ } )
+ )
+
if __name__ == "__main__":
unittest.main()
diff --git a/python/GafferSceneTest/RenderControllerTest.py b/python/GafferSceneTest/RenderControllerTest.py
index 9cdf17d963b..55e8329c512 100644
--- a/python/GafferSceneTest/RenderControllerTest.py
+++ b/python/GafferSceneTest/RenderControllerTest.py
@@ -1584,5 +1584,104 @@ def testIncludedPurposes( self ) :
controller.update()
self.assertIsNotNone( renderer.capturedObject( "/group/sphere" ) )
+ def testCapsuleRenderOptions( self ) :
+
+ rootFilter = GafferScene.PathFilter()
+ rootFilter["paths"].setValue( IECore.StringVectorData( [ "*" ] ) )
+
+ cube = GafferScene.Cube()
+
+ encapsulate = GafferScene.Encapsulate()
+ encapsulate["in"].setInput( cube["out"] )
+ encapsulate["filter"].setInput( rootFilter["out"] )
+
+ standardOptions = GafferScene.StandardOptions()
+ standardOptions["in"].setInput( encapsulate["out"] )
+ standardOptions["options"]["includedPurposes"]["enabled"].setValue( True )
+
+ renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer()
+ controller = GafferScene.RenderController( standardOptions["out"], Gaffer.Context(), renderer )
+ controller.setMinimumExpansionDepth( 2 )
+
+ def assertExpectedRenderOptions() :
+
+ captured = renderer.capturedObject( "/cube" )
+ self.assertIsNotNone( captured )
+ self.assertEqual( len( captured.capturedSamples() ), 1 )
+ self.assertIsInstance( captured.capturedSamples()[0], GafferScene.Capsule )
+ self.assertEqual(
+ captured.capturedSamples()[0].getRenderOptions(),
+ GafferScene.Private.RendererAlgo.RenderOptions( standardOptions["out"] )
+ )
+
+ # Check that a capsule has the initial RenderOptions we expect.
+
+ self.assertTrue( controller.updateRequired() )
+ controller.update()
+ assertExpectedRenderOptions()
+
+ # Check that the capsule is updated when the RenderOptions change.
+
+ standardOptions["options"]["includedPurposes"]["value"].setValue( IECore.StringVectorData( [ "default", "proxy" ] ) )
+ self.assertTrue( controller.updateRequired() )
+ controller.update()
+ assertExpectedRenderOptions()
+
+ # Check that the capsule is not updated when the globals change
+ # but the RenderOptions that the capsule uses aren't affected.
+
+ capture = renderer.capturedObject( "/cube" )
+ standardOptions["options"]["performanceMonitor"]["enabled"].setValue( True )
+ self.assertTrue( controller.updateRequired() )
+ controller.update()
+ self.assertTrue( renderer.capturedObject( "/cube" ).isSame( capture ) )
+
+ # Change RenderOptions again, this time to the default, and check we
+ # get another update.
+
+ del capture
+ standardOptions["options"]["includedPurposes"]["value"].setValue( IECore.StringVectorData( [ "default", "render", "proxy", "guide" ] ) )
+ self.assertTrue( controller.updateRequired() )
+ controller.update()
+ assertExpectedRenderOptions()
+
+ # Remove `includedPurposes` option, so it's not in the globals. The
+ # fallback is the same as the previous value, so we should get no
+ # update.
+
+ capture = renderer.capturedObject( "/cube" )
+ standardOptions["options"]["includedPurposes"]["enabled"].setValue( False )
+ self.assertTrue( controller.updateRequired() )
+ controller.update()
+ self.assertTrue( renderer.capturedObject( "/cube" ).isSame( capture ) )
+
+ def testNoUnnecessaryObjectUpdatesOnPurposeChange( self ) :
+
+ cube = GafferScene.Cube()
+
+ standardOptions = GafferScene.StandardOptions()
+ standardOptions["in"].setInput( cube["out"] )
+
+ renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer()
+ controller = GafferScene.RenderController( standardOptions["out"], Gaffer.Context(), renderer )
+ controller.setMinimumExpansionDepth( 2 )
+
+ # Check initial capture
+
+ self.assertTrue( controller.updateRequired() )
+ controller.update()
+ capture = renderer.capturedObject( "/cube" )
+ self.assertIsNotNone( capture )
+
+ # Check that changing the purposes doesn't make an unnecessary edit for
+ # the object. It was included before and it is still included, so we
+ # want to reuse the old object.
+
+ standardOptions["options"]["includedPurposes"]["enabled"].setValue( True )
+ standardOptions["options"]["includedPurposes"]["value"].setValue( IECore.StringVectorData( [ "default", "proxy" ] ) )
+ self.assertTrue( controller.updateRequired() )
+ controller.update()
+ self.assertTrue( capture.isSame( renderer.capturedObject( "/cube" ) ) )
+
if __name__ == "__main__":
unittest.main()
diff --git a/python/GafferSceneTest/RendererAlgoTest.py b/python/GafferSceneTest/RendererAlgoTest.py
index ef651635f21..6f7e799177e 100644
--- a/python/GafferSceneTest/RendererAlgoTest.py
+++ b/python/GafferSceneTest/RendererAlgoTest.py
@@ -36,6 +36,8 @@
import unittest
+import imath
+
import IECore
import Gaffer
@@ -97,6 +99,7 @@ def testOutputCameras( self ) :
options = GafferScene.StandardOptions()
options["in"].setInput( camera["out"] )
+ renderOptions = GafferScene.Private.RendererAlgo.RenderOptions( options["out"] )
renderSets = GafferScene.Private.RendererAlgo.RenderSets( options["out"] )
def expectedCamera( frame ) :
@@ -105,7 +108,7 @@ def expectedCamera( frame ) :
c.setFrame( frame )
camera = options["out"].object( "/camera" )
- GafferScene.SceneAlgo.applyCameraGlobals( camera, sceneGlobals, options["out"] )
+ GafferScene.SceneAlgo.applyCameraGlobals( camera, options["out"].globals(), options["out"] )
return camera
# Non-animated case
@@ -113,8 +116,7 @@ def expectedCamera( frame ) :
renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer(
GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch
)
- sceneGlobals = options["out"].globals()
- GafferScene.Private.RendererAlgo.outputCameras( options["out"], sceneGlobals, renderSets, renderer )
+ GafferScene.Private.RendererAlgo.outputCameras( options["out"], renderOptions, renderSets, renderer )
capturedCamera = renderer.capturedObject( "/camera" )
@@ -129,8 +131,8 @@ def expectedCamera( frame ) :
renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer(
GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch
)
- sceneGlobals = options["out"].globals()
- GafferScene.Private.RendererAlgo.outputCameras( options["out"], sceneGlobals, renderSets, renderer )
+ renderOptions = GafferScene.Private.RendererAlgo.RenderOptions( options["out"] )
+ GafferScene.Private.RendererAlgo.outputCameras( options["out"], renderOptions, renderSets, renderer )
capturedCamera = renderer.capturedObject( "/camera" )
self.assertEqual( capturedCamera.capturedSamples(), [ expectedCamera( 0.75 ), expectedCamera( 1.25 ) ] )
@@ -155,7 +157,8 @@ def testInvisibleCamera( self ) :
)
with self.assertRaisesRegex( RuntimeError, "Camera \"/camera\" is hidden" ) :
GafferScene.Private.RendererAlgo.outputCameras(
- standardOptions["out"], standardOptions["out"].globals(),
+ standardOptions["out"],
+ GafferScene.Private.RendererAlgo.RenderOptions( standardOptions["out"] ),
GafferScene.Private.RendererAlgo.RenderSets( standardOptions["out"] ),
renderer
)
@@ -317,14 +320,14 @@ def testLightSolo( self ) :
# Output the lights to the renderer
+ renderOptions = GafferScene.Private.RendererAlgo.RenderOptions( soloSet["out"] )
renderSets = GafferScene.Private.RendererAlgo.RenderSets( soloSet["out"] )
lightLinks = GafferScene.Private.RendererAlgo.LightLinks()
renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer(
GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch
)
- sceneGlobals = soloSet["out"].globals()
- GafferScene.Private.RendererAlgo.outputLights( soloSet["out"], sceneGlobals, renderSets, lightLinks, renderer )
+ GafferScene.Private.RendererAlgo.outputLights( soloSet["out"], renderOptions, renderSets, lightLinks, renderer )
# Check that the output is correct
@@ -448,14 +451,14 @@ def testLightMute( self ) :
# Output the lights to the renderer
+ renderOptions = GafferScene.Private.RendererAlgo.RenderOptions( muteAttributes["out"] )
renderSets = GafferScene.Private.RendererAlgo.RenderSets( muteAttributes["out"] )
lightLinks = GafferScene.Private.RendererAlgo.LightLinks()
renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer(
GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch
)
- sceneGlobals = muteAttributes["out"].globals()
- GafferScene.Private.RendererAlgo.outputLights( muteAttributes["out"], sceneGlobals, renderSets, lightLinks, renderer )
+ GafferScene.Private.RendererAlgo.outputLights( muteAttributes["out"], renderOptions, renderSets, lightLinks, renderer )
# Check that the output is correct
@@ -643,15 +646,15 @@ def purposeAttribute( purpose ) :
def assertIncludedObjects( scene, includedPurposes, paths ) :
- globals = IECore.CompoundObject()
+ renderOptions = GafferScene.Private.RendererAlgo.RenderOptions( scene )
if includedPurposes :
- globals["option:render:includedPurposes"] = IECore.StringVectorData( includedPurposes )
+ renderOptions.includedPurposes = IECore.StringVectorData( includedPurposes )
renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer(
GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch
)
GafferScene.Private.RendererAlgo.outputObjects(
- group["out"], globals, GafferScene.Private.RendererAlgo.RenderSets( scene ), GafferScene.Private.RendererAlgo.LightLinks(),
+ group["out"], renderOptions, GafferScene.Private.RendererAlgo.RenderSets( scene ), GafferScene.Private.RendererAlgo.LightLinks(),
renderer
)
@@ -748,5 +751,179 @@ def assertIncludedObjects( scene, includedPurposes, paths ) :
}
)
+ def testCapsuleMotionBlur( self ) :
+
+ sphere = GafferScene.Sphere()
+ sphere["type"].setValue( sphere.Type.Primitive )
+ sphere["expression"] = Gaffer.Expression()
+ sphere["expression"].setExpression(
+ 'parent["radius"] = context.getFrame() + 1; parent["transform"]["translate"]["x"] = context.getFrame()'
+ )
+
+ group = GafferScene.Group()
+ group["in"][0].setInput( sphere["out"] )
+
+ groupFilter = GafferScene.PathFilter()
+ groupFilter["paths"].setValue( IECore.StringVectorData( [ "/group" ] ) )
+
+ encapsulate = GafferScene.Encapsulate()
+ encapsulate["in"].setInput( group["out"] )
+ encapsulate["filter"].setInput( groupFilter["out"] )
+
+ camera = GafferScene.Camera()
+ camera["renderSettingOverrides"]["shutter"]["value"].setValue( imath.V2f( -0.5, 0.5 ) )
+
+ parent = GafferScene.Parent()
+ parent["in"].setInput( encapsulate["out"] )
+ parent["parent"].setValue( "/" )
+ parent["in"].setInput( encapsulate["out"] )
+ parent["children"][0].setInput( camera["out"] )
+
+ standardOptions = GafferScene.StandardOptions()
+ standardOptions["in"].setInput( parent["out"] )
+ standardOptions["options"]["transformBlur"]["enabled"].setValue( True )
+ standardOptions["options"]["deformationBlur"]["enabled"].setValue( True )
+ standardOptions["options"]["shutter"]["enabled"].setValue( True )
+ standardOptions["options"]["renderCamera"]["enabled"].setValue( True )
+ standardOptions["options"]["renderCamera"]["value"].setValue( "/camera" )
+
+ def assertExpectedMotion( scene ) :
+
+ # Render to capture Capsule. This will always contain only a single
+ # motion sample because procedurals themselves can't have motion samples
+ # (although their contents can).
+
+ renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer(
+ GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch
+ )
+ GafferScene.Private.RendererAlgo.outputObjects(
+ scene, GafferScene.Private.RendererAlgo.RenderOptions( scene ),
+ GafferScene.Private.RendererAlgo.RenderSets( scene ), GafferScene.Private.RendererAlgo.LightLinks(),
+ renderer
+ )
+
+ capsule = renderer.capturedObject( "/group" )
+ self.assertEqual( len( capsule.capturedSamples() ), 1 )
+ capsule = capsule.capturedSamples()[0]
+ self.assertIsInstance( capsule, GafferScene.Capsule )
+
+ # Render again to expand contents of Capsule.
+
+ capsuleRenderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer(
+ GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch
+ )
+ capsule.render( capsuleRenderer )
+
+ # Check transform blur of contents matches what was requested by the scene globals.
+
+ motionTimes = list( GafferScene.SceneAlgo.shutter( scene.globals(), scene ) )
+ sphere = capsuleRenderer.capturedObject( "/sphere" )
+ if scene.globals()["option:render:transformBlur"].value :
+ transformTimes = motionTimes
+ else :
+ transformTimes = [ Gaffer.Context.current().getFrame() ]
+
+ self.assertEqual( len( sphere.capturedTransforms() ), len( transformTimes ) )
+ self.assertEqual( sphere.capturedTransformTimes(), transformTimes if len( transformTimes ) > 1 else [] )
+ for index, time in enumerate( transformTimes ) :
+ with Gaffer.Context() as context :
+ context.setFrame( time )
+ self.assertEqual( sphere.capturedTransforms()[index], capsule.scene().transform( "/group/sphere" ) )
+
+ # Check deformation blur of contents matches what was requested by the scene globals.
+
+ if scene.globals()["option:render:deformationBlur"].value :
+ objectTimes = motionTimes
+ else :
+ objectTimes = [ Gaffer.Context.current().getFrame() ]
+
+ self.assertEqual( len( sphere.capturedSamples() ), len( objectTimes ) )
+ self.assertEqual( sphere.capturedSampleTimes(), objectTimes if len( objectTimes ) > 1 else [] )
+ for index, time in enumerate( objectTimes ) :
+ with Gaffer.Context() as context :
+ context.setFrame( time )
+ self.assertEqual( sphere.capturedSamples()[index].radius, capsule.scene().object( "/group/sphere" ).radius )
+
+ for frame in ( 0, 1 ) :
+ for deformation in ( False, True ) :
+ for transform in ( False, True ) :
+ for shutter in ( imath.V2f( -0.25, 0.25 ), imath.V2f( 0, 0.5 ) ) :
+ for overrideShutter in ( False, True ) :
+ with self.subTest( frame = frame, deformation = deformation, transform = transform, shutter = shutter, overrideShutter = overrideShutter ) :
+ standardOptions["options"]["transformBlur"]["value"].setValue( transform )
+ standardOptions["options"]["deformationBlur"]["value"].setValue( deformation )
+ standardOptions["options"]["shutter"]["value"].setValue( shutter )
+ camera["renderSettingOverrides"]["shutter"]["enabled"].setValue( overrideShutter )
+ with Gaffer.Context() as context :
+ context.setFrame( frame )
+ assertExpectedMotion( standardOptions["out"] )
+
+ def testCapsulePurposes( self ) :
+
+ rootFilter = GafferScene.PathFilter()
+ rootFilter["paths"].setValue( IECore.StringVectorData( [ "*" ] ) )
+
+ cube = GafferScene.Cube()
+
+ attributes = GafferScene.CustomAttributes()
+ attributes["in"].setInput( cube["out"] )
+ attributes["filter"].setInput( rootFilter["out"] )
+ attributes["attributes"].addChild( Gaffer.NameValuePlug( "usd:purpose", "${collect:rootName}" ) )
+
+ collect = GafferScene.CollectScenes()
+ collect["in"].setInput( attributes["out"] )
+ collect["rootNames"].setValue( IECore.StringVectorData( [ "default", "render", "proxy", "guide" ] ) )
+
+ group = GafferScene.Group()
+ group["in"][0].setInput( collect["out"] )
+
+ encapsulate = GafferScene.Encapsulate()
+ encapsulate["in"].setInput( group["out"] )
+ encapsulate["filter"].setInput( rootFilter["out"] )
+
+ standardOptions = GafferScene.StandardOptions()
+ standardOptions["in"].setInput( encapsulate["out"] )
+ standardOptions["options"]["includedPurposes"]["enabled"].setValue( True )
+
+ for includedPurposes in [
+ [ "default", "render" ],
+ [ "default", "proxy" ],
+ [ "default", "render", "proxy", "guide" ],
+ [ "default" ],
+ ] :
+ with self.subTest( includedPurposes = includedPurposes ) :
+
+ standardOptions["options"]["includedPurposes"]["value"].setValue( IECore.StringVectorData( includedPurposes ) )
+
+ # Render to capture Capsule.
+
+ renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer(
+ GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch
+ )
+ GafferScene.Private.RendererAlgo.outputObjects(
+ standardOptions["out"], GafferScene.Private.RendererAlgo.RenderOptions( standardOptions["out"] ),
+ GafferScene.Private.RendererAlgo.RenderSets( standardOptions["out"] ), GafferScene.Private.RendererAlgo.LightLinks(),
+ renderer
+ )
+
+ capsule = renderer.capturedObject( "/group" )
+ capsule = capsule.capturedSamples()[0]
+ self.assertIsInstance( capsule, GafferScene.Capsule )
+
+ # Render again to expand contents of Capsule.
+
+ capsuleRenderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer(
+ GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch
+ )
+ capsule.render( capsuleRenderer )
+
+ # Check that only objects with the right purpose have been included.
+
+ for purpose in [ "default", "render", "proxy", "guide" ] :
+ if purpose in includedPurposes :
+ self.assertIsNotNone( capsuleRenderer.capturedObject( f"/{purpose}/cube" ) )
+ else :
+ self.assertIsNone( capsuleRenderer.capturedObject( f"/{purpose}/cube" ) )
+
if __name__ == "__main__":
unittest.main()
diff --git a/python/GafferSceneUI/EncapsulateUI.py b/python/GafferSceneUI/EncapsulateUI.py
index a180c6eca86..17669037b12 100644
--- a/python/GafferSceneUI/EncapsulateUI.py
+++ b/python/GafferSceneUI/EncapsulateUI.py
@@ -61,11 +61,11 @@
> Note : Encapsulation currently has some limitations
>
- > - Motion blur options are taken from the globals at the
- > point of Encapsulation, not the downstream globals
- > at the point of rendering.
> - Motion blur attributes are not inherited - only
- > attributes within the encapsulate hierarchy are
+ > attributes within the encapsulated hierarchy are
+ > considered.
+ > - The `usd:purpose` attribute is not inherited - only
+ > attributes within the encapsulated hierarchy are
> considered.
""",
diff --git a/src/GafferScene/Capsule.cpp b/src/GafferScene/Capsule.cpp
index 9f1fa54b6dc..221dc405fe3 100644
--- a/src/GafferScene/Capsule.cpp
+++ b/src/GafferScene/Capsule.cpp
@@ -45,6 +45,8 @@
#include "boost/bind/bind.hpp"
+#include
+
using namespace boost::placeholders;
using namespace IECore;
using namespace IECoreScene;
@@ -53,6 +55,7 @@ using namespace GafferScene;
namespace
{
+
Gaffer::Context *capsuleContext( const Context &context )
{
Gaffer::Context *result = new Gaffer::Context( context );
@@ -62,6 +65,10 @@ namespace
result->remove( ScenePlug::scenePathContextName );
return result;
}
+
+std::mutex g_renderOptionsMutex;
+std::unordered_map g_renderOptions;
+
}
IE_CORE_DEFINEOBJECTTYPEDESCRIPTION( Capsule );
@@ -84,6 +91,8 @@ Capsule::Capsule(
Capsule::~Capsule()
{
+ std::unique_lock renderOptionsLock( g_renderOptionsMutex );
+ g_renderOptions.erase( this );
}
bool Capsule::isEqualTo( const IECore::Object *other ) const
@@ -103,6 +112,16 @@ void Capsule::hash( IECore::MurmurHash &h ) const
Procedural::hash( h );
h.append( m_hash );
+
+ if( auto renderOptions = getRenderOptions() )
+ {
+ // Hash only what affects our rendering, not everything in
+ // `RenderOptions::globals`.
+ h.append( renderOptions->transformBlur );
+ h.append( renderOptions->deformationBlur );
+ h.append( renderOptions->shutter );
+ renderOptions->includedPurposes->hash( h );
+ }
}
void Capsule::copyFrom( const IECore::Object *other, IECore::Object::CopyContext *context )
@@ -147,9 +166,13 @@ void Capsule::render( IECoreScenePreview::Renderer *renderer ) const
{
throwIfNoScene();
ScenePlug::GlobalScope scope( m_context.get() );
- IECore::ConstCompoundObjectPtr globals = m_scene->globalsPlug()->getValue();
+ std::optional renderOptions = getRenderOptions();
+ if( !renderOptions )
+ {
+ renderOptions = GafferScene::Private::RendererAlgo::RenderOptions( m_scene );
+ }
GafferScene::Private::RendererAlgo::RenderSets renderSets( m_scene );
- GafferScene::Private::RendererAlgo::outputObjects( m_scene, globals.get(), renderSets, /* lightLinks = */ nullptr, renderer, m_root );
+ GafferScene::Private::RendererAlgo::outputObjects( m_scene, *renderOptions, renderSets, /* lightLinks = */ nullptr, renderer, m_root );
}
const ScenePlug *Capsule::scene() const
@@ -170,6 +193,26 @@ const Gaffer::Context *Capsule::context() const
return m_context.get();
}
+void Capsule::setRenderOptions( const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions )
+{
+ // This is not pretty, but it allows the capsule to render with the correct
+ // motion blur and `includedPurposes`, taken from the downstream node being
+ // rendered rather than from the capsule's own globals.
+ std::unique_lock renderOptionsLock( g_renderOptionsMutex );
+ g_renderOptions[this] = renderOptions;
+}
+
+std::optional Capsule::getRenderOptions() const
+{
+ std::unique_lock renderOptionsLock( g_renderOptionsMutex );
+ auto it = g_renderOptions.find( this );
+ if( it != g_renderOptions.end() )
+ {
+ return it->second;
+ }
+ return std::nullopt;
+}
+
void Capsule::throwIfNoScene() const
{
if( !m_scene )
diff --git a/src/GafferScene/Render.cpp b/src/GafferScene/Render.cpp
index 4c2908f02af..1dd32b7cf46 100644
--- a/src/GafferScene/Render.cpp
+++ b/src/GafferScene/Render.cpp
@@ -297,14 +297,14 @@ void Render::executeInternal( bool flushCaches ) const
return;
}
- ConstCompoundObjectPtr globals = adaptedInPlug()->globalsPlug()->getValue();
+ GafferScene::Private::RendererAlgo::RenderOptions renderOptions( adaptedInPlug() );
if( !renderScope.sceneTranslationOnly() )
{
- GafferScene::Private::RendererAlgo::createOutputDirectories( globals.get() );
+ GafferScene::Private::RendererAlgo::createOutputDirectories( renderOptions.globals.get() );
}
PerformanceMonitorPtr performanceMonitor;
- if( const BoolData *d = globals->member( g_performanceMonitorOptionName ) )
+ if( const BoolData *d = renderOptions.globals->member( g_performanceMonitorOptionName ) )
{
if( d->readable() )
{
@@ -313,8 +313,8 @@ void Render::executeInternal( bool flushCaches ) const
}
Monitor::Scope performanceMonitorScope( performanceMonitor );
- GafferScene::Private::RendererAlgo::outputOptions( globals.get(), renderer.get() );
- GafferScene::Private::RendererAlgo::outputOutputs( inPlug(), globals.get(), renderer.get() );
+ GafferScene::Private::RendererAlgo::outputOptions( renderOptions.globals.get(), renderer.get() );
+ GafferScene::Private::RendererAlgo::outputOutputs( inPlug(), renderOptions.globals.get(), renderer.get() );
{
// Using nested scope so that we free the memory used by `renderSets`
@@ -322,11 +322,11 @@ void Render::executeInternal( bool flushCaches ) const
GafferScene::Private::RendererAlgo::RenderSets renderSets( adaptedInPlug() );
GafferScene::Private::RendererAlgo::LightLinks lightLinks;
- GafferScene::Private::RendererAlgo::outputCameras( adaptedInPlug(), globals.get(), renderSets, renderer.get() );
- GafferScene::Private::RendererAlgo::outputLights( adaptedInPlug(), globals.get(), renderSets, &lightLinks, renderer.get() );
- GafferScene::Private::RendererAlgo::outputLightFilters( adaptedInPlug(), globals.get(), renderSets, &lightLinks, renderer.get() );
+ GafferScene::Private::RendererAlgo::outputCameras( adaptedInPlug(), renderOptions, renderSets, renderer.get() );
+ GafferScene::Private::RendererAlgo::outputLights( adaptedInPlug(), renderOptions, renderSets, &lightLinks, renderer.get() );
+ GafferScene::Private::RendererAlgo::outputLightFilters( adaptedInPlug(), renderOptions, renderSets, &lightLinks, renderer.get() );
lightLinks.outputLightFilterLinks( adaptedInPlug() );
- GafferScene::Private::RendererAlgo::outputObjects( adaptedInPlug(), globals.get(), renderSets, &lightLinks, renderer.get() );
+ GafferScene::Private::RendererAlgo::outputObjects( adaptedInPlug(), renderOptions, renderSets, &lightLinks, renderer.get() );
}
if( renderScope.sceneTranslationOnly() )
diff --git a/src/GafferScene/RenderController.cpp b/src/GafferScene/RenderController.cpp
index 9b2a89fd5d0..5150c2c7cbb 100644
--- a/src/GafferScene/RenderController.cpp
+++ b/src/GafferScene/RenderController.cpp
@@ -36,6 +36,7 @@
#include "GafferScene/RenderController.h"
+#include "GafferScene/Capsule.h"
#include "GafferScene/Private/IECoreScenePreview/Placeholder.h"
#include "GafferScene/SceneAlgo.h"
@@ -110,34 +111,6 @@ bool cameraGlobalsChanged( const CompoundObject *globals, const CompoundObject *
return *camera1 != *camera2;
}
-const IECore::ConstStringVectorDataPtr g_defaultIncludedPurposes = new StringVectorData( { "default", "render", "proxy", "guide" } );
-
-bool includedPurposesChanged( const CompoundObject *globals, const CompoundObject *previousGlobals )
-{
- if( !previousGlobals )
- {
- return true;
- }
-
- auto *v1 = globals->member( g_includedPurposesOptionName );
- v1 = v1 ? v1 : g_defaultIncludedPurposes.get();
-
- auto *v2 = previousGlobals->member( g_includedPurposesOptionName );
- v2 = v2 ? v2 : g_defaultIncludedPurposes.get();
-
- return *v1 != *v2;
-}
-
-bool purposeIncluded( const CompoundObject *attributes, const CompoundObject *globals )
-{
- const auto purposeData = attributes->member( g_purposeAttributeName );
- const std::string &purpose = purposeData ? purposeData->readable() : g_defaultPurpose;
-
- const auto includedPurposesData = globals->member( g_includedPurposesOptionName );
- const vector &includedPurposes = includedPurposesData ? includedPurposesData->readable() : g_defaultIncludedPurposes->readable();
- return std::find( includedPurposes.begin(), includedPurposes.end(), purpose ) != includedPurposes.end();
-}
-
// This is for the very specific case of determining change for global
// attributes, where we need to avoid comparisons of certain synthetic members
// that are only present in previousFullAttributes.
@@ -201,6 +174,7 @@ struct ObjectInterfaceHandle : public boost::noncopyable
using RemovalCallback = std::function;
ObjectInterfaceHandle()
+ : m_isCapsule( false )
{
}
@@ -217,7 +191,7 @@ struct ObjectInterfaceHandle : public boost::noncopyable
assign( p, RemovalCallback() );
}
- void assign( const IECoreScenePreview::Renderer::ObjectInterfacePtr &p, const RemovalCallback &removalCallback )
+ void assign( const IECoreScenePreview::Renderer::ObjectInterfacePtr &p, const RemovalCallback &removalCallback, bool isCapsule = false )
{
if( m_removalCallback )
{
@@ -225,6 +199,7 @@ struct ObjectInterfaceHandle : public boost::noncopyable
}
m_objectInterface = p;
m_removalCallback = removalCallback;
+ m_isCapsule = isCapsule;
}
IECoreScenePreview::Renderer::ObjectInterface *operator->() const
@@ -242,10 +217,16 @@ struct ObjectInterfaceHandle : public boost::noncopyable
return m_objectInterface.get();
}
+ bool isCapsule() const
+ {
+ return m_isCapsule;
+ }
+
private :
IECoreScenePreview::Renderer::ObjectInterfacePtr m_objectInterface;
RemovalCallback m_removalCallback;
+ bool m_isCapsule;
};
@@ -399,7 +380,7 @@ class RenderController::SceneGraph
// Root - get attributes from globals.
if( changedGlobals & GlobalsGlobalComponent )
{
- if( updateAttributes( controller->m_globals.get() ) )
+ if( updateAttributes( controller->m_renderOptions->globals.get() ) )
{
m_changedComponents |= AttributesComponent;
}
@@ -420,7 +401,7 @@ class RenderController::SceneGraph
// If attributes have changed, need to check if this has affected our motion sample times
if( ( m_changedComponents & AttributesComponent ) || ( changedGlobals & TransformBlurGlobalComponent ) )
{
- if( Private::RendererAlgo::transformMotionTimes( controller->m_motionBlurOptions.transformBlur, controller->m_motionBlurOptions.shutter, m_fullAttributes.get(), m_transformTimes ) )
+ if( Private::RendererAlgo::transformMotionTimes( *controller->m_renderOptions, m_fullAttributes.get(), m_transformTimes ) )
{
m_dirtyComponents |= TransformComponent;
}
@@ -428,7 +409,7 @@ class RenderController::SceneGraph
if( ( m_changedComponents & AttributesComponent ) || ( changedGlobals & DeformationBlurGlobalComponent ) )
{
- if( Private::RendererAlgo::deformationMotionTimes( controller->m_motionBlurOptions.deformationBlur, controller->m_motionBlurOptions.shutter, m_fullAttributes.get(), m_deformationTimes ) )
+ if( Private::RendererAlgo::deformationMotionTimes( *controller->m_renderOptions, m_fullAttributes.get(), m_deformationTimes ) )
{
m_dirtyComponents |= ObjectComponent;
}
@@ -461,7 +442,7 @@ class RenderController::SceneGraph
if( ( m_changedComponents & AttributesComponent ) || ( changedGlobals & IncludedPurposesGlobalComponent ) )
{
const bool purposeIncludedPreviously = m_purposeIncluded;
- m_purposeIncluded = purposeIncluded( m_fullAttributes.get(), controller->m_globals.get() );
+ m_purposeIncluded = controller->m_renderOptions->purposeIncluded( m_fullAttributes.get() );
if( m_purposeIncluded != purposeIncludedPreviously )
{
// We'll need to hide or show the object by considering `m_purposeIncluded` in
@@ -499,7 +480,14 @@ class RenderController::SceneGraph
// Object
- if( ( m_dirtyComponents & ObjectComponent ) && updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), controller->m_globals.get(), controller->m_scene.get(), controller->m_lightLinks.get() ) )
+ if( m_objectInterface.isCapsule() && ( changedGlobals & CapsuleAffectingGlobalComponents ) )
+ {
+ // Account for `Capsule::setRenderOptions()` being called in `updateObject()`.
+ m_dirtyComponents |= ObjectComponent;
+ m_objectHash = MurmurHash();
+ }
+
+ if( ( m_dirtyComponents & ObjectComponent ) && updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), *controller->m_renderOptions, controller->m_scene.get(), controller->m_lightLinks.get() ) )
{
m_changedComponents |= ObjectComponent;
}
@@ -523,7 +511,7 @@ class RenderController::SceneGraph
{
// Failed to apply attributes - must replace entire object.
m_objectHash = MurmurHash();
- if( updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), controller->m_globals.get(), controller->m_scene.get(), controller->m_lightLinks.get() ) )
+ if( updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), *controller->m_renderOptions, controller->m_scene.get(), controller->m_lightLinks.get() ) )
{
m_changedComponents |= ObjectComponent;
controller->m_failedAttributeEdits++;
@@ -815,7 +803,7 @@ class RenderController::SceneGraph
}
// Returns true if the object changed.
- bool updateObject( const ObjectPlug *objectPlug, Type type, IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const ScenePlug *scene, LightLinks *lightLinks )
+ bool updateObject( const ObjectPlug *objectPlug, Type type, IECoreScenePreview::Renderer *renderer, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const ScenePlug *scene, LightLinks *lightLinks )
{
const bool hadObjectInterface = static_cast( m_objectInterface );
if( type == NoType || m_drawMode != VisibleSet::Visibility::Visible || !m_purposeIncluded )
@@ -882,7 +870,6 @@ class RenderController::SceneGraph
}
return true;
-
}
vector samples;
@@ -891,16 +878,12 @@ class RenderController::SceneGraph
return false;
}
- bool isNull = true;
- for( ConstObjectPtr &i : samples )
- {
- if( !runTimeCast( i.get() ) )
- {
- isNull = false;
- }
- }
-
- if( (type != LightType && type != LightFilterType) && isNull )
+ if(
+ std::all_of(
+ samples.begin(), samples.end(),
+ [] ( const ConstObjectPtr &sample ) { return runTimeCast( sample.get() ); }
+ )
+ )
{
m_objectInterface = nullptr;
return hadObjectInterface;
@@ -938,7 +921,7 @@ class RenderController::SceneGraph
if( auto cameraSample = runTimeCast( sample.get() ) )
{
IECoreScene::CameraPtr cameraSampleCopy = cameraSample->copy();
- SceneAlgo::applyCameraGlobals( cameraSampleCopy.get(), globals, scene );
+ SceneAlgo::applyCameraGlobals( cameraSampleCopy.get(), renderOptions.globals.get(), scene );
cameraSamples.push_back( cameraSampleCopy );
}
}
@@ -991,7 +974,18 @@ class RenderController::SceneGraph
if( samples.size() == 1 )
{
- m_objectInterface = renderer->object( name, samples[0].get(), attributesInterface( renderer ) );
+ ConstObjectPtr sample = samples[0];
+ if( auto capsule = runTimeCast( sample.get() ) )
+ {
+ CapsulePtr capsuleCopy = capsule->copy();
+ capsuleCopy->setRenderOptions( renderOptions );
+ sample = capsuleCopy;
+ }
+ m_objectInterface.assign(
+ renderer->object( name, sample.get(), attributesInterface( renderer ) ),
+ ObjectInterfaceHandle::RemovalCallback(),
+ /* isCapsule = */ runTimeCast( sample.get() )
+ );
}
else
{
@@ -1354,7 +1348,7 @@ RenderController::RenderController( const ConstScenePlugPtr &scene, const Gaffer
m_failedAttributeEdits( 0 ),
m_dirtyGlobalComponents( NoGlobalComponent ),
m_changedGlobalComponents( NoGlobalComponent ),
- m_globals( new CompoundObject )
+ m_renderOptions( make_unique() )
{
for( int i = SceneGraph::FirstType; i <= SceneGraph::LastType; ++i )
{
@@ -1564,10 +1558,9 @@ void RenderController::dirtySceneGraphs( unsigned components )
if( components & SceneGraph::ObjectComponent )
{
- // We don't track dirtiness of different SceneGraphs separately anyway,
- // so just recheck if a camera has changed a shutter override whenever
- // any object is dirtied
- m_changedGlobalComponents |= CameraShutterGlobalComponent;
+ // Changes to a camera object may include changing the
+ // shutter that overrides the global shutter.
+ m_dirtyGlobalComponents |= CameraShutterGlobalComponent;
}
}
@@ -1634,50 +1627,46 @@ void RenderController::updateInternal( const ProgressCallback &callback, const I
// Update globals
if( m_dirtyGlobalComponents & GlobalsGlobalComponent )
{
- ConstCompoundObjectPtr globals = m_scene->globalsPlug()->getValue();
- Private::RendererAlgo::outputOptions( globals.get(), m_globals.get(), m_renderer.get() );
- Private::RendererAlgo::outputOutputs( m_scene.get(), globals.get(), m_globals.get(), m_renderer.get() );
- if( !m_globals || *m_globals != *globals )
+ RenderOptions renderOptions( m_scene.get() );
+ Private::RendererAlgo::outputOptions( renderOptions.globals.get(), m_renderOptions->globals.get(), m_renderer.get() );
+ Private::RendererAlgo::outputOutputs( m_scene.get(), renderOptions.globals.get(), m_renderOptions->globals.get(), m_renderer.get() );
+ if( *renderOptions.globals != *m_renderOptions->globals )
{
m_changedGlobalComponents |= GlobalsGlobalComponent;
}
- if( cameraGlobalsChanged( globals.get(), m_globals.get(), m_scene.get() ) )
+ if( cameraGlobalsChanged( renderOptions.globals.get(), m_renderOptions->globals.get(), m_scene.get() ) )
{
m_changedGlobalComponents |= CameraOptionsGlobalComponent;
}
- if( includedPurposesChanged( globals.get(), m_globals.get() ) )
+ if( *renderOptions.includedPurposes != *m_renderOptions->includedPurposes )
{
m_changedGlobalComponents |= IncludedPurposesGlobalComponent;
}
- m_globals = globals;
- }
-
- // Update motion blur options
- if( m_changedGlobalComponents & ( GlobalsGlobalComponent | CameraShutterGlobalComponent ) )
- {
- const BoolData *transformBlurData = m_globals->member( g_transformBlurOptionName );
- bool transformBlur = transformBlurData ? transformBlurData->readable() : false;
-
- const BoolData *deformationBlurData = m_globals->member( g_deformationBlurOptionName );
- bool deformationBlur = deformationBlurData ? deformationBlurData->readable() : false;
-
- V2f shutter = SceneAlgo::shutter( m_globals.get(), m_scene.get() );
-
- if( shutter != m_motionBlurOptions.shutter || transformBlur != m_motionBlurOptions.transformBlur )
+ if( renderOptions.shutter != m_renderOptions->shutter || renderOptions.transformBlur != m_renderOptions->transformBlur )
{
m_changedGlobalComponents |= TransformBlurGlobalComponent;
}
- if( shutter != m_motionBlurOptions.shutter || deformationBlur != m_motionBlurOptions.deformationBlur )
+ if( renderOptions.shutter != m_renderOptions->shutter || renderOptions.deformationBlur != m_renderOptions->deformationBlur )
{
m_changedGlobalComponents |= DeformationBlurGlobalComponent;
}
- if( shutter != m_motionBlurOptions.shutter )
+ if( renderOptions.shutter != m_renderOptions->shutter )
{
m_changedGlobalComponents |= CameraOptionsGlobalComponent;
}
- m_motionBlurOptions.transformBlur = transformBlur;
- m_motionBlurOptions.deformationBlur = deformationBlur;
- m_motionBlurOptions.shutter = shutter;
+ *m_renderOptions = renderOptions;
+ }
+
+ if( ( m_dirtyGlobalComponents & CameraShutterGlobalComponent ) && !( m_dirtyGlobalComponents & GlobalsGlobalComponent ) )
+ {
+ // Shutter override from a camera may have changed, and won't have been covered by
+ // the block above (because the globals weren't dirty).
+ const V2f shutter = SceneAlgo::shutter( m_renderOptions->globals.get(), m_scene.get() );
+ if( shutter != m_renderOptions->shutter )
+ {
+ m_renderOptions->shutter = shutter;
+ m_changedGlobalComponents |= ( DeformationBlurGlobalComponent | TransformBlurGlobalComponent );
+ }
}
if( m_dirtyGlobalComponents & SetsGlobalComponent )
@@ -1786,7 +1775,7 @@ void RenderController::updateDefaultCamera()
return;
}
- const StringData *cameraOption = m_globals->member( g_cameraGlobalName );
+ const StringData *cameraOption = m_renderOptions->globals->member( g_cameraGlobalName );
m_defaultCamera = nullptr;
if( cameraOption && !cameraOption->readable().empty() )
{
@@ -1794,7 +1783,7 @@ void RenderController::updateDefaultCamera()
}
CameraPtr defaultCamera = new IECoreScene::Camera;
- SceneAlgo::applyCameraGlobals( defaultCamera.get(), m_globals.get(), m_scene.get() );
+ SceneAlgo::applyCameraGlobals( defaultCamera.get(), m_renderOptions->globals.get(), m_scene.get() );
IECoreScenePreview::Renderer::AttributesInterfacePtr defaultAttributes = m_renderer->attributes( m_scene->attributesPlug()->defaultValue() );
ConstStringDataPtr name = new StringData( "gaffer:defaultCamera" );
m_defaultCamera = m_renderer->camera( name->readable(), defaultCamera.get(), defaultAttributes.get() );
diff --git a/src/GafferScene/RendererAlgo.cpp b/src/GafferScene/RendererAlgo.cpp
index 836614b6ca7..ade0b58ed06 100644
--- a/src/GafferScene/RendererAlgo.cpp
+++ b/src/GafferScene/RendererAlgo.cpp
@@ -36,6 +36,7 @@
#include "GafferScene/Private/RendererAlgo.h"
+#include "GafferScene/Capsule.h"
#include "GafferScene/Private/IECoreScenePreview/Renderer.h"
#include "GafferScene/SceneAlgo.h"
#include "GafferScene/SceneProcessor.h"
@@ -77,6 +78,71 @@ using namespace IECoreScene;
using namespace Gaffer;
using namespace GafferScene;
+//////////////////////////////////////////////////////////////////////////
+// RenderOptions
+//////////////////////////////////////////////////////////////////////////
+
+namespace
+{
+
+const InternedString g_transformBlurOptionName( "option:render:transformBlur" );
+const InternedString g_deformationBlurOptionName( "option:render:deformationBlur" );
+const InternedString g_shutterOptionName( "option:render:shutter" );
+const InternedString g_includedPurposesOptionName( "option:render:includedPurposes" );
+const InternedString g_purposeAttributeName( "usd:purpose" );
+
+/// \todo We should really default to `{ "default", "render" }`, but can only
+/// change that on a major version update.
+const ConstStringVectorDataPtr g_defaultIncludedPurposes( new StringVectorData( { "default", "render", "proxy", "guide" } ) );
+const std::string g_defaultPurpose( "default" );
+
+} // namespace
+
+namespace GafferScene::Private::RendererAlgo
+{
+
+RenderOptions::RenderOptions()
+ : globals( new CompoundObject ),
+ transformBlur( false ),
+ deformationBlur( false ),
+ shutter( V2f( -0.25, 0.25 ) ),
+ includedPurposes( g_defaultIncludedPurposes )
+{
+}
+
+RenderOptions::RenderOptions( const ScenePlug *scene )
+{
+ globals = scene->globals();
+
+ const BoolData *transformBlurData = globals->member( g_transformBlurOptionName );
+ transformBlur = transformBlurData ? transformBlurData->readable() : false;
+
+ const BoolData *deformationBlurData = globals->member( g_deformationBlurOptionName );
+ deformationBlur = deformationBlurData ? deformationBlurData->readable() : false;
+
+ shutter = SceneAlgo::shutter( globals.get(), scene );
+
+ const StringVectorData *includedPurposesData = globals->member( g_includedPurposesOptionName );
+ includedPurposes = includedPurposesData ? includedPurposesData : g_defaultIncludedPurposes;
+}
+
+bool RenderOptions::operator==( const RenderOptions &other ) const
+{
+ // No need to test other fields because they are all derived directly
+ // from the globals.
+ return *globals == *other.globals && shutter == other.shutter;
+}
+
+bool RenderOptions::purposeIncluded( const CompoundObject *attributes ) const
+{
+ const auto purposeData = attributes->member( g_purposeAttributeName );
+ const std::string &purpose = purposeData ? purposeData->readable() : g_defaultPurpose;
+ const vector &purposes = includedPurposes->readable();
+ return std::find( purposes.begin(), purposes.end(), purpose ) != purposes.end();
+}
+
+} // namespace GafferScene::Private::RendererAlgo
+
//////////////////////////////////////////////////////////////////////////
// RendererAlgo implementation
//////////////////////////////////////////////////////////////////////////
@@ -157,14 +223,14 @@ bool motionTimes( bool motionBlur, const V2f &shutter, const CompoundObject *att
return changed;
}
-bool transformMotionTimes( bool motionBlur, const V2f &shutter, const CompoundObject *attributes, std::vector × )
+bool transformMotionTimes( const RenderOptions &renderOptions, const CompoundObject *attributes, std::vector × )
{
- return motionTimes( motionBlur, shutter, attributes, g_transformBlurAttributeName, g_transformBlurSegmentsAttributeName, times );
+ return motionTimes( renderOptions.transformBlur, renderOptions.shutter, attributes, g_transformBlurAttributeName, g_transformBlurSegmentsAttributeName, times );
}
-bool deformationMotionTimes( bool motionBlur, const V2f &shutter, const CompoundObject *attributes, std::vector × )
+bool deformationMotionTimes( const RenderOptions &renderOptions, const CompoundObject *attributes, std::vector × )
{
- return motionTimes( motionBlur, shutter, attributes, g_deformationBlurAttributeName, g_deformationBlurSegmentsAttributeName, times );
+ return motionTimes( renderOptions.deformationBlur, renderOptions.shutter, attributes, g_deformationBlurAttributeName, g_deformationBlurSegmentsAttributeName, times );
}
bool transformSamples( const M44fPlug *transformPlug, const std::vector &sampleTimes, std::vector &samples, IECore::MurmurHash *hash )
@@ -1009,15 +1075,9 @@ namespace
{
const std::string g_optionPrefix( "option:" );
-const std::string g_defaultPurpose( "default" );
const IECore::InternedString g_frameOptionName( "frame" );
const IECore::InternedString g_cameraOptionLegacyName( "option:render:camera" );
-const InternedString g_transformBlurOptionName( "option:render:transformBlur" );
-const InternedString g_deformationBlurOptionName( "option:render:deformationBlur" );
-const InternedString g_shutterOptionName( "option:render:shutter" );
-const InternedString g_includedPurposesOptionName( "option:render:includedPurposes" );
-const InternedString g_purposeAttributeName( "usd:purpose" );
InternedString g_visibleAttributeName( "scene:visible" );
@@ -1036,19 +1096,9 @@ IECore::InternedString optionName( const IECore::InternedString &globalsName )
struct LocationOutput
{
- LocationOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, const ScenePlug::ScenePath &root, const ScenePlug *scene )
- : m_renderer( renderer ), m_attributes( SceneAlgo::globalAttributes( globals ) ), m_renderSets( renderSets ), m_root( root )
+ LocationOutput( IECoreScenePreview::Renderer *renderer, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, const ScenePlug::ScenePath &root, const ScenePlug *scene )
+ : m_renderer( renderer ), m_options( renderOptions ), m_attributes( root.empty() ? SceneAlgo::globalAttributes( renderOptions.globals.get() ) : new CompoundObject ), m_renderSets( renderSets ), m_root( root )
{
- const BoolData *transformBlurData = globals->member( g_transformBlurOptionName );
- m_options.transformBlur = transformBlurData ? transformBlurData->readable() : false;
-
- const BoolData *deformationBlurData = globals->member( g_deformationBlurOptionName );
- m_options.deformationBlur = deformationBlurData ? deformationBlurData->readable() : false;
-
- m_options.shutter = SceneAlgo::shutter( globals, scene );
-
- m_options.includedPurposes = globals->member( g_includedPurposesOptionName );
-
m_transformSamples.push_back( M44f() );
}
@@ -1077,17 +1127,14 @@ struct LocationOutput
protected :
- bool purposeIncluded() const
+ const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions() const
{
- if( !m_options.includedPurposes )
- {
- return true;
- }
- const auto purposeData = m_attributes->member( g_purposeAttributeName );
- const std::string &purpose = purposeData ? purposeData->readable() : g_defaultPurpose;
- const vector &purposes = m_options.includedPurposes->readable();
- return std::find( purposes.begin(), purposes.end(), purpose ) != purposes.end();
+ return m_options;
+ }
+ bool purposeIncluded() const
+ {
+ return m_options.purposeIncluded( m_attributes.get() );
}
std::string name( const ScenePlug::ScenePath &path ) const
@@ -1114,7 +1161,7 @@ struct LocationOutput
void deformationMotionTimes( std::vector × )
{
- GafferScene::Private::RendererAlgo::deformationMotionTimes( m_options.deformationBlur, m_options.shutter, m_attributes.get(), times );
+ GafferScene::Private::RendererAlgo::deformationMotionTimes( m_options, m_attributes.get(), times );
}
const IECore::CompoundObject *attributes() const
@@ -1179,7 +1226,7 @@ struct LocationOutput
void updateTransform( const ScenePlug *scene )
{
vector sampleTimes;
- GafferScene::Private::RendererAlgo::transformMotionTimes( m_options.transformBlur, m_options.shutter, m_attributes.get(), sampleTimes );
+ GafferScene::Private::RendererAlgo::transformMotionTimes( m_options, m_attributes.get(), sampleTimes );
vector samples;
GafferScene::Private::RendererAlgo::transformSamples( scene->transformPlug(), sampleTimes, samples );
@@ -1240,15 +1287,7 @@ struct LocationOutput
IECoreScenePreview::Renderer *m_renderer;
- struct Options
- {
- bool transformBlur;
- bool deformationBlur;
- Imath::V2f shutter;
- ConstStringVectorDataPtr includedPurposes;
- };
-
- Options m_options;
+ const GafferScene::Private::RendererAlgo::RenderOptions m_options;
IECore::ConstCompoundObjectPtr m_attributes;
const GafferScene::Private::RendererAlgo::RenderSets &m_renderSets;
const ScenePlug::ScenePath &m_root;
@@ -1261,8 +1300,8 @@ struct LocationOutput
struct CameraOutput : public LocationOutput
{
- CameraOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, const ScenePlug::ScenePath &root, const ScenePlug *scene )
- : LocationOutput( renderer, globals, renderSets, root, scene ), m_globals( globals ), m_cameraSet( renderSets.camerasSet() )
+ CameraOutput( IECoreScenePreview::Renderer *renderer, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, const ScenePlug::ScenePath &root, const ScenePlug *scene )
+ : LocationOutput( renderer, renderOptions, renderSets, root, scene ), m_globals( renderOptions.globals.get() ), m_cameraSet( renderSets.camerasSet() )
{
}
@@ -1353,8 +1392,8 @@ struct CameraOutput : public LocationOutput
struct LightOutput : public LocationOutput
{
- LightOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene )
- : LocationOutput( renderer, globals, renderSets, root, scene ), m_lightSet( renderSets.lightsSet() ), m_lightLinks( lightLinks )
+ LightOutput( IECoreScenePreview::Renderer *renderer, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene )
+ : LocationOutput( renderer, renderOptions, renderSets, root, scene ), m_lightSet( renderSets.lightsSet() ), m_lightLinks( lightLinks )
{
}
@@ -1399,8 +1438,8 @@ struct LightOutput : public LocationOutput
struct LightFiltersOutput : public LocationOutput
{
- LightFiltersOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene )
- : LocationOutput( renderer, globals, renderSets, root, scene ), m_lightFiltersSet( renderSets.lightFiltersSet() ), m_lightLinks( lightLinks )
+ LightFiltersOutput( IECoreScenePreview::Renderer *renderer, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene )
+ : LocationOutput( renderer, renderOptions, renderSets, root, scene ), m_lightFiltersSet( renderSets.lightFiltersSet() ), m_lightLinks( lightLinks )
{
}
@@ -1445,8 +1484,8 @@ struct LightFiltersOutput : public LocationOutput
struct ObjectOutput : public LocationOutput
{
- ObjectOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, const GafferScene::Private::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene )
- : LocationOutput( renderer, globals, renderSets, root, scene ), m_cameraSet( renderSets.camerasSet() ), m_lightSet( renderSets.lightsSet() ), m_lightFiltersSet( renderSets.lightFiltersSet() ), m_lightLinks( lightLinks )
+ ObjectOutput( IECoreScenePreview::Renderer *renderer, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, const GafferScene::Private::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene )
+ : LocationOutput( renderer, renderOptions, renderSets, root, scene ), m_cameraSet( renderSets.camerasSet() ), m_lightSet( renderSets.lightsSet() ), m_lightFiltersSet( renderSets.lightFiltersSet() ), m_lightLinks( lightLinks )
{
}
@@ -1479,12 +1518,20 @@ struct ObjectOutput : public LocationOutput
IECoreScenePreview::Renderer::ObjectInterfacePtr objectInterface;
IECoreScenePreview::Renderer::AttributesInterfacePtr attributesInterface = this->attributesInterface();
- if( !sampleTimes.size() )
+ if( samples.size() == 1 )
{
- objectInterface = renderer()->object( name( path ), samples[0].get(), attributesInterface.get() );
+ ConstObjectPtr sample = samples[0];
+ if( auto capsule = runTimeCast( sample.get() ) )
+ {
+ CapsulePtr capsuleCopy = capsule->copy();
+ capsuleCopy->setRenderOptions( renderOptions() );
+ sample = capsuleCopy;
+ }
+ objectInterface = renderer()->object( name( path ), sample.get(), attributesInterface.get() );
}
else
{
+ assert( sampleTimes.size() == samples.size() );
/// \todo Can we rejig things so this conversion isn't necessary?
vector objectsVector; objectsVector.reserve( samples.size() );
for( const auto &sample : samples )
@@ -1708,9 +1755,9 @@ void outputOutputs( const ScenePlug *scene, const IECore::CompoundObject *global
}
}
-void outputCameras( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, IECoreScenePreview::Renderer *renderer )
+void outputCameras( const ScenePlug *scene, const RenderOptions &renderOptions, const RenderSets &renderSets, IECoreScenePreview::Renderer *renderer )
{
- const StringData *cameraOption = globals->member( g_cameraOptionLegacyName );
+ const StringData *cameraOption = renderOptions.globals->member( g_cameraOptionLegacyName );
if( cameraOption && !cameraOption->readable().empty() )
{
ScenePlug::ScenePath cameraPath; ScenePlug::stringToPath( cameraOption->readable(), cameraPath );
@@ -1729,13 +1776,13 @@ void outputCameras( const ScenePlug *scene, const IECore::CompoundObject *global
}
const ScenePlug::ScenePath root;
- CameraOutput output( renderer, globals, renderSets, root, scene );
+ CameraOutput output( renderer, renderOptions, renderSets, root, scene );
SceneAlgo::parallelProcessLocations( scene, output );
if( !cameraOption || cameraOption->readable().empty() )
{
CameraPtr defaultCamera = new IECoreScene::Camera;
- SceneAlgo::applyCameraGlobals( defaultCamera.get(), globals, scene );
+ SceneAlgo::applyCameraGlobals( defaultCamera.get(), renderOptions.globals.get(), scene );
IECoreScenePreview::Renderer::AttributesInterfacePtr defaultAttributes = renderer->attributes( scene->attributesPlug()->defaultValue() );
ConstStringDataPtr name = new StringData( "gaffer:defaultCamera" );
renderer->camera( name->readable(), defaultCamera.get(), defaultAttributes.get() );
@@ -1743,23 +1790,23 @@ void outputCameras( const ScenePlug *scene, const IECore::CompoundObject *global
}
}
-void outputLightFilters( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer )
+void outputLightFilters( const ScenePlug *scene, const RenderOptions &renderOptions, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer )
{
const ScenePlug::ScenePath root;
- LightFiltersOutput output( renderer, globals, renderSets, lightLinks, root, scene );
+ LightFiltersOutput output( renderer, renderOptions, renderSets, lightLinks, root, scene );
SceneAlgo::parallelProcessLocations( scene, output );
}
-void outputLights( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer )
+void outputLights( const ScenePlug *scene, const RenderOptions &renderOptions, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer )
{
const ScenePlug::ScenePath root;
- LightOutput output( renderer, globals, renderSets, lightLinks, root, scene );
+ LightOutput output( renderer, renderOptions, renderSets, lightLinks, root, scene );
SceneAlgo::parallelProcessLocations( scene, output );
}
-void outputObjects( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, const LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer, const ScenePlug::ScenePath &root )
+void outputObjects( const ScenePlug *scene, const RenderOptions &renderOptions, const RenderSets &renderSets, const LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer, const ScenePlug::ScenePath &root )
{
- ObjectOutput output( renderer, globals, renderSets, lightLinks, root, scene );
+ ObjectOutput output( renderer, renderOptions, renderSets, lightLinks, root, scene );
SceneAlgo::parallelProcessLocations( scene, output, root );
}
diff --git a/src/GafferSceneModule/HierarchyBinding.cpp b/src/GafferSceneModule/HierarchyBinding.cpp
index f0c18077292..9b7a51284ff 100644
--- a/src/GafferSceneModule/HierarchyBinding.cpp
+++ b/src/GafferSceneModule/HierarchyBinding.cpp
@@ -67,6 +67,15 @@ using namespace GafferScene;
namespace
{
+object getRenderOptionsWrapper( const Capsule &c )
+{
+ if( auto o = c.getRenderOptions() )
+ {
+ return object( *o );
+ }
+ return object();
+}
+
ScenePlugPtr scene( const Capsule &c )
{
return const_cast( c.scene() );
@@ -102,6 +111,8 @@ void GafferSceneModule::bindHierarchy()
.def( "scene", &scene )
.def( "root", &root )
.def( "context", &context )
+ .def( "setRenderOptions", &Capsule::setRenderOptions )
+ .def( "getRenderOptions", &getRenderOptionsWrapper )
;
GafferBindings::DependencyNodeClass()
diff --git a/src/GafferSceneModule/RenderBinding.cpp b/src/GafferSceneModule/RenderBinding.cpp
index c74207961c3..c2286b12d4e 100644
--- a/src/GafferSceneModule/RenderBinding.cpp
+++ b/src/GafferSceneModule/RenderBinding.cpp
@@ -389,22 +389,22 @@ object transformSamplesWrapper( const Gaffer::M44fPlug &transformPlug, const std
return pythonSamples;
}
-void outputCamerasWrapper( const ScenePlug &scene, const IECore::CompoundObject &globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, IECoreScenePreview::Renderer &renderer )
+void outputCamerasWrapper( const ScenePlug &scene, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, IECoreScenePreview::Renderer &renderer )
{
IECorePython::ScopedGILRelease gilRelease;
- GafferScene::Private::RendererAlgo::outputCameras( &scene, &globals, renderSets, &renderer );
+ GafferScene::Private::RendererAlgo::outputCameras( &scene, renderOptions, renderSets, &renderer );
}
-void outputLightsWrapper( const ScenePlug &scene, const IECore::CompoundObject &globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks &lightLinks, IECoreScenePreview::Renderer &renderer )
+void outputLightsWrapper( const ScenePlug &scene, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks &lightLinks, IECoreScenePreview::Renderer &renderer )
{
IECorePython::ScopedGILRelease gilRelease;
- GafferScene::Private::RendererAlgo::outputLights( &scene, &globals, renderSets, &lightLinks, &renderer );
+ GafferScene::Private::RendererAlgo::outputLights( &scene, renderOptions, renderSets, &lightLinks, &renderer );
}
-void outputObjectsWrapper( const ScenePlug &scene, const IECore::CompoundObject &globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks &lightLinks, IECoreScenePreview::Renderer &renderer, const ScenePlug::ScenePath &root )
+void outputObjectsWrapper( const ScenePlug &scene, const GafferScene::Private::RendererAlgo::RenderOptions &renderOptions, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks &lightLinks, IECoreScenePreview::Renderer &renderer, const ScenePlug::ScenePath &root )
{
IECorePython::ScopedGILRelease gilRelease;
- GafferScene::Private::RendererAlgo::outputObjects( &scene, &globals, renderSets, &lightLinks, &renderer, root );
+ GafferScene::Private::RendererAlgo::outputObjects( &scene, renderOptions, renderSets, &lightLinks, &renderer, root );
}
} // namespace
@@ -444,6 +444,16 @@ void GafferSceneModule::bindRender()
scope rendererAlgomoduleScope( rendererAlgoModule );
+ class_( "RenderOptions" )
+ .def( init() )
+ .def_readwrite( "globals", &GafferScene::Private::RendererAlgo::RenderOptions::globals )
+ .def_readwrite( "transformBlur", &GafferScene::Private::RendererAlgo::RenderOptions::transformBlur )
+ .def_readwrite( "deformationBlur", &GafferScene::Private::RendererAlgo::RenderOptions::deformationBlur )
+ .def_readwrite( "shutter", &GafferScene::Private::RendererAlgo::RenderOptions::shutter )
+ .def_readwrite( "includedPurposes", &GafferScene::Private::RendererAlgo::RenderOptions::includedPurposes )
+ .def( self == self )
+ ;
+
def( "objectSamples", &objectSamplesWrapper, ( arg( "objectPlug" ), arg( "sampleTimes" ), arg( "hash" ) = object(), arg( "_copy" ) = true ) );
def( "transformSamples", &transformSamplesWrapper, ( arg( "transformPlug" ), arg( "sampleTimes" ), arg( "hash" ) = object() ) );