Skip to content

Commit

Permalink
GafferScene : Add matteAdaptor
Browse files Browse the repository at this point in the history
  • Loading branch information
murraystevenson committed Apr 22, 2024
1 parent 122c936 commit bdd9301
Show file tree
Hide file tree
Showing 2 changed files with 235 additions and 0 deletions.
124 changes: 124 additions & 0 deletions python/GafferSceneTest/RenderAdaptorTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,3 +549,127 @@ def assertCameraVisibleObjects( paths, cameraInclusions = None, cameraExclusions
assertCameraVisibleObjects( { "/groupA/cube", "/groupA/sphere" }, cameraInclusions = "CUBE", inclusionOverrides = "/groupA/sphere" )
assertCameraVisibleObjects( { "/groupA/cube", "/groupA/sphere" }, cameraInclusions = "", inclusionOverrides = "A" )
assertCameraVisibleObjects( {}, cameraInclusions = "/", exclusionOverrides = "A" )

def testMatteAdaptor( self ) :

# /groupA
# /cube (A, CUBE)
# /sphere (A, SPHERE)

cubeA = GafferScene.Cube()
cubeA["sets"].setValue( "A CUBE" )

sphereA = GafferScene.Sphere()
sphereA["sets"].setValue( "A SPHERE" )

groupA = GafferScene.Group()
groupA["name"].setValue( "groupA" )
groupA["in"][0].setInput( cubeA["out"] )
groupA["in"][1].setInput( sphereA["out"] )

customOptions = GafferScene.CustomOptions()
customOptions["in"].setInput( groupA["out"] )
customOptions["options"].addChild( Gaffer.NameValuePlug( "render:matteInclusions", "", True, "matteInclusions" ) )
customOptions["options"].addChild( Gaffer.NameValuePlug( "render:matteExclusions", "", True, "matteExclusions" ) )

inclusionAttributesFilter = GafferScene.SetFilter()
inclusionAttributes = GafferScene.CustomAttributes()
inclusionAttributes["in"].setInput( customOptions["out"] )
inclusionAttributes["filter"].setInput( inclusionAttributesFilter["out"] )
inclusionAttributes["attributes"].addChild( Gaffer.NameValuePlug( "ai:matte", True, True, "aiMatte" ) )
inclusionAttributes["attributes"].addChild( Gaffer.NameValuePlug( "cycles:use_holdout", True, True, "cyclesUseHoldout" ) )
inclusionAttributes["attributes"].addChild( Gaffer.NameValuePlug( "dl:matte", True, True, "dlMatte" ) )

exclusionAttributesFilter = GafferScene.SetFilter()
exclusionAttributes = GafferScene.CustomAttributes()
exclusionAttributes["in"].setInput( inclusionAttributes["out"] )
exclusionAttributes["filter"].setInput( exclusionAttributesFilter["out"] )
exclusionAttributes["attributes"].addChild( Gaffer.NameValuePlug( "ai:matte", False, True, "aiMatte" ) )
exclusionAttributes["attributes"].addChild( Gaffer.NameValuePlug( "cycles:use_holdout", False, True, "cyclesUseHoldout" ) )
exclusionAttributes["attributes"].addChild( Gaffer.NameValuePlug( "dl:matte", False, True, "dlMatte" ) )

# Create adaptors for the CapturingRenderer
testAdaptors = GafferScene.SceneAlgo.createRenderAdaptors()
testAdaptors["in"].setInput( exclusionAttributes["out"] )

def assertMatte( paths, matteInclusions = None, matteExclusions = None, inclusionOverrides = "", exclusionOverrides = "" ) :

if matteInclusions is not None :
customOptions["options"]["matteInclusions"]["value"].setValue( matteInclusions )
customOptions["options"]["matteInclusions"]["enabled"].setValue( matteInclusions is not None )

if matteExclusions is not None :
customOptions["options"]["matteExclusions"]["value"].setValue( matteExclusions )
customOptions["options"]["matteExclusions"]["enabled"].setValue( matteExclusions is not None )

inclusionAttributesFilter["setExpression"].setValue( inclusionOverrides )
exclusionAttributesFilter["setExpression"].setValue( exclusionOverrides )

allPaths = {
"/groupA/cube",
"/groupA/sphere",
}

renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer(
GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch
)
GafferScene.Private.RendererAlgo.outputObjects(
testAdaptors["out"], GafferScene.Private.RendererAlgo.RenderOptions( testAdaptors["out"] ), GafferScene.Private.RendererAlgo.RenderSets( testAdaptors["out"] ), GafferScene.Private.RendererAlgo.LightLinks(),
renderer
)

for path in allPaths :
capturedObject = renderer.capturedObject( path )
for attribute in [ "ai:matte", "cycles:use_holdout", "dl:matte" ] :
if path in paths :
# path is matte only by the presence of the attribute with a value of True
self.assertTrue( attribute in capturedObject.capturedAttributes().attributes() )
self.assertTrue( capturedObject.capturedAttributes().attributes()[attribute].value )
else :
# path isn't matte by the absence of the attribute, or its presence with a value of False
if attribute in capturedObject.capturedAttributes().attributes() :
self.assertFalse( capturedObject.capturedAttributes().attributes()[attribute].value )

# Nothing should be matte when matte inclusions and exclusions are empty or undefined
assertMatte( {} )
assertMatte( {}, matteInclusions = "" )
assertMatte( {}, matteExclusions = "" )
assertMatte( {}, matteInclusions = "", matteExclusions = "" )

# Including the root of the scene should make everything matte
assertMatte( { "/groupA/cube", "/groupA/sphere" }, matteInclusions = "/" )

# Including the group should make its descendants matte
assertMatte( { "/groupA/cube", "/groupA/sphere" }, matteInclusions = "/groupA" )
# Unless a descendant has been excluded
assertMatte( { "/groupA/cube" }, matteInclusions = "/groupA", matteExclusions = "/groupA/sphere" )

# Including a specific location should not affect its siblings
assertMatte( { "/groupA/cube" }, matteInclusions = "/groupA/cube" )

# Test a variety of set expressions
assertMatte( { "/groupA/cube", "/groupA/sphere" }, matteInclusions = "A" )
assertMatte( { "/groupA/sphere" }, matteInclusions = "SPHERE" )
assertMatte( { "/groupA/cube" }, matteInclusions = "CUBE" )
assertMatte( { "/groupA/sphere" }, matteInclusions = "A - CUBE" )

# Exclusions should override inclusions
assertMatte( {}, matteInclusions = "A", matteExclusions = "A" )
assertMatte( {}, matteInclusions = "/groupA/sphere", matteExclusions = "/groupA/sphere" )
assertMatte( { "/groupA/sphere" }, matteInclusions = "A", matteExclusions = "CUBE" )
assertMatte( { "/groupA/cube" }, matteInclusions = "/groupA", matteExclusions = "SPHERE" )
assertMatte( { "/groupA/sphere" }, matteInclusions = "A", matteExclusions = "/groupA/cube" )

# Matte inclusions override matte exclusions at lower locations
assertMatte( { "/groupA/sphere" }, matteInclusions = "/groupA/sphere", matteExclusions = "/groupA" )

# Test interaction with scene attributes
assertMatte( { "/groupA/sphere" }, inclusionOverrides = "/groupA/sphere" )
assertMatte( { "/groupA/cube", "/groupA/sphere" }, matteInclusions = "/groupA/cube", inclusionOverrides = "/groupA/sphere" )
assertMatte( { "/groupA/sphere" }, matteExclusions = "/groupA/sphere", inclusionOverrides = "/groupA/sphere" )
assertMatte( { "/groupA/sphere" }, matteExclusions = "/groupA", inclusionOverrides = "/groupA/sphere" )
assertMatte( { "/groupA/cube", "/groupA/sphere" }, matteInclusions = "/groupA", matteExclusions = "/groupA/sphere", inclusionOverrides = "/groupA/sphere" )

assertMatte( {}, matteInclusions = "/groupA/sphere", exclusionOverrides = "/groupA/sphere" )
assertMatte( { "/groupA/sphere" }, matteInclusions = "/groupA/sphere", exclusionOverrides = "/groupA" )
assertMatte( { "/groupA/sphere" }, matteInclusions = "/groupA", exclusionOverrides = "/groupA/cube" )
111 changes: 111 additions & 0 deletions startup/GafferScene/matteAdaptor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
##########################################################################
#
# Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with
# the distribution.
#
# * Neither the name of John Haddon nor the names of
# any other contributors to this software may be used to endorse or
# promote products derived from this software without specific prior
# written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##########################################################################

import inspect

import Gaffer
import GafferScene

def __matteAdaptor() :

processor = GafferScene.SceneProcessor()

processor["__optionQuery"] = GafferScene.OptionQuery()
processor["__optionQuery"]["scene"].setInput( processor["in"] )

processor["__optionQuery"].addQuery( Gaffer.StringPlug() )
processor["__optionQuery"].addQuery( Gaffer.StringPlug() )

processor["__optionQuery"]["queries"][0]["name"].setValue( "render:matteInclusions" )
processor["__optionQuery"]["queries"][1]["name"].setValue( "render:matteExclusions" )

processor["__matteInclusionsFilter"] = GafferScene.SetFilter()
processor["__matteInclusionsExpression"] = Gaffer.Expression()
processor["__matteInclusionsExpression"].setExpression(
inspect.cleandoc(
"""
matteInclusions = parent["__optionQuery"]["out"]["out0"]["value"]
matteExclusions = parent["__optionQuery"]["out"]["out1"]["value"]
parent["__matteInclusionsFilter"]["setExpression"] = "({}) - ({})".format( matteInclusions, matteExclusions ) if ( matteInclusions and matteExclusions ) else matteInclusions
"""
)
)

processor["__filterQuery"] = GafferScene.FilterQuery()
processor["__filterQuery"]["scene"].setInput( processor["in"] )
processor["__filterQuery"]["filter"].setInput( processor["__matteInclusionsFilter"]["out"] )
processor["__filterQuery"]["location"].setValue( "/" )

processor["__allMatte"] = GafferScene.CustomAttributes()
processor["__allMatte"]["in"].setInput( processor["in"] )
processor["__allMatte"]["attributes"].addChild( Gaffer.NameValuePlug( "ai:matte", Gaffer.BoolPlug( defaultValue = True ) ) )
processor["__allMatte"]["attributes"].addChild( Gaffer.NameValuePlug( "cycles:use_holdout", Gaffer.BoolPlug( defaultValue = True ) ) )
processor["__allMatte"]["attributes"].addChild( Gaffer.NameValuePlug( "dl:matte", Gaffer.BoolPlug( defaultValue = True ) ) )
processor["__allMatte"]["global"].setValue( True )
# all locations are matte if `render:matteInclusions` matches the root of the scene
processor["__allMatte"]["enabled"].setInput( processor["__filterQuery"]["exactMatch"] )

processor["__matteInclusions"] = GafferScene.AttributeTweaks()
processor["__matteInclusions"]["in"].setInput( processor["__allMatte"]["out"] )
processor["__matteInclusions"]["tweaks"].addChild( Gaffer.TweakPlug( "ai:matte", Gaffer.BoolPlug( defaultValue = True ), mode = Gaffer.TweakPlug.Mode.CreateIfMissing ) )
processor["__matteInclusions"]["tweaks"].addChild( Gaffer.TweakPlug( "cycles:use_holdout", Gaffer.BoolPlug( defaultValue = True ), mode = Gaffer.TweakPlug.Mode.CreateIfMissing ) )
processor["__matteInclusions"]["tweaks"].addChild( Gaffer.TweakPlug( "dl:matte", Gaffer.BoolPlug( defaultValue = True ), mode = Gaffer.TweakPlug.Mode.CreateIfMissing ) )

processor["__matteInclusions"]["filter"].setInput( processor["__matteInclusionsFilter"]["out"] )
# __matteInclusions is only required when `render:matteInclusions` exists and the root of the scene hasn't been set as matte
processor["__matteInclusionsEnabledExpression"] = Gaffer.Expression()
processor["__matteInclusionsEnabledExpression"].setExpression(
"""parent["__matteInclusions"]["enabled"] = parent["__optionQuery"]["out"]["out0"]["exists"] and not parent["__filterQuery"]["exactMatch"]"""
)

processor["__matteExclusionsFilter"] = GafferScene.SetFilter()
processor["__matteExclusionsFilter"]["setExpression"].setInput( processor["__optionQuery"]["out"][1]["value"] )

processor["__matteExclusions"] = GafferScene.AttributeTweaks()
processor["__matteExclusions"]["in"].setInput( processor["__matteInclusions"]["out"] )
processor["__matteExclusions"]["tweaks"].addChild( Gaffer.TweakPlug( "ai:matte", Gaffer.BoolPlug(), mode = Gaffer.TweakPlug.Mode.CreateIfMissing ) )
processor["__matteExclusions"]["tweaks"].addChild( Gaffer.TweakPlug( "cycles:use_holdout", Gaffer.BoolPlug(), mode = Gaffer.TweakPlug.Mode.CreateIfMissing ) )
processor["__matteExclusions"]["tweaks"].addChild( Gaffer.TweakPlug( "dl:matte", Gaffer.BoolPlug(), mode = Gaffer.TweakPlug.Mode.CreateIfMissing ) )

processor["__matteExclusions"]["filter"].setInput( processor["__matteExclusionsFilter"]["out"] )
# __matteExclusions is only required when `render:matteExclusions` exists
processor["__matteExclusions"]["enabled"].setInput( processor["__optionQuery"]["out"][1]["exists"] )

processor["out"].setInput( processor["__matteExclusions"]["out"] )

return processor

GafferScene.SceneAlgo.registerRenderAdaptor( "MatteAdaptor", __matteAdaptor )

0 comments on commit bdd9301

Please sign in to comment.