diff --git a/Changes.md b/Changes.md index e058c16cefe..7f07eb6cbb1 100644 --- a/Changes.md +++ b/Changes.md @@ -1,6 +1,11 @@ 1.4.x.x (relative to 1.4.4.0) ======= +Features +-------- + +- ShaderTweaks : Added support for creating ShaderTweakProxy nodes that allow making input connections to the original network. + Improvements ------------ diff --git a/doc/examples/sceneProcessing/shaderTweaks.gfr b/doc/examples/sceneProcessing/shaderTweaks.gfr new file mode 100644 index 00000000000..144b15cc409 --- /dev/null +++ b/doc/examples/sceneProcessing/shaderTweaks.gfr @@ -0,0 +1,363 @@ +import Gaffer +import GafferCycles +import GafferImage +import GafferScene +import IECore +import imath + +Gaffer.Metadata.registerValue( parent, "serialiser:milestoneVersion", 1, persistent=False ) +Gaffer.Metadata.registerValue( parent, "serialiser:majorVersion", 4, persistent=False ) +Gaffer.Metadata.registerValue( parent, "serialiser:minorVersion", 4, persistent=False ) +Gaffer.Metadata.registerValue( parent, "serialiser:patchVersion", 0, persistent=False ) + +__children = {} + +parent["variables"].addChild( Gaffer.NameValuePlug( "image:catalogue:port", Gaffer.IntPlug( "value", defaultValue = 0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ), "imageCataloguePort", Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) ) +parent["variables"].addChild( Gaffer.NameValuePlug( "project:name", Gaffer.StringPlug( "value", defaultValue = 'default', flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ), "projectName", Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) ) +parent["variables"].addChild( Gaffer.NameValuePlug( "project:rootDirectory", Gaffer.StringPlug( "value", defaultValue = '$HOME/gaffer/projects/${project:name}', flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ), "projectRootDirectory", Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) ) +__children["Backdrop"] = Gaffer.Backdrop( "Backdrop" ) +parent.addChild( __children["Backdrop"] ) +__children["Backdrop"].addChild( Gaffer.Box2fPlug( "__uiBound", defaultValue = imath.Box2f( imath.V2f( -10, -10 ), imath.V2f( 10, 10 ) ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Backdrop"].addChild( Gaffer.Box2fPlug( "__uiBound1", defaultValue = imath.Box2f( imath.V2f( -10, -10 ), imath.V2f( 10, 10 ) ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Backdrop"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["openColorIO"] = GafferImage.OpenColorIOConfigPlug( "openColorIO", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) +parent.addChild( __children["openColorIO"] ) +__children["defaultFormat"] = GafferImage.FormatPlug( "defaultFormat", defaultValue = GafferImage.Format( 1920, 1080, 1.000 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) +parent.addChild( __children["defaultFormat"] ) +__children["Sphere"] = GafferScene.Sphere( "Sphere" ) +parent.addChild( __children["Sphere"] ) +__children["Sphere"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["ShaderAssignment"] = GafferScene.ShaderAssignment( "ShaderAssignment" ) +parent.addChild( __children["ShaderAssignment"] ) +__children["ShaderAssignment"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["principled_bsdf"] = GafferCycles.CyclesShader( "principled_bsdf" ) +parent.addChild( __children["principled_bsdf"] ) +__children["principled_bsdf"].loadShader( "principled_bsdf" ) +__children["principled_bsdf"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["PathFilter1"] = GafferScene.PathFilter( "PathFilter1" ) +parent.addChild( __children["PathFilter1"] ) +__children["PathFilter1"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Parent"] = GafferScene.Parent( "Parent" ) +parent.addChild( __children["Parent"] ) +__children["Parent"]["children"].addChild( GafferScene.ScenePlug( "child1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Parent"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["distant_light"] = GafferCycles.CyclesLight( "distant_light" ) +parent.addChild( __children["distant_light"] ) +__children["distant_light"].loadShader( "distant_light" ) +__children["distant_light"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["image_texture"] = GafferCycles.CyclesShader( "image_texture" ) +parent.addChild( __children["image_texture"] ) +__children["image_texture"].loadShader( "image_texture" ) +__children["image_texture"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Backdrop7"] = Gaffer.Backdrop( "Backdrop7" ) +parent.addChild( __children["Backdrop7"] ) +__children["Backdrop7"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Backdrop7"].addChild( Gaffer.Box2fPlug( "__uiBound", defaultValue = imath.Box2f( imath.V2f( -10, -10 ), imath.V2f( 10, 10 ) ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["ShaderTweaks"] = GafferScene.ShaderTweaks( "ShaderTweaks" ) +parent.addChild( __children["ShaderTweaks"] ) +__children["ShaderTweaks"]["tweaks"].addChild( Gaffer.TweakPlug( Gaffer.FloatPlug( "value", defaultValue = 0.30000001192092896, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ), "roughness", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["ShaderTweaks"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["PathFilter2"] = GafferScene.PathFilter( "PathFilter2" ) +parent.addChild( __children["PathFilter2"] ) +__children["PathFilter2"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Backdrop8"] = Gaffer.Backdrop( "Backdrop8" ) +parent.addChild( __children["Backdrop8"] ) +__children["Backdrop8"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Backdrop8"].addChild( Gaffer.Box2fPlug( "__uiBound", defaultValue = imath.Box2f( imath.V2f( -10, -10 ), imath.V2f( 10, 10 ) ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Dot"] = Gaffer.Dot( "Dot" ) +parent.addChild( __children["Dot"] ) +__children["Dot"].setup( GafferScene.ScenePlug( "in", ) ) +__children["Dot"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Dot1"] = Gaffer.Dot( "Dot1" ) +parent.addChild( __children["Dot1"] ) +__children["Dot1"].setup( GafferScene.ScenePlug( "in", ) ) +__children["Dot1"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Dot2"] = Gaffer.Dot( "Dot2" ) +parent.addChild( __children["Dot2"] ) +__children["Dot2"].setup( GafferScene.ScenePlug( "in", ) ) +__children["Dot2"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["ShaderTweaks1"] = GafferScene.ShaderTweaks( "ShaderTweaks1" ) +parent.addChild( __children["ShaderTweaks1"] ) +__children["ShaderTweaks1"]["tweaks"].addChild( Gaffer.TweakPlug( Gaffer.V3fPlug( "value", defaultValue = imath.V3f( 0.5, 0.5, 1 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ), "image_texture_tex_mapping__scale", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["ShaderTweaks1"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["PathFilter3"] = GafferScene.PathFilter( "PathFilter3" ) +parent.addChild( __children["PathFilter3"] ) +__children["PathFilter3"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Backdrop9"] = Gaffer.Backdrop( "Backdrop9" ) +parent.addChild( __children["Backdrop9"] ) +__children["Backdrop9"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Backdrop9"].addChild( Gaffer.Box2fPlug( "__uiBound", defaultValue = imath.Box2f( imath.V2f( -10, -10 ), imath.V2f( 10, 10 ) ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Dot3"] = Gaffer.Dot( "Dot3" ) +parent.addChild( __children["Dot3"] ) +__children["Dot3"].setup( GafferScene.ScenePlug( "in", ) ) +__children["Dot3"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Dot4"] = Gaffer.Dot( "Dot4" ) +parent.addChild( __children["Dot4"] ) +__children["Dot4"].setup( GafferScene.ScenePlug( "in", ) ) +__children["Dot4"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Dot5"] = Gaffer.Dot( "Dot5" ) +parent.addChild( __children["Dot5"] ) +__children["Dot5"].setup( GafferScene.ScenePlug( "in", ) ) +__children["Dot5"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["ShaderTweaks2"] = GafferScene.ShaderTweaks( "ShaderTweaks2" ) +parent.addChild( __children["ShaderTweaks2"] ) +__children["ShaderTweaks2"]["tweaks"].addChild( Gaffer.TweakPlug( Gaffer.FloatPlug( "value", defaultValue = 0.0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ), "emission_strength", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["ShaderTweaks2"]["tweaks"].addChild( Gaffer.TweakPlug( Gaffer.Color3fPlug( "value", defaultValue = imath.Color3f( 1, 1, 1 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ), "emission_color", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["ShaderTweaks2"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["PathFilter4"] = GafferScene.PathFilter( "PathFilter4" ) +parent.addChild( __children["PathFilter4"] ) +__children["PathFilter4"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Backdrop10"] = Gaffer.Backdrop( "Backdrop10" ) +parent.addChild( __children["Backdrop10"] ) +__children["Backdrop10"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Backdrop10"].addChild( Gaffer.Box2fPlug( "__uiBound", defaultValue = imath.Box2f( imath.V2f( -10, -10 ), imath.V2f( 10, 10 ) ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Dot6"] = Gaffer.Dot( "Dot6" ) +parent.addChild( __children["Dot6"] ) +__children["Dot6"].setup( GafferScene.ScenePlug( "in", ) ) +__children["Dot6"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Dot7"] = Gaffer.Dot( "Dot7" ) +parent.addChild( __children["Dot7"] ) +__children["Dot7"].setup( GafferScene.ScenePlug( "in", ) ) +__children["Dot7"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Dot8"] = Gaffer.Dot( "Dot8" ) +parent.addChild( __children["Dot8"] ) +__children["Dot8"].setup( GafferScene.ScenePlug( "in", ) ) +__children["Dot8"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["noise_texture"] = GafferCycles.CyclesShader( "noise_texture" ) +parent.addChild( __children["noise_texture"] ) +__children["noise_texture"].loadShader( "noise_texture" ) +__children["noise_texture"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["rgb_ramp"] = GafferCycles.CyclesShader( "rgb_ramp" ) +parent.addChild( __children["rgb_ramp"] ) +__children["rgb_ramp"].loadShader( "rgb_ramp" ) +__children["rgb_ramp"]["parameters"]["ramp"].clearPoints() +__children["rgb_ramp"]["parameters"]["ramp"].addChild( Gaffer.ValuePlug( "p0", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["rgb_ramp"]["parameters"]["ramp"]["p0"].addChild( Gaffer.FloatPlug( "x", defaultValue = 0.0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["rgb_ramp"]["parameters"]["ramp"]["p0"].addChild( Gaffer.Color3fPlug( "y", defaultValue = imath.Color3f( 0, 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["rgb_ramp"]["parameters"]["ramp"].addChild( Gaffer.ValuePlug( "p1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["rgb_ramp"]["parameters"]["ramp"]["p1"].addChild( Gaffer.FloatPlug( "x", defaultValue = 1.0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["rgb_ramp"]["parameters"]["ramp"]["p1"].addChild( Gaffer.Color3fPlug( "y", defaultValue = imath.Color3f( 1, 1, 1 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["rgb_ramp"]["parameters"]["ramp"].addChild( Gaffer.ValuePlug( "p2", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["rgb_ramp"]["parameters"]["ramp"]["p2"].addChild( Gaffer.FloatPlug( "x", defaultValue = 0.0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["rgb_ramp"]["parameters"]["ramp"]["p2"].addChild( Gaffer.Color3fPlug( "y", defaultValue = imath.Color3f( 0, 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["rgb_ramp"]["parameters"]["ramp_alpha"].clearPoints() +__children["rgb_ramp"]["parameters"]["ramp_alpha"].addChild( Gaffer.ValuePlug( "p0", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["rgb_ramp"]["parameters"]["ramp_alpha"]["p0"].addChild( Gaffer.FloatPlug( "x", defaultValue = 0.0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["rgb_ramp"]["parameters"]["ramp_alpha"]["p0"].addChild( Gaffer.FloatPlug( "y", defaultValue = 0.0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["rgb_ramp"]["parameters"]["ramp_alpha"].addChild( Gaffer.ValuePlug( "p1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["rgb_ramp"]["parameters"]["ramp_alpha"]["p1"].addChild( Gaffer.FloatPlug( "x", defaultValue = 1.0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["rgb_ramp"]["parameters"]["ramp_alpha"]["p1"].addChild( Gaffer.FloatPlug( "y", defaultValue = 1.0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["rgb_ramp"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["ShaderTweaks3"] = GafferScene.ShaderTweaks( "ShaderTweaks3" ) +parent.addChild( __children["ShaderTweaks3"] ) +__children["ShaderTweaks3"]["tweaks"].addChild( Gaffer.TweakPlug( Gaffer.Color3fPlug( "value", defaultValue = imath.Color3f( 0, 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ), "base_color", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["ShaderTweaks3"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["PathFilter5"] = GafferScene.PathFilter( "PathFilter5" ) +parent.addChild( __children["PathFilter5"] ) +__children["PathFilter5"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Backdrop11"] = Gaffer.Backdrop( "Backdrop11" ) +parent.addChild( __children["Backdrop11"] ) +__children["Backdrop11"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Backdrop11"].addChild( Gaffer.Box2fPlug( "__uiBound", defaultValue = imath.Box2f( imath.V2f( -10, -10 ), imath.V2f( 10, 10 ) ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Dot9"] = Gaffer.Dot( "Dot9" ) +parent.addChild( __children["Dot9"] ) +__children["Dot9"].setup( GafferScene.ScenePlug( "in", ) ) +__children["Dot9"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Dot10"] = Gaffer.Dot( "Dot10" ) +parent.addChild( __children["Dot10"] ) +__children["Dot10"].setup( GafferScene.ScenePlug( "in", ) ) +__children["Dot10"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Dot11"] = Gaffer.Dot( "Dot11" ) +parent.addChild( __children["Dot11"] ) +__children["Dot11"].setup( GafferScene.ScenePlug( "in", ) ) +__children["Dot11"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Auto"] = GafferScene.ShaderTweakProxy( "Auto" ) +parent.addChild( __children["Auto"] ) +__children["Auto"].loadShader( "autoProxy" ) +__children["Auto"]["out"].addChild( Gaffer.Color3fPlug( "auto", direction = Gaffer.Plug.Direction.Out, defaultValue = imath.Color3f( 0, 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Auto"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["brightness_contrast"] = GafferCycles.CyclesShader( "brightness_contrast" ) +parent.addChild( __children["brightness_contrast"] ) +__children["brightness_contrast"].loadShader( "brightness_contrast" ) +__children["brightness_contrast"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["ShaderTweaks4"] = GafferScene.ShaderTweaks( "ShaderTweaks4" ) +parent.addChild( __children["ShaderTweaks4"] ) +__children["ShaderTweaks4"]["tweaks"].addChild( Gaffer.TweakPlug( Gaffer.Color3fPlug( "value", defaultValue = imath.Color3f( 0, 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ), "base_color", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["ShaderTweaks4"]["tweaks"].addChild( Gaffer.TweakPlug( Gaffer.FloatPlug( "value", defaultValue = 0.0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ), "emission_strength", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["ShaderTweaks4"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["PathFilter6"] = GafferScene.PathFilter( "PathFilter6" ) +parent.addChild( __children["PathFilter6"] ) +__children["PathFilter6"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Backdrop12"] = Gaffer.Backdrop( "Backdrop12" ) +parent.addChild( __children["Backdrop12"] ) +__children["Backdrop12"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Backdrop12"].addChild( Gaffer.Box2fPlug( "__uiBound", defaultValue = imath.Box2f( imath.V2f( -10, -10 ), imath.V2f( 10, 10 ) ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Dot12"] = Gaffer.Dot( "Dot12" ) +parent.addChild( __children["Dot12"] ) +__children["Dot12"].setup( GafferScene.ScenePlug( "in", ) ) +__children["Dot12"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Dot13"] = Gaffer.Dot( "Dot13" ) +parent.addChild( __children["Dot13"] ) +__children["Dot13"].setup( GafferScene.ScenePlug( "in", ) ) +__children["Dot13"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Dot14"] = Gaffer.Dot( "Dot14" ) +parent.addChild( __children["Dot14"] ) +__children["Dot14"].setup( GafferScene.ScenePlug( "in", ) ) +__children["Dot14"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["image_texture1"] = GafferScene.ShaderTweakProxy( "image_texture1" ) +parent.addChild( __children["image_texture1"] ) +__children["image_texture1"].loadShader( "cycles:image_texture" ) +__children["image_texture1"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +parent["variables"]["imageCataloguePort"]["value"].setValue( 42309 ) +Gaffer.Metadata.registerValue( parent["variables"]["imageCataloguePort"], 'readOnly', True ) +Gaffer.Metadata.registerValue( parent["variables"]["projectName"]["name"], 'readOnly', True ) +Gaffer.Metadata.registerValue( parent["variables"]["projectRootDirectory"]["name"], 'readOnly', True ) +__children["Backdrop"]["title"].setValue( 'Example: Shader Tweaks' ) +__children["Backdrop"]["scale"].setValue( 1.5 ) +__children["Backdrop"]["description"].setValue( 'This example shows some of the ways to use the ShaderTweaks node to modify shader networks.\n' ) +__children["Backdrop"]["__uiBound"].setValue( imath.Box2f( imath.V2f( 0, 0 ), imath.V2f( 60, 37.7212219 ) ) ) +__children["Backdrop"]["__uiPosition"].setValue( imath.V2f( -87.729538, 144.494415 ) ) +__children["Sphere"]["__uiPosition"].setValue( imath.V2f( 46.9047699, 164.237778 ) ) +__children["ShaderAssignment"]["in"].setInput( __children["Sphere"]["out"] ) +__children["ShaderAssignment"]["filter"].setInput( __children["PathFilter1"]["out"] ) +__children["ShaderAssignment"]["shader"].setInput( __children["principled_bsdf"]["out"]["BSDF"] ) +__children["ShaderAssignment"]["__uiPosition"].setValue( imath.V2f( 46.9047699, 156.073715 ) ) +__children["principled_bsdf"]["parameters"]["base_color"].setInput( __children["image_texture"]["out"]["color"] ) +__children["principled_bsdf"]["parameters"]["roughness"].setValue( 0.30000001192092896 ) +__children["principled_bsdf"]["parameters"]["subsurface_radius"].setValue( imath.V3f( 1, 0.200000003, 0.100000001 ) ) +__children["principled_bsdf"]["parameters"]["specular_ior_level"].setValue( 0.5 ) +__children["principled_bsdf"]["__uiPosition"].setValue( imath.V2f( 31.5053749, 156.129333 ) ) +__children["PathFilter1"]["paths"].setValue( IECore.StringVectorData( [ '/sphere' ] ) ) +__children["PathFilter1"]["__uiPosition"].setValue( imath.V2f( 60.1454582, 160.092712 ) ) +__children["Parent"]["in"].setInput( __children["ShaderAssignment"]["out"] ) +__children["Parent"]["parent"].setValue( '/' ) +__children["Parent"]["children"][0].setInput( __children["distant_light"]["out"] ) +__children["Parent"]["__uiPosition"].setValue( imath.V2f( 49.1547699, 142.406265 ) ) +__children["distant_light"]["transform"]["translate"].setValue( imath.V3f( 2.74629998, 0, 0 ) ) +__children["distant_light"]["transform"]["rotate"].setValue( imath.V3f( -47.4163017, 36.5225983, 0 ) ) +__children["distant_light"]["parameters"]["exposure"].setValue( 1.0 ) +__children["distant_light"]["__uiPosition"].setValue( imath.V2f( 71.8731079, 150.88829 ) ) +__children["image_texture"]["parameters"]["tex_mapping__scale"].setValue( imath.V3f( 0.5, 0.5, 1 ) ) +__children["image_texture"]["parameters"]["filename"].setValue( '${GAFFER_ROOT}/resources/images/macaw.exr' ) +__children["image_texture"]["__uiPosition"].setValue( imath.V2f( 16.2441425, 171.129318 ) ) +__children["Backdrop7"]["title"].setValue( 'Sample Source Scene' ) +__children["Backdrop7"]["__uiPosition"].setValue( imath.V2f( 45.1973228, 156.27034 ) ) +__children["Backdrop7"]["__uiBound"].setValue( imath.Box2f( imath.V2f( -37.4451256, -19.9410095 ), imath.V2f( 43.4451256, 25.9410095 ) ) ) +__children["ShaderTweaks"]["in"].setInput( __children["Dot"]["out"] ) +__children["ShaderTweaks"]["filter"].setInput( __children["PathFilter2"]["out"] ) +__children["ShaderTweaks"]["shader"].setValue( 'cycles:surface' ) +__children["ShaderTweaks"]["tweaks"]["roughness"]["name"].setValue( 'roughness' ) +__children["ShaderTweaks"]["tweaks"]["roughness"]["value"].setValue( 0.10000000149011612 ) +__children["ShaderTweaks"]["__uiPosition"].setValue( imath.V2f( -44.1000366, 91.7172546 ) ) +__children["PathFilter2"]["paths"].setValue( IECore.StringVectorData( [ '/sphere' ] ) ) +__children["PathFilter2"]["__uiPosition"].setValue( imath.V2f( -30.8247299, 98.4154205 ) ) +__children["Backdrop8"]["title"].setValue( 'A Basic Tweak To The Surface Shader' ) +__children["Backdrop8"]["description"].setValue( 'This tweak was created by:\n* creating a ShaderTweaks node\n* filtering to /sphere\n* setting the "shader" setting to "Cycles Surface", so that it knows what kind of shader to target\n* clicking the "+" button, choosing "From Affected", and navigating to the roughness parameter' ) +__children["Backdrop8"]["__uiPosition"].setValue( imath.V2f( -66.1594467, 106.003471 ) ) +__children["Backdrop8"]["__uiBound"].setValue( imath.Box2f( imath.V2f( -15, -23.6519547 ), imath.V2f( 47.9447174, 14.1233864 ) ) ) +__children["Dot"]["in"].setInput( __children["Dot2"]["out"] ) +__children["Dot"]["__uiPosition"].setValue( imath.V2f( -44.1000366, 99.0492859 ) ) +__children["Dot1"]["in"].setInput( __children["Parent"]["out"] ) +__children["Dot1"]["__uiPosition"].setValue( imath.V2f( -21.423481, 117.248367 ) ) +__children["Dot2"]["in"].setInput( __children["Dot1"]["out"] ) +__children["Dot2"]["__uiPosition"].setValue( imath.V2f( -21.4234791, 105.549286 ) ) +__children["ShaderTweaks1"]["in"].setInput( __children["Dot3"]["out"] ) +__children["ShaderTweaks1"]["filter"].setInput( __children["PathFilter3"]["out"] ) +__children["ShaderTweaks1"]["shader"].setValue( 'cycles:surface' ) +__children["ShaderTweaks1"]["tweaks"]["image_texture_tex_mapping__scale"]["name"].setValue( 'image_texture.tex_mapping__scale' ) +__children["ShaderTweaks1"]["tweaks"]["image_texture_tex_mapping__scale"]["mode"].setValue( 3 ) +__children["ShaderTweaks1"]["tweaks"]["image_texture_tex_mapping__scale"]["value"].setValue( imath.V3f( 0.5, 0.5, 0.5 ) ) +__children["ShaderTweaks1"]["__uiPosition"].setValue( imath.V2f( 26.9852219, 91.4676056 ) ) +__children["PathFilter3"]["paths"].setValue( IECore.StringVectorData( [ '/sphere' ] ) ) +__children["PathFilter3"]["__uiPosition"].setValue( imath.V2f( 40.2605286, 98.1657715 ) ) +__children["Backdrop9"]["title"].setValue( 'Any Shader In The Network Can Be Targeted' ) +__children["Backdrop9"]["description"].setValue( 'Here we target the scale of the texture shader. We can also use special modes like "Multiply" to make adjustments relative to the current values.' ) +__children["Backdrop9"]["__uiPosition"].setValue( imath.V2f( 4.92581177, 105.753822 ) ) +__children["Backdrop9"]["__uiBound"].setValue( imath.Box2f( imath.V2f( -15, -23.6519547 ), imath.V2f( 47.9447174, 14.1233864 ) ) ) +__children["Dot3"]["in"].setInput( __children["Dot5"]["out"] ) +__children["Dot3"]["__uiPosition"].setValue( imath.V2f( 26.9852219, 98.7996368 ) ) +__children["Dot4"]["in"].setInput( __children["Parent"]["out"] ) +__children["Dot4"]["__uiPosition"].setValue( imath.V2f( 49.6617737, 116.998726 ) ) +__children["Dot5"]["in"].setInput( __children["Dot4"]["out"] ) +__children["Dot5"]["__uiPosition"].setValue( imath.V2f( 49.6617737, 105.299637 ) ) +__children["ShaderTweaks2"]["in"].setInput( __children["Dot6"]["out"] ) +__children["ShaderTweaks2"]["filter"].setInput( __children["PathFilter4"]["out"] ) +__children["ShaderTweaks2"]["shader"].setValue( 'cycles:surface' ) +__children["ShaderTweaks2"]["tweaks"]["emission_strength"]["name"].setValue( 'emission_strength' ) +__children["ShaderTweaks2"]["tweaks"]["emission_strength"]["value"].setValue( 0.30000001192092896 ) +__children["ShaderTweaks2"]["tweaks"]["emission_color"]["name"].setValue( 'emission_color' ) +Gaffer.Metadata.registerValue( __children["ShaderTweaks2"]["tweaks"]["emission_color"], 'noduleLayout:visible', True ) +__children["ShaderTweaks2"]["tweaks"]["emission_color"]["value"].setInput( __children["rgb_ramp"]["out"]["color"] ) +__children["ShaderTweaks2"]["__uiPosition"].setValue( imath.V2f( 97.2128067, 91.651329 ) ) +__children["PathFilter4"]["paths"].setValue( IECore.StringVectorData( [ '/sphere' ] ) ) +__children["PathFilter4"]["__uiPosition"].setValue( imath.V2f( 110.488113, 98.3494949 ) ) +__children["Backdrop10"]["title"].setValue( 'New Shaders Can Be Connected' ) +__children["Backdrop10"]["description"].setValue( 'Here we add a tweak on emission_color, create a new noise shader, and drag a connection to the "+" icon on the ShaderTweaks node in the Graph Editor.\n\nThere is also a ramp inserted here, illustrating that arbitrary networks of new shaders can be connected.' ) +__children["Backdrop10"]["__uiPosition"].setValue( imath.V2f( 75.1533966, 105.937546 ) ) +__children["Backdrop10"]["__uiBound"].setValue( imath.Box2f( imath.V2f( -15, -23.6519547 ), imath.V2f( 47.9447174, 14.1233864 ) ) ) +__children["Dot6"]["in"].setInput( __children["Dot8"]["out"] ) +__children["Dot6"]["__uiPosition"].setValue( imath.V2f( 97.2128067, 98.9833603 ) ) +__children["Dot7"]["in"].setInput( __children["Parent"]["out"] ) +__children["Dot7"]["__uiPosition"].setValue( imath.V2f( 119.889359, 117.182449 ) ) +__children["Dot8"]["in"].setInput( __children["Dot7"]["out"] ) +__children["Dot8"]["__uiPosition"].setValue( imath.V2f( 119.889359, 105.48336 ) ) +__children["noise_texture"]["parameters"]["scale"].setValue( 4.0 ) +__children["noise_texture"]["__uiPosition"].setValue( imath.V2f( 67.4756851, 91.0513306 ) ) +__children["rgb_ramp"]["parameters"]["ramp"]["p0"]["x"].setValue( 0.3123359680175781 ) +__children["rgb_ramp"]["parameters"]["ramp"]["p0"]["y"].setValue( imath.Color3f( 1, 0, 0 ) ) +__children["rgb_ramp"]["parameters"]["ramp"]["p1"]["x"].setValue( 0.6010498404502869 ) +__children["rgb_ramp"]["parameters"]["ramp"]["p1"]["y"].setValue( imath.Color3f( 0, 0.685185194, 1 ) ) +__children["rgb_ramp"]["parameters"]["ramp"]["p2"]["x"].setValue( 0.5039370059967041 ) +__children["rgb_ramp"]["parameters"]["ramp"]["p2"]["y"].setValue( imath.Color3f( 0.0679012537, 1, 0.0824652985 ) ) +__children["rgb_ramp"]["parameters"]["fac"].setInput( __children["noise_texture"]["out"]["fac"] ) +__children["rgb_ramp"]["__uiPosition"].setValue( imath.V2f( 83.2816544, 91.651329 ) ) +__children["ShaderTweaks3"]["in"].setInput( __children["Dot9"]["out"] ) +__children["ShaderTweaks3"]["filter"].setInput( __children["PathFilter5"]["out"] ) +__children["ShaderTweaks3"]["shader"].setValue( 'cycles:surface' ) +__children["ShaderTweaks3"]["tweaks"]["base_color"]["name"].setValue( 'base_color' ) +Gaffer.Metadata.registerValue( __children["ShaderTweaks3"]["tweaks"]["base_color"], 'noduleLayout:visible', True ) +__children["ShaderTweaks3"]["tweaks"]["base_color"]["value"].setInput( __children["brightness_contrast"]["out"]["color"] ) +__children["ShaderTweaks3"]["__uiPosition"].setValue( imath.V2f( 184.99176, 91.927948 ) ) +__children["PathFilter5"]["paths"].setValue( IECore.StringVectorData( [ '/sphere' ] ) ) +__children["PathFilter5"]["__uiPosition"].setValue( imath.V2f( 198.267075, 98.6261139 ) ) +__children["Backdrop11"]["title"].setValue( 'Proxy Nodes Allow Inserting Shaders Into Existing Connections' ) +__children["Backdrop11"]["description"].setValue( 'Here we use an auto proxy to insert a contrast node on the base_color parameter.\n\nWe create the tweak, then click on the proxy button on the far right of the ShaderTweak\'s node parameters in the Node Editor ( this menu can also be accessed by right clicking any shader input plug in the Graph Editor ).\n\nHere, we just selected "Auto" from this menu, which allows forming a connection from whatever was originally connected to the shader parameter we are tweaking. We use this reduce the contrast of whatever is connected to the base_color parameter.' ) +__children["Backdrop11"]["__uiPosition"].setValue( imath.V2f( 162.932358, 106.214165 ) ) +__children["Backdrop11"]["__uiBound"].setValue( imath.Box2f( imath.V2f( -33.770874, -23.6519547 ), imath.V2f( 47.9447174, 14.1233864 ) ) ) +__children["Dot9"]["in"].setInput( __children["Dot11"]["out"] ) +__children["Dot9"]["__uiPosition"].setValue( imath.V2f( 184.99176, 99.2599792 ) ) +__children["Dot10"]["in"].setInput( __children["Parent"]["out"] ) +__children["Dot10"]["__uiPosition"].setValue( imath.V2f( 207.66832, 117.459076 ) ) +__children["Dot11"]["in"].setInput( __children["Dot10"]["out"] ) +__children["Dot11"]["__uiPosition"].setValue( imath.V2f( 207.66832, 105.759979 ) ) +__children["Auto"]["__uiPosition"].setValue( imath.V2f( 153.224182, 93.1279449 ) ) +__children["brightness_contrast"]["parameters"]["color"].setInput( __children["Auto"]["out"]["auto"] ) +__children["brightness_contrast"]["parameters"]["contrast"].setValue( -0.6899999976158142 ) +__children["brightness_contrast"]["__uiPosition"].setValue( imath.V2f( 168.096741, 91.927948 ) ) +__children["ShaderTweaks4"]["in"].setInput( __children["Dot12"]["out"] ) +__children["ShaderTweaks4"]["filter"].setInput( __children["PathFilter6"]["out"] ) +__children["ShaderTweaks4"]["shader"].setValue( 'cycles:surface' ) +__children["ShaderTweaks4"]["tweaks"]["base_color"]["name"].setValue( 'emission_color' ) +__children["ShaderTweaks4"]["tweaks"]["emission_strength"]["name"].setValue( 'emission_strength' ) +__children["ShaderTweaks4"]["tweaks"]["emission_strength"]["value"].setValue( 0.25 ) +Gaffer.Metadata.registerValue( __children["ShaderTweaks4"]["tweaks"]["base_color"], 'noduleLayout:visible', True ) +__children["ShaderTweaks4"]["tweaks"]["base_color"]["value"].setInput( __children["image_texture1"]["out"]["color"] ) +__children["ShaderTweaks4"]["__uiPosition"].setValue( imath.V2f( 269.956543, 91.877861 ) ) +__children["PathFilter6"]["paths"].setValue( IECore.StringVectorData( [ '/sphere' ] ) ) +__children["PathFilter6"]["__uiPosition"].setValue( imath.V2f( 283.231934, 98.5760269 ) ) +__children["Backdrop12"]["title"].setValue( 'Proxy Nodes Can Also Grab Specific Input Shaders' ) +__children["Backdrop12"]["description"].setValue( 'Here, instead of using an auto proxy, we select "From Affected" from the proxy menu, and choose the shader "image_texture".\n\nWe use this to find a node anywhere in the input network named "image_texture", and use it in our tweaks, in addition to whatever it was already connected to. Here we use this texture to drive emission_color as well.' ) +__children["Backdrop12"]["__uiPosition"].setValue( imath.V2f( 247.897064, 106.164078 ) ) +__children["Backdrop12"]["__uiBound"].setValue( imath.Box2f( imath.V2f( -33.770874, -23.6519547 ), imath.V2f( 47.9447174, 14.1233864 ) ) ) +__children["Dot12"]["in"].setInput( __children["Dot14"]["out"] ) +__children["Dot12"]["__uiPosition"].setValue( imath.V2f( 269.956543, 99.2098923 ) ) +__children["Dot13"]["in"].setInput( __children["Parent"]["out"] ) +__children["Dot13"]["__uiPosition"].setValue( imath.V2f( 292.633179, 117.408997 ) ) +__children["Dot14"]["in"].setInput( __children["Dot13"]["out"] ) +__children["Dot14"]["__uiPosition"].setValue( imath.V2f( 292.633179, 105.709892 ) ) +__children["image_texture1"]["parameters"]["targetShader"].setValue( 'image_texture' ) +__children["image_texture1"]["__uiPosition"].setValue( imath.V2f( 249.956543, 91.877861 ) ) + + +del __children + diff --git a/include/GafferScene/ShaderTweakProxy.h b/include/GafferScene/ShaderTweakProxy.h new file mode 100644 index 00000000000..f465321ec88 --- /dev/null +++ b/include/GafferScene/ShaderTweakProxy.h @@ -0,0 +1,96 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Image Engine Design Inc. 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. +// +////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "GafferScene/Shader.h" + +namespace GafferScene +{ + +class GAFFERSCENE_API ShaderTweakProxy : public Shader +{ + + public : + + ShaderTweakProxy( const std::string &name = defaultName() ); + + ~ShaderTweakProxy() override; + + GAFFER_NODE_DECLARE_TYPE( GafferScene::ShaderTweakProxy, ShaderTweakProxyTypeId, Shader ); + + // Use this to set up a proxy for a specific type of shader - for auto proxies, call setupAutoProxy + // instead. The shader name passed in should start with a type prefix followed by a colon, to + // indicate how we need to load a shader in order to find its outputs to create a proxy. For example + // "osl:Conversion/ColorToFloat" means we will look for an OSL shader named "Conversion/ColorToFloat", + // and set up a proxy with matching output plugs. keepExistingValues is ignored, because proxies have + // only outputs. + void loadShader( const std::string &shaderName, bool keepExistingValues=false ) override; + + // Auto-proxies connect to the original input of whatever parameter you are tweaking on a ShaderTweaks. + // They use dynamic plugs to store the type of their output - the reference plug provides the type + // of plug to create. + void setupAutoProxy( const Gaffer::Plug* referencePlug ); + + // Parse the current shader name for the type prefix and source shader name + void typePrefixAndSourceShaderName( std::string &typePrefix, std::string &sourceShaderName ) const; + + // Identify if a shader is a proxy, created by ShaderTweakProxy + static bool isProxy( const IECoreScene::Shader *shader ); + + template + struct ShaderLoaderDescription + { + ShaderLoaderDescription( const std::string &typePrefix ) + { + registerShaderLoader( typePrefix, []() -> GafferScene::ShaderPtr{ return new T(); } ); + } + }; + + private : + + using ShaderLoaderCreator = std::function< ShaderPtr() >; + using ShaderLoaderCreatorMap = std::map< std::string, ShaderTweakProxy::ShaderLoaderCreator >; + static ShaderLoaderCreatorMap &shaderLoaderCreators(); + + static void registerShaderLoader( const std::string &typePrefix, ShaderLoaderCreator creator ); + + static size_t g_firstPlugIndex; +}; + +IE_CORE_DECLAREPTR( ShaderTweakProxy ) + +} // namespace GafferScene diff --git a/include/GafferScene/TypeIds.h b/include/GafferScene/TypeIds.h index 9e67835d112..734b592e57e 100644 --- a/include/GafferScene/TypeIds.h +++ b/include/GafferScene/TypeIds.h @@ -182,6 +182,7 @@ enum TypeId DeletePassesTypeId = 110638, MeshTessellateTypeId = 110639, RenderPassShaderTypeId = 110640, + ShaderTweakProxyTypeId = 110641, PreviewPlaceholderTypeId = 110647, PreviewGeometryTypeId = 110648, diff --git a/python/GafferSceneTest/ShaderTweakProxyTest.py b/python/GafferSceneTest/ShaderTweakProxyTest.py new file mode 100644 index 00000000000..ff5622c413f --- /dev/null +++ b/python/GafferSceneTest/ShaderTweakProxyTest.py @@ -0,0 +1,207 @@ +########################################################################## +# +# Copyright (c) 2024, Image Engine Design Inc. 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 pathlib +import unittest +import imath + +import IECore +import IECoreScene + +import Gaffer +import GafferScene +import GafferSceneTest + +class ShaderTweakProxyTest( GafferSceneTest.SceneTestCase ) : + + def test( self ) : + + plane = GafferScene.Plane() + shader = GafferSceneTest.TestShader( "surface" ) + shader["type"].setValue( "surface" ) + + textureShader1 = GafferSceneTest.TestShader( "texture1" ) + + textureShader2 = GafferSceneTest.TestShader( "texture2" ) + + shader["parameters"]["c"].setInput( textureShader1["out"] ) + textureShader1["parameters"]["c"].setInput( textureShader2["out"] ) + + planeFilter = GafferScene.PathFilter() + planeFilter["paths"].setValue( IECore.StringVectorData( [ "/plane" ] ) ) + + assignment = GafferScene.ShaderAssignment() + assignment["in"].setInput( plane["out"] ) + assignment["filter"].setInput( planeFilter["out"] ) + assignment["shader"].setInput( shader["out"] ) + + # Check the untweaked network + originalNetwork = assignment["out"].attributes( "/plane" )["surface"] + self.assertEqual( len( originalNetwork ), 3 ) + self.assertEqual( originalNetwork.input( ( "surface", "c" ) ), ( "texture1", "out" ) ) + + tweakShader = GafferSceneTest.TestShader( "tweakShader" ) + + tweaks = GafferScene.ShaderTweaks() + tweaks["in"].setInput( assignment["out"] ) + tweaks["filter"].setInput( planeFilter["out"] ) + tweaks["shader"].setValue( "surface" ) + + tweaks["tweaks"].addChild( Gaffer.TweakPlug( "c", Gaffer.Color3fPlug() ) ) + tweaks["tweaks"][0]["value"].setInput( tweakShader["out"] ) + + # If we replace the upstream network with a tweak, now we have just 2 nodes + tweakedNetwork = tweaks["out"].attributes( "/plane" )["surface"] + self.assertEqual( len( tweakedNetwork ), 2 ) + self.assertEqual( tweakedNetwork.input( ( "surface", "c" ) ), ( "tweakShader", "out" ) ) + + autoProxy = GafferScene.ShaderTweakProxy() + autoProxy.setupAutoProxy( Gaffer.Color3fPlug() ) + + # Using an auto proxy with no tweak shaders inserted recreates the original network + tweaks["tweaks"][0]["value"].setInput( autoProxy["out"]["auto"] ) + self.assertEqual( tweaks["out"].attributes( "/plane" )["surface"], originalNetwork ) + + # Test adding a tweak shader in the middle of the network using the proxy + tweakShader["parameters"]["c"].setInput( autoProxy["out"]["auto"] ) + tweaks["tweaks"][0]["value"].setInput( tweakShader["out"] ) + tweakedNetwork = tweaks["out"].attributes( "/plane" )["surface"] + self.assertEqual( len( tweakedNetwork ), 4 ) + self.assertEqual( tweakedNetwork.input( ( "surface", "c" ) ), ( "tweakShader", "out" ) ) + self.assertEqual( tweakedNetwork.input( ( "tweakShader", "c" ) ), ( "texture1", "out" ) ) + + # If we target the end of the network where there is no input, then the tweak gets inserted fine, + # and there is no input to the tweak, since there's nothing upstream + tweaks["tweaks"][0]["name"].setValue( "texture2.c" ) + tweakedNetwork = tweaks["out"].attributes( "/plane" )["surface"] + self.assertEqual( len( tweakedNetwork ), 4 ) + self.assertEqual( tweakedNetwork.input( ( "surface", "c" ) ), ( "texture1", "out" ) ) + self.assertEqual( tweakedNetwork.input( ( "texture1", "c" ) ), ( "texture2", "out" ) ) + self.assertEqual( tweakedNetwork.input( ( "texture2", "c" ) ), ( "tweakShader", "out" ) ) + self.assertEqual( tweakedNetwork.input( ( "tweakShader", "c" ) ), ( "", "" ) ) + + # Test using an auto-proxy on a parameter with no input ( it should apply the value to what the + # auto-proxy is connected to ) + textureShader2["parameters"]["c"].setValue( imath.Color3f( 5, 6, 7 ) ) + tweaks["tweaks"][0]["name"].setValue( "texture2.c" ) + tweakedNetwork = tweaks["out"].attributes( "/plane" )["surface"] + self.assertEqual( tweakedNetwork.getShader( "tweakShader" ).parameters["c"].value, imath.Color3f( 5, 6, 7 ) ) + + # Test proxying a specific node using a named handle + tweaks["tweaks"][0]["name"].setValue( "c" ) + + specificProxy = GafferScene.ShaderTweakProxy() + specificProxy.loadShader( "test:testShader" ) + + specificProxy["parameters"]["targetShader"].setValue( "texture2" ) + + tweakShader["parameters"]["c"].setInput( specificProxy["out"]["out"] ) + + tweakedNetwork = tweaks["out"].attributes( "/plane" )["surface"] + self.assertEqual( len( tweakedNetwork ), 3 ) + self.assertEqual( tweakedNetwork.input( ( "surface", "c" ) ), ( "tweakShader", "out" ) ) + self.assertEqual( tweakedNetwork.input( ( "tweakShader", "c" ) ), ( "texture2", "out" ) ) + + # Test error if we try to make a cycle + tweaks["tweaks"][0]["name"].setValue( "texture2.c" ) + specificProxy["parameters"]["targetShader"].setValue( "texture1" ) + + with self.assertRaisesRegex( Gaffer.ProcessException, 'Cannot use "texture1" in ShaderTweakProxy when tweaking "texture2", this would create cycle in shader network' ): + tweaks["out"].attributes( "/plane" ) + + def testAutoProxyValueTransferToComponent( self ) : + + plane = GafferScene.Plane() + shader = GafferSceneTest.TestShader( "surface" ) + shader["type"].setValue( "surface" ) + shader["parameters"]["i"].setValue( 42 ) + + planeFilter = GafferScene.PathFilter() + planeFilter["paths"].setValue( IECore.StringVectorData( [ "/plane" ] ) ) + + assignment = GafferScene.ShaderAssignment() + assignment["in"].setInput( plane["out"] ) + assignment["filter"].setInput( planeFilter["out"] ) + assignment["shader"].setInput( shader["out"] ) + + tweakShader = GafferSceneTest.TestShader( "tweakShader" ) + + tweaks = GafferScene.ShaderTweaks() + tweaks["in"].setInput( assignment["out"] ) + tweaks["filter"].setInput( planeFilter["out"] ) + tweaks["shader"].setValue( "surface" ) + + tweaks["tweaks"].addChild( Gaffer.TweakPlug( "i", Gaffer.IntPlug() ) ) + tweaks["tweaks"][0]["value"].setInput( tweakShader["out"]["r"] ) + + autoProxy = GafferScene.ShaderTweakProxy() + autoProxy.setupAutoProxy( Gaffer.IntPlug() ) + + tweakShader["parameters"]["c"]["g"].setInput( autoProxy["out"]["auto"] ) + + # This is quite a special case - there is no input to the parameter we are tweaking, so there is no + # connection to transfer, so we would expect the auto proxy to transfer the value - however the auto + # proxy output is connected to a subcomponent. + # + # The correct result is that the green component of tweakShader.c should be set to 42, transferring the value + # that was set. However, we have not yet added support for this fairly obscure case, so instead this test + # documents the current behaviour, which is to throw a semi-helpful exception. + + with self.assertRaisesRegex( Gaffer.ProcessException, 'CompoundData has no child named "c.g"' ): + tweaks["out"].attributes( "/plane" )["surface"] + + def testInvalidInShaderAssignment( self ) : + + plane = GafferScene.Plane() + + autoProxy = GafferScene.ShaderTweakProxy() + autoProxy.setupAutoProxy( Gaffer.Color3fPlug() ) + + planeFilter = GafferScene.PathFilter() + planeFilter["paths"].setValue( IECore.StringVectorData( [ "/plane" ] ) ) + + assignment = GafferScene.ShaderAssignment() + assignment["in"].setInput( plane["out"] ) + assignment["filter"].setInput( planeFilter["out"] ) + assignment["shader"].setInput( autoProxy["out"]["auto"] ) + + # Using a proxy in a shader assignment is invalid + with self.assertRaisesRegex( Gaffer.ProcessException, "ShaderTweakProxy only works with ShaderTweaks" ): + assignment["out"].attributes( "/plane" ) + + +if __name__ == "__main__": + unittest.main() diff --git a/python/GafferSceneTest/ShaderTweaksTest.py b/python/GafferSceneTest/ShaderTweaksTest.py index 3e26306dd5c..3d519acaeb1 100644 --- a/python/GafferSceneTest/ShaderTweaksTest.py +++ b/python/GafferSceneTest/ShaderTweaksTest.py @@ -361,12 +361,28 @@ def testIgnoreMissing( self ) : with self.assertRaisesRegex( RuntimeError, "Cannot apply tweak with mode Replace to \"badParameter\" : This parameter does not exist" ) : t["out"].attributes( "/light" ) + inputShader = GafferSceneTest.TestShader() + badTweak["value"].setInput( inputShader["out"]["r"] ) + + with self.assertRaisesRegex( RuntimeError, "Cannot apply tweak \"badParameter\" because shader \"__shader\" does not have parameter \"badParameter\"" ) : + t["out"].attributes( "/light" ) + + badTweak["value"].setInput( None ) + t["ignoreMissing"].setValue( True ) self.assertEqual( t["out"].attributes( "/light" ), t["in"].attributes( "/light" ) ) badTweak["name"].setValue( "badShader.p" ) self.assertEqual( t["out"].attributes( "/light" ), t["in"].attributes( "/light" ) ) + badTweak["value"].setInput( inputShader["out"]["r"] ) + badTweak["name"].setValue( "badParameter" ) + self.assertEqual( t["out"].attributes( "/light" ), t["in"].attributes( "/light" ) ) + self.assertEqual( t["out"].attributes( "/light" ), t["in"].attributes( "/light" ) ) + + badTweak["name"].setValue( "badShader.p" ) + self.assertEqual( t["out"].attributes( "/light" ), t["in"].attributes( "/light" ) ) + t["ignoreMissing"].setValue( False ) with self.assertRaisesRegex( Gaffer.ProcessException, "Cannot apply tweak \"badShader.p\" because shader \"badShader\" does not exist" ) : t["out"].attributes( "/light" ) diff --git a/python/GafferSceneTest/__init__.py b/python/GafferSceneTest/__init__.py index 2f85bf71db3..da9ecb37aa6 100644 --- a/python/GafferSceneTest/__init__.py +++ b/python/GafferSceneTest/__init__.py @@ -112,6 +112,7 @@ from .FilteredSceneProcessorTest import FilteredSceneProcessorTest from .ShaderBallTest import ShaderBallTest from .ShaderTweaksTest import ShaderTweaksTest +from .ShaderTweakProxyTest import ShaderTweakProxyTest from .FilterResultsTest import FilterResultsTest from .RendererAlgoTest import RendererAlgoTest from .SetAlgoTest import SetAlgoTest diff --git a/python/GafferSceneUI/ShaderTweakProxyUI.py b/python/GafferSceneUI/ShaderTweakProxyUI.py new file mode 100644 index 00000000000..502d083d2a1 --- /dev/null +++ b/python/GafferSceneUI/ShaderTweakProxyUI.py @@ -0,0 +1,305 @@ +########################################################################## +# +# Copyright (c) 2024, Image Engine Design Inc. 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 Gaffer +import GafferUI +import GafferScene +import GafferSceneUI + +import IECore +import IECoreScene + +import functools +import imath + +Gaffer.Metadata.registerNode( + + GafferScene.ShaderTweakProxy, + + "description", + """ + Represents a shader in the shader network that a ShaderTweaks node is modifying. Allows forming + connections from existing shaders to shaders that are being inserted. + """, + + "icon", "shaderTweakProxy.png", + + plugs = { + + "name" : [ + + "description", "Hardcoded for ShaderTweakProxy nodes.", + "plugValueWidget:type", "", + + ], + + "type" : [ + + "description", "Hardcoded for ShaderTweakProxy nodes.", + "plugValueWidget:type", "", + + ], + + "parameters" : [ + + "plugValueWidget:type", "GafferUI.LayoutPlugValueWidget", + + ], + + "parameters.targetShader" : [ + + "description", + """ + The handle of the upstream shader being fetched by this proxy - or Auto, indicating that + the original input of the parameter being ShaderTweaked will be used. + """, + "readOnly", True, + "nodule:type", "", + "stringPlugValueWidget:placeholderText", "Auto", + + ], + + "out" : [ + + "plugValueWidget:type", "", + "nodule:type", "GafferUI::CompoundNodule" + + ], + + "out.*" : [ + + "description", + """ + The name of the output on the shader we are fetching, or "auto" for an auto proxy. + """, + + ], + + } + +) + +def __findConnectedShaderTweaks( startShader ): + shadersScanned = set() + shadersToScan = [ startShader ] + shaderTweaks = set() + + while len( shadersToScan ): + shader = shadersToScan.pop() + shadersScanned.add( shader ) + if isinstance( shader, GafferScene.ShaderTweaks ): + shaderTweaks.add( shader ) + continue + elif not isinstance( shader, GafferScene.Shader ): + continue + elif not "out" in shader: + continue + + possibleOutputs = [ shader["out"] ] + + outputs = [] + + while len( possibleOutputs ): + po = possibleOutputs.pop() + if po.outputs(): + outputs.append( po ) + else: + for c in po.children(): + possibleOutputs.append( c ) + + while len( outputs ): + o = outputs.pop() + if o.outputs(): + outputs += o.outputs() + else: + dest = Gaffer.PlugAlgo.findDestination( o, lambda plug : plug if not plug.outputs() else None ).node() + if not dest in shadersScanned: + shadersToScan.append( dest ) + + return shaderTweaks + +def __createShaderTweakProxy( plug, sourceHandle, sourceType, sourceName ): + + with Gaffer.UndoScope( plug.ancestor( Gaffer.ScriptNode ) ): + result = GafferScene.ShaderTweakProxy( sourceHandle or "Auto" ) + if sourceHandle: + sourceTypePrefix = sourceType.split( ":" )[0] + result.loadShader( sourceTypePrefix + ":" + sourceName ) + else: + result.setupAutoProxy( plug ) + result["parameters"]["targetShader"].setValue( sourceHandle ) + + plug.node().parent().addChild( result ) + plug.node().scriptNode().selection().clear() + plug.node().scriptNode().selection().add( result ) + + # See if there are any output plugs on the new proxy which can be connected to this plug + for p in result["out"].children(): + try: + plug.setInput( p ) + except: + continue + break + + # Make sure the target plug on the destination ShaderTweaks is visible if we're connecting to it + # ( might not be the case if we're doing this using the menu buttons on ShaderTweaks ) + if type( plug.node() ) == GafferScene.ShaderTweaks and plug.parent().parent() == plug.node()["tweaks"]: + Gaffer.Metadata.registerValue( plug.parent(), "noduleLayout:visible", True ) + + # \todo - It's probably bad that I'm doing this manually, instead of using GraphGadget.setNodePosition + # ... but it also feels wrong that that is a non-static member of GraphGadget ... it doesn't use + # any members of GraphGadget, and when creating a new ShaderTweakProxy from the Node Editor, this totally + # makes sense to do, even if there are no current GraphGadgets + if "__uiPosition" in plug.node(): + result.addChild( Gaffer.V2fPlug( "__uiPosition", Gaffer.Plug.Direction.In, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) ) + result["__uiPosition"].setValue( plug.node()["__uiPosition"].getValue() + imath.V2f( -20, 0 ) ) + +def _shaderAttributes( context, nodes, paths, affectedOnly ) : + + result = {} + + with context : + for node in nodes: + useFullAttr = node["localise"].getValue() + attributeNamePatterns = node["shader"].getValue() if affectedOnly else "*" + for path in paths : + if not node["in"].exists( path ): + continue + + attributes = node["in"].fullAttributes( path ) if useFullAttr else node["in"].attributes( path ) + for name, attribute in attributes.items() : + if not IECore.StringAlgo.matchMultiple( name, attributeNamePatterns ) : + continue + if not isinstance( attribute, IECoreScene.ShaderNetwork ) or not len( attribute ) : + continue + result.setdefault( path, {} )[name] = attribute + + return result + +def __browseShaders( scriptWindow, plug, context, nodes, paths ) : + + shaderAttributes = _shaderAttributes( context, nodes, paths, affectedOnly = True ) + + uniqueNetworks = { n.hash(): n for a in shaderAttributes.values() for n in a.values() } + + browser = GafferSceneUI.ShaderUI._ShaderDialogue( uniqueNetworks.values(), "Select Source Shader" ) + + shaderHandle = browser.waitForShader( parentWindow = scriptWindow ) + + if shaderHandle is not None : + for n in uniqueNetworks.values(): + if shaderHandle in n.shaders().keys(): + shader = n.shaders()[shaderHandle] + __createShaderTweakProxy( plug, shaderHandle, shader.type, shader.name ) + break + +def _pathsFromAffected( context, nodes ) : + + pathMatcher = IECore.PathMatcher() + with context: + for node in nodes: + GafferScene.SceneAlgo.matchingPaths( node["filter"], node["in"], pathMatcher ) + + return pathMatcher.paths() + +def _pathsFromSelection( context ) : + + paths = GafferSceneUI.ContextAlgo.getSelectedPaths( context ) + paths = paths.paths() if paths else [] + + return paths + + +def __browseAffectedShaders( plug, shaderTweaksOverride, menu ) : + + context = plug.ancestor( Gaffer.ScriptNode ).context() + shaderTweaks = [ shaderTweaksOverride ] if shaderTweaksOverride else __findConnectedShaderTweaks( plug.node() ) + + __browseShaders( + menu.ancestor( GafferUI.Window ), plug, context, shaderTweaks, _pathsFromAffected( context, shaderTweaks ) + ) + +def __browseSelectedShaders( plug, shaderTweaksOverride, menu ) : + + context = plug.ancestor( Gaffer.ScriptNode ).context() + shaderTweaks = [ shaderTweaksOverride ] if shaderTweaksOverride else __findConnectedShaderTweaks( plug.node() ) + __browseShaders( + menu.ancestor( GafferUI.Window ), plug, context, shaderTweaks, _pathsFromSelection( context ) + ) + +def _plugContextMenu( plug, shaderTweaks ) : + + menuDefinition = IECore.MenuDefinition() + + # Find the actual node if we're looking at something like a box input + # NOTE : This could fail if a shader output is connected to 2 things, and the first thing is not a shader, + # but that seems like a pretty weird case, and we want to get to the early out without doing too much traversal + destPlug = Gaffer.PlugAlgo.findDestination( plug, lambda plug : plug if not plug.outputs() else None ) + + if not ( isinstance( destPlug.node(), GafferScene.Shader ) or isinstance( destPlug.node(), GafferScene.ShaderTweaks ) ): + return + + menuDefinition.append( + "Auto ( Original Input )", + { + "command" : functools.partial( __createShaderTweakProxy, plug, "", "", "" ), + "active" : not Gaffer.MetadataAlgo.readOnly( plug.node().parent() ), + } + ) + + menuDefinition.append( + "From Affected", + { + "command" : functools.partial( __browseAffectedShaders, plug, shaderTweaks ), + "active" : not Gaffer.MetadataAlgo.readOnly( plug.node().parent() ), + } + ) + menuDefinition.append( + "From Selected", + { + "command" : functools.partial( __browseSelectedShaders, plug, shaderTweaks ), + "active" : not Gaffer.MetadataAlgo.readOnly( plug.node().parent() ), + } + ) + + return menuDefinition + +def __plugContextMenuSignal( graphEditor, plug, menuDefinition ) : + menuDefinition.append( "/Create ShaderTweakProxy", + { "subMenu" : functools.partial( _plugContextMenu, plug, None ) } + ) + +GafferUI.GraphEditor.plugContextMenuSignal().connect( __plugContextMenuSignal, scoped = False ) diff --git a/python/GafferSceneUI/ShaderTweaksUI.py b/python/GafferSceneUI/ShaderTweaksUI.py index 95e7f850698..78e54f39781 100644 --- a/python/GafferSceneUI/ShaderTweaksUI.py +++ b/python/GafferSceneUI/ShaderTweaksUI.py @@ -129,6 +129,7 @@ "tweakPlugValueWidget:allowCreate", True, "tweakPlugValueWidget:allowRemove", True, "tweakPlugValueWidget:propertyType", "parameter", + "plugValueWidget:type", "GafferSceneUI.ShaderTweaksUI._ShaderTweakPlugValueWidget", ], @@ -327,6 +328,26 @@ def __addTweak( self, name, plugTypeOrValue ) : with Gaffer.UndoScope( self.getPlug().ancestor( Gaffer.ScriptNode ) ) : self.getPlug().addChild( plug ) +class _ShaderTweakPlugValueWidget( GafferUI.TweakPlugValueWidget ) : + + def __init__( self, plugs ): + GafferUI.TweakPlugValueWidget.__init__( self, plugs ) + self._TweakPlugValueWidget__row.append( + GafferUI.MenuButton( + image="shaderTweakProxyIcon.png", + hasFrame=False, + menu=GafferUI.Menu( Gaffer.WeakMethod( self.__createProxyMenuDefinition ), title = "Create Proxy" ), + toolTip = "Proxies allow making connections from the outputs of nodes in the input network." + ) + ) + + def __createProxyMenuDefinition( self ) : + return GafferSceneUI.ShaderTweakProxyUI._plugContextMenu( self.getPlug()["value"], self.getPlug().node() ) + + def __updateReadOnly( self ) : + + self.setEnabled( not Gaffer.MetadataAlgo.readOnly( self.__plugParent.node().parent() ) ) + ########################################################################## # PlugValueWidget context menu ########################################################################## diff --git a/python/GafferSceneUI/ShaderUI.py b/python/GafferSceneUI/ShaderUI.py index cba527edad3..ed7f1d6cb79 100644 --- a/python/GafferSceneUI/ShaderUI.py +++ b/python/GafferSceneUI/ShaderUI.py @@ -445,10 +445,12 @@ def headerData( self, canceller = None ) : class _ShaderPath( Gaffer.Path ) : - def __init__( self, shaderNetworks, path, root = "/", filter = None ) : + def __init__( self, shaderNetworks, path, root = "/", filter = None, connectedParametersOnly = False ) : Gaffer.Path.__init__( self, path, root, filter ) + self.__connectedParametersOnly = connectedParametersOnly + assert( all( [ isinstance( n, IECoreScene.ShaderNetwork ) for n in shaderNetworks ] ) ) self.__shaderNetworks = shaderNetworks @@ -458,7 +460,7 @@ def isValid( self ) : if len( self ) > 0 and self.__shaders() is None : return False - if len( self ) > 1 and self[1] not in self.__parameters() : + if len( self ) > 1 and self[1] not in self.__parameters(): return False if len( self ) > 2 : @@ -524,7 +526,7 @@ def property( self, name, canceller = None ) : def copy( self ) : - return _ShaderPath( self.__shaderNetworks, self[:], self.root(), self.getFilter() ) + return _ShaderPath( self.__shaderNetworks, self[:], self.root(), self.getFilter(), self.__connectedParametersOnly ) def _children( self, canceller ) : @@ -560,7 +562,8 @@ def _children( self, canceller ) : self.__shaderNetworks, self[:] + [shaderHandle], self.root(), - self.getFilter() + self.getFilter(), + self.__connectedParametersOnly ) ) @@ -577,7 +580,8 @@ def _children( self, canceller ) : self.__shaderNetworks, self[:] + [p], self.root(), - self.getFilter() + self.getFilter(), + self.__connectedParametersOnly ) ) @@ -591,6 +595,14 @@ def __parameters( self ) : if len( self ) == 0 : return [] + if self.__connectedParametersOnly : + connectedParams = set() + for n in self.__shaderNetworks : + if self[0] in n.shaders() : + for c in n.inputConnections( self[0] ) : + connectedParams.add( c.destination.name ) + return list( connectedParams ) + return [ p for s in self.__shaders() for p in s.parameters.keys() ] # Returns a list of all shaders with a name matching the path's shader name. @@ -717,18 +729,18 @@ def __rootPathChanged( self, path ) : GafferUI.PathFilterWidget.registerType( _PathMatcherPathFilter, GafferUI.MatchPatternPathFilterWidget ) -class _ShaderParameterDialogue( GafferUI.Dialogue ) : - - def __init__( self, shaderNetworks, title = None, **kw ) : +class _ShaderDialogueBase( GafferUI.Dialogue ) : - if title is None : - title = "Select Shader Parameters" + def __init__( self, shaderNetworks, title, selectParameters, **kw ) : GafferUI.Dialogue.__init__( self, title, **kw ) + self.__selectParameters = selectParameters + self.__cancelled = False + self.__shaderNetworks = shaderNetworks - self.__path = _ShaderPath( self.__shaderNetworks, path = "/" ) + self.__path = _ShaderPath( self.__shaderNetworks, path = "/", connectedParametersOnly = not self.__selectParameters ) self.__filter = _PathMatcherPathFilter( [ "" ], self.__path.copy() ) self.__filter.setEnabled( False ) @@ -749,7 +761,7 @@ def __init__( self, shaderNetworks, title = None, **kw ) : GafferUI.PathListingWidget.StandardColumn( "Value", "shader:value" ), _ShaderInputColumn( "Input" ), ), - allowMultipleSelection = True, + allowMultipleSelection = self.__selectParameters, displayMode = GafferUI.PathListingWidget.DisplayMode.Tree, sortable = False, horizontalScrollMode = GafferUI.ScrollMode.Automatic @@ -769,20 +781,15 @@ def __init__( self, shaderNetworks, title = None, **kw ) : self.__confirmButton = self._addButton( "OK" ) self.__confirmButton.clickedSignal().connect( Gaffer.WeakMethod( self.__buttonClicked ), scoped = False ) - self.__parametersSelectedSignal = Gaffer.Signal1() + self.__selectedSignal = Gaffer.Signal1() self.__updateButtonState() - def parametersSelectedSignal( self ) : - - return self.__parametersSelectedSignal + def _resultSelectedSignal( self ) : - # Causes the dialogue to enter a modal state, returning the selected shader parameters - # once they have been selected by the user. Returns None if the dialogue is cancelled. - # Parameters are returned as a list of tuples of the form - # [ ( "shaderName", "parameterName" ), ... ] - def waitForParameters( self, **kw ) : + return self.__selectedSignal + def _waitForResult( self, **kw ) : if len( self.__path.children() ) == 0 : dialogue = GafferUI.ConfirmationDialogue( "Shader Browser", @@ -792,7 +799,6 @@ def waitForParameters( self, **kw ) : else : button = self.waitForButton( **kw ) - if button is self.__confirmButton : return self.__result() @@ -801,7 +807,7 @@ def waitForParameters( self, **kw ) : def __buttonClicked( self, button ) : if button is self.__confirmButton : - self.parametersSelectedSignal()( self.__result() ) + self._resultSelectedSignal()( self.__result() ) def __pathSelected( self, pathListing ) : @@ -840,14 +846,57 @@ def __buttonRelease( self, pathListing, event ) : def __result( self ) : resultPaths = self.__pathListingWidget.getSelection() + + if not self.__selectParameters: + if not resultPaths.paths() or resultPaths.paths()[0].rfind( "/" ) > 0: + return None + + return resultPaths.paths()[0].strip( "/" ) + resultPaths = [ self.__path.copy().setFromString( x ) for x in resultPaths.paths() ] for p in resultPaths : if len( p ) < 2 : return [] - return [ ( p[0], p[1] ) for p in resultPaths ] def __updateButtonState( self, *unused ) : + self.__confirmButton.setEnabled( bool( self.__result() ) ) + +class _ShaderParameterDialogue( _ShaderDialogueBase ) : + + def __init__( self, shaderNetworks, title = None, **kw ) : + + if title is None : + title = "Select Shader Parameters" + + _ShaderDialogueBase.__init__( self, shaderNetworks, title, True, **kw ) + + # Causes the dialogue to enter a modal state, returning the selected shader parameters + # once they have been selected by the user. Returns None if the dialogue is cancelled. + # Parameters are returned as a list of tuples of the form + # [ ( "shaderName", "parameterName" ), ... ] + def waitForParameters( self, **kw ) : + return self._waitForResult( **kw ) + + def parametersSelectedSignal( self ) : + return self._resultSelectedSignal() + + + +class _ShaderDialogue( _ShaderDialogueBase ) : + + def __init__( self, shaderNetworks, title = None, **kw ) : + + if title is None : + title = "Select Shader" + + _ShaderDialogueBase.__init__( self, shaderNetworks, title, False, **kw ) + + # Causes the dialogue to enter a modal state, returning the handle of the selected shader once it + # has been selected by the user. Returns None if the dialogue is cancelled. + def waitForShader( self, **kw ) : + return self._waitForResult( **kw ) - self.__confirmButton.setEnabled( len( self.__result() ) > 0 ) + def shaderSelectedSignal( self ) : + return self._resultSelectedSignal() diff --git a/python/GafferSceneUI/__init__.py b/python/GafferSceneUI/__init__.py index e7f6379ebb9..4491b37e1f4 100644 --- a/python/GafferSceneUI/__init__.py +++ b/python/GafferSceneUI/__init__.py @@ -129,6 +129,7 @@ from . import MeshToPointsUI from . import ShaderBallUI from . import ShaderTweaksUI +from . import ShaderTweakProxyUI from . import CameraTweaksUI from . import LightToCameraUI from . import FilterResultsUI diff --git a/resources/graphics.py b/resources/graphics.py index e4c0ac8d842..b5b720018f2 100644 --- a/resources/graphics.py +++ b/resources/graphics.py @@ -556,6 +556,8 @@ 'focusOn', 'focusOff', 'focusOnHover', - 'focusOffHover' + 'focusOffHover', + 'shaderTweakProxy', + 'shaderTweakProxyIcon' ] } diff --git a/resources/graphics.svg b/resources/graphics.svg index 096006ce339..a79f4fd1be5 100644 --- a/resources/graphics.svg +++ b/resources/graphics.svg @@ -6,7 +6,7 @@ height="1000" id="svg2" version="1.1" - inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)" sodipodi:docname="graphics.svg" enable-background="new" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" @@ -275,20 +275,6 @@ offset="0" id="stop2644-7" /> - - - + inkscape:deskcolor="#4c4c4c"> + + + + + + + + + + diff --git a/src/GafferArnold/ArnoldShader.cpp b/src/GafferArnold/ArnoldShader.cpp index 4fd3d4ed973..0491da38be2 100644 --- a/src/GafferArnold/ArnoldShader.cpp +++ b/src/GafferArnold/ArnoldShader.cpp @@ -41,6 +41,8 @@ #include "GafferOSL/OSLShader.h" +#include "GafferScene/ShaderTweakProxy.h" + #include "Gaffer/CompoundNumericPlug.h" #include "Gaffer/NumericPlug.h" #include "Gaffer/StringPlug.h" @@ -75,6 +77,9 @@ namespace const bool g_oslRegistration = OSLShader::registerCompatibleShader( "ai:surface" ); const InternedString g_inputParameterName( "input" ); +ShaderTweakProxy::ShaderLoaderDescription g_arnoldShaderTweakProxyLoaderRegistration( "ai" ); + + } // namespace GAFFER_NODE_DEFINE_TYPE( ArnoldShader ); diff --git a/src/GafferCycles/CyclesShader.cpp b/src/GafferCycles/CyclesShader.cpp index f0f56607ee4..f6c640586cb 100644 --- a/src/GafferCycles/CyclesShader.cpp +++ b/src/GafferCycles/CyclesShader.cpp @@ -39,6 +39,8 @@ #include "GafferCycles/SocketHandler.h" +#include "GafferScene/ShaderTweakProxy.h" + #include "Gaffer/CompoundNumericPlug.h" #include "Gaffer/Metadata.h" #include "Gaffer/NumericPlug.h" @@ -73,6 +75,9 @@ bool g_oslRegistrationSurface = OSLShader::registerCompatibleShader( "cycles:sur bool g_oslRegistrationVolume = OSLShader::registerCompatibleShader( "cycles:volume" ); bool g_oslRegistrationDisplacement = OSLShader::registerCompatibleShader( "cycles:displacement" ); +ShaderTweakProxy::ShaderLoaderDescription g_cyclesShaderTweakProxyLoaderRegistration( "cycles" ); + + } // namespace IE_CORE_DEFINERUNTIMETYPED( CyclesShader ); diff --git a/src/GafferOSL/OSLShader.cpp b/src/GafferOSL/OSLShader.cpp index b6877c9193b..77c30607478 100644 --- a/src/GafferOSL/OSLShader.cpp +++ b/src/GafferOSL/OSLShader.cpp @@ -39,6 +39,8 @@ #include "GafferOSL/ClosurePlug.h" #include "GafferOSL/ShadingEngine.h" +#include "GafferScene/ShaderTweakProxy.h" + #include "Gaffer/CompoundNumericPlug.h" #include "Gaffer/Metadata.h" #include "Gaffer/NumericPlug.h" @@ -163,6 +165,13 @@ ShaderTypeSet &compatibleShaders() return g_compatibleShaders; } +// Auto-proxies are always allowed to connect, because we don't know what type they will be until we evaluate +// the graph. +const bool g_oslShaderTweakAutoProxyRegistration = OSLShader::registerCompatibleShader( "autoProxy" ); + +ShaderTweakProxy::ShaderLoaderDescription g_oslShaderTweakProxyLoaderRegistration( "osl" ); + + } // namespace ///////////////////////////////////////////////////////////////////////// @@ -266,7 +275,16 @@ bool OSLShader::acceptsInput( const Plug *plug, const Plug *inputPlug ) const return true; } - const IECore::InternedString sourceShaderType = sourceShader->typePlug()->getValue(); + std::string sourceShaderType; + if( const ShaderTweakProxy *shaderTweakProxy = IECore::runTimeCast< const ShaderTweakProxy >( sourceShader ) ) + { + std::string unusedSourceShaderName; + shaderTweakProxy->typePrefixAndSourceShaderName( sourceShaderType, unusedSourceShaderName ); + } + else + { + sourceShaderType = sourceShader->typePlug()->getValue(); + } const ShaderTypeSet &cs = compatibleShaders(); if( cs.find( sourceShaderType ) != cs.end() ) { diff --git a/src/GafferScene/Shader.cpp b/src/GafferScene/Shader.cpp index 93666fcd760..2bfba0f01a2 100644 --- a/src/GafferScene/Shader.cpp +++ b/src/GafferScene/Shader.cpp @@ -37,6 +37,8 @@ #include "GafferScene/Shader.h" +#include "GafferScene/ShaderTweakProxy.h" + #include "Gaffer/PlugAlgo.h" #include "Gaffer/Metadata.h" #include "Gaffer/NumericPlug.h" @@ -178,7 +180,7 @@ class Shader::NetworkBuilder public : NetworkBuilder( const Gaffer::Plug *output ) - : m_output( output ) + : m_output( output ), m_hasProxyNodes( false ) { } @@ -199,6 +201,8 @@ class Shader::NetworkBuilder IECoreScene::ConstShaderNetworkPtr network() { + static IECore::InternedString hasProxyNodesIdentifier( "__hasProxyNodes" ); + if( !m_network ) { m_network = new IECoreScene::ShaderNetwork; @@ -210,6 +214,12 @@ class Shader::NetworkBuilder } } } + + if( m_hasProxyNodes ) + { + m_network->blindData()->writable()[hasProxyNodesIdentifier] = new IECore::BoolData( true ); + } + return m_network; } @@ -365,25 +375,29 @@ class Shader::NetworkBuilder return handleAndHash.handle; } - std::string type = shaderNode->typePlug()->getValue(); - if( shaderNode != m_output->node() && !boost::ends_with( type, "shader" ) ) + IECoreScene::ShaderPtr shader = new IECoreScene::Shader( + shaderNode->namePlug()->getValue(), shaderNode->typePlug()->getValue() + ); + if( + !ShaderTweakProxy::isProxy( shader.get() ) && + shaderNode != m_output->node() && !boost::ends_with( shader->getType(), "shader" ) + ) { // Some renderers (Arnold for one) allow surface shaders to be connected // as inputs to other shaders, so we may need to change the shader type to // convert it into a standard shader. We must take care to preserve any // renderer specific prefix when doing this. - size_t i = type.find_first_of( ":" ); + size_t i = shader->getType().find_first_of( ":" ); if( i != std::string::npos ) { - type = type.substr( 0, i + 1 ) + "shader"; + shader->setType( shader->getType().substr( 0, i + 1 ) + "shader" ); } else { - type = "shader"; + shader->setType( "shader" ); } } - - IECoreScene::ShaderPtr shader = new IECoreScene::Shader( shaderNode->namePlug()->getValue(), type ); + m_hasProxyNodes |= ShaderTweakProxy::isProxy( shader.get() ); const std::string nodeName = shaderNode->nodeNamePlug()->getValue(); shader->blindData()->writable()["label"] = new IECore::StringData( nodeName ); @@ -759,6 +773,8 @@ class Shader::NetworkBuilder ShaderSet m_downstreamShaders; // Used for detecting cycles + bool m_hasProxyNodes; + }; ////////////////////////////////////////////////////////////////////////// diff --git a/src/GafferScene/ShaderPlug.cpp b/src/GafferScene/ShaderPlug.cpp index 09f4a22252b..3bd36f42649 100644 --- a/src/GafferScene/ShaderPlug.cpp +++ b/src/GafferScene/ShaderPlug.cpp @@ -45,6 +45,8 @@ #include "Gaffer/SubGraph.h" #include "Gaffer/Switch.h" +#include "IECoreScene/ShaderNetwork.h" + #include "IECore/MurmurHash.h" using namespace IECore; @@ -231,6 +233,7 @@ IECore::MurmurHash ShaderPlug::attributesHash() const IECore::ConstCompoundObjectPtr ShaderPlug::attributes() const { + static IECore::InternedString hasProxyNodesIdentifier( "__hasProxyNodes" ); if( const Gaffer::Plug *p = shaderOutPlug() ) { if( auto s = runTimeCast( p->node() ) ) @@ -242,7 +245,27 @@ IECore::ConstCompoundObjectPtr ShaderPlug::attributes() const outputParameter = p->relativeName( s->outPlug() ); scope.set( Shader::g_outputParameterContextName, &outputParameter ); } - return s->outAttributesPlug()->getValue(); + + IECore::ConstCompoundObjectPtr result = s->outAttributesPlug()->getValue(); + + // Check for outputs from ShaderTweakProxy, which should only be used with ShaderTweaks nodes + for( const auto &i : result->members() ) + { + if( const IECoreScene::ShaderNetwork *shaderNetwork = IECore::runTimeCast< const IECoreScene::ShaderNetwork >( i.second.get() ) ) + { + if( const BoolData *hasProxyNodes = shaderNetwork->blindData()->member( hasProxyNodesIdentifier ) ) + { + if( hasProxyNodes->readable() ) + { + throw IECore::Exception( + "ShaderTweakProxy only works with ShaderTweaks - it doesn't make sense to connect one here" + ); + + } + } + } + } + return result; } } return new CompoundObject; diff --git a/src/GafferScene/ShaderTweakProxy.cpp b/src/GafferScene/ShaderTweakProxy.cpp new file mode 100644 index 00000000000..96a8d8e82d9 --- /dev/null +++ b/src/GafferScene/ShaderTweakProxy.cpp @@ -0,0 +1,174 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Image Engine Design Inc. 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. +// +////////////////////////////////////////////////////////////////////////// + +#include "GafferScene/ShaderTweakProxy.h" + +#include "Gaffer/StringPlug.h" +#include "Gaffer/PlugAlgo.h" + +#include + +using namespace Gaffer; +using namespace GafferScene; + +namespace +{ + +const std::string g_shaderTweakProxyIdentifier = "__SHADER_TWEAK_PROXY"; + +} // namespace + + +GAFFER_NODE_DEFINE_TYPE( ShaderTweakProxy ); + +size_t ShaderTweakProxy::g_firstPlugIndex; + +ShaderTweakProxy::ShaderTweakProxy( const std::string &name ) + : Shader( name ) +{ + storeIndexOfNextChild( g_firstPlugIndex ); + + addChild( new Plug( "out", Plug::Out ) ); + parametersPlug()->addChild( new StringPlug( "targetShader", Plug::Direction::In, "" ) ); +} + +ShaderTweakProxy::~ShaderTweakProxy() +{ +} + +ShaderTweakProxy::ShaderLoaderCreatorMap &ShaderTweakProxy::shaderLoaderCreators() +{ + // Deliberately "leaking" list, as it may contain Python functors which + // cannot be destroyed during program exit (because Python will have been + // shut down first). + static auto g_creators = new ShaderLoaderCreatorMap; + return *g_creators; +} + +void ShaderTweakProxy::typePrefixAndSourceShaderName( std::string &typePrefix, std::string &sourceShaderName ) const +{ + std::string shaderName = namePlug()->getValue(); + if( shaderName == "autoProxy" ) + { + typePrefix = "autoProxy"; + sourceShaderName = ""; + return; + } + + size_t sep = shaderName.find(":"); + if( sep == std::string::npos ) + { + throw IECore::Exception( fmt::format( + "Malformed ShaderTweakProxy shader name \"{}\". Must include type prefix.", shaderName + ) ); + } + + typePrefix = shaderName.substr(0, sep ); + sourceShaderName = shaderName.substr( sep + 1 ); +} + + +void ShaderTweakProxy::loadShader( const std::string &shaderName, bool keepExistingValues ) +{ + typePlug()->setValue( g_shaderTweakProxyIdentifier ); + namePlug()->setValue( shaderName ); + if( shaderName == "autoProxy" ) + { + // Auto-proxies use dynamic plugs to represent their outputs instead of serializing a specific + // shader type. + return; + } + + // If we're proxying a specific node type, we need to find out what the outputs of that node type are + outPlug()->clearChildren(); + + std::string shaderTypePrefix, sourceShaderName; + typePrefixAndSourceShaderName( shaderTypePrefix, sourceShaderName ); + + ShaderPtr loaderNode; + + // Find the correct node type to load this shader with, and create a temporary loader node + const ShaderLoaderCreatorMap& creatorMap = shaderLoaderCreators(); + auto match = creatorMap.find( shaderTypePrefix ); + if( match != creatorMap.end() ) + { + loaderNode = match->second(); + } + else + { + std::vector possibilityList; + for( const auto &i : creatorMap ) + { + possibilityList.push_back( "\"" + i.first + "\"" ); + } + std::string possibilities = boost::algorithm::join( possibilityList, ", " ); + throw IECore::Exception( fmt::format( "No ShaderTweakProxy shader loader registered for type prefix \"{}\", options are {}", shaderTypePrefix, possibilities ) ); + } + + loaderNode->loadShader( sourceShaderName ); + + if( loaderNode->outPlug()->isInstanceOf( Gaffer::ValuePlug::staticTypeId() ) ) + { + outPlug()->addChild( loaderNode->outPlug()->createCounterpart( "out", Plug::Direction::Out ) ); + } + else + { + // Not a value plug, which means it should have children + for( const auto &untypedChild : loaderNode->outPlug()->children() ) + { + Plug *child = IECore::runTimeCast< Plug >( untypedChild.get() ); + outPlug()->addChild( child->createCounterpart( child->getName(), Plug::Direction::Out ) ); + } + } +} + +void ShaderTweakProxy::setupAutoProxy( const Plug* referencePlug ) +{ + loadShader( "autoProxy" ); + PlugPtr newPlug = referencePlug->createCounterpart( "auto", Plug::Direction::Out ); + newPlug->setFlags( Plug::Flags::Dynamic, true ); + outPlug()->addChild( newPlug ); +} + +bool ShaderTweakProxy::isProxy( const IECoreScene::Shader *shader ) +{ + return shader->getType() == g_shaderTweakProxyIdentifier; +} + +void ShaderTweakProxy::registerShaderLoader( const std::string &typePrefix, ShaderLoaderCreator creator ) +{ + shaderLoaderCreators()[typePrefix] = creator; +} diff --git a/src/GafferScene/ShaderTweaks.cpp b/src/GafferScene/ShaderTweaks.cpp index 75b097ba02c..2865fc9ead3 100644 --- a/src/GafferScene/ShaderTweaks.cpp +++ b/src/GafferScene/ShaderTweaks.cpp @@ -37,6 +37,7 @@ #include "GafferScene/ShaderTweaks.h" #include "GafferScene/Shader.h" +#include "GafferScene/ShaderTweakProxy.h" #include "Gaffer/CompoundDataPlug.h" #include "Gaffer/TweakPlug.h" @@ -47,6 +48,8 @@ #include "IECore/SimpleTypedData.h" #include "IECore/StringAlgo.h" +#include "IECore/TypeTraits.h" +#include "IECore/DataAlgo.h" #include "fmt/format.h" @@ -77,6 +80,104 @@ std::pair shaderOutput( const return { nullptr, nullptr }; } +DataPtr castDataToType( const Data* source, const Data *target ) +{ + DataPtr result; + if( source->typeId() == target->typeId() ) + { + result = source->copy(); + } + + dispatch( target, + [source, &result]( const auto *targetTyped ) + { + using TargetType = typename std::remove_const_t >; + if constexpr( TypeTraits::IsSimpleTypedData::value ) + { + using TargetValueType = typename TargetType::ValueType; + if constexpr( std::is_arithmetic_v< TargetValueType > ) + { + dispatch( source, + [&result]( const auto *sourceTyped ) + { + using SourceType = typename std::remove_const_t >; + if constexpr( TypeTraits::IsNumericSimpleTypedData::value ) + { + result = new TargetType( sourceTyped->readable() ); + } + } + ); + return; + } + + if constexpr( TypeTraits::IsVec3::value || TypeTraits::IsColor::value ) + { + dispatch( source, + [&result]( const auto *sourceTyped ) + { + using SourceType = typename std::remove_const_t >; + if constexpr( TypeTraits::IsSimpleTypedData::value ) + { + using SourceValueType = typename SourceType::ValueType; + if constexpr( + TypeTraits::IsVec3TypedData::value || + TypeTraits::IsColor::value + ) + { + typename TargetType::ValueType r; + r[0] = sourceTyped->readable()[0]; + r[1] = sourceTyped->readable()[1]; + r[2] = sourceTyped->readable()[2]; + result = new TargetType( r ); + } + } + } + ); + return; + } + } + + } + ); + + if( !result ) + { + throw IECore::Exception( fmt::format( + "Cannot connect auto proxy from \"{}\" tweak to shader input of type \"{}\"", + source->typeName(), target->typeName() + ) ); + } + + return result; +} + +void checkForCycleWalkDownstream( const ShaderNetwork &network, const IECore::InternedString &shader, std::unordered_set &dependentShaders ) +{ + if( dependentShaders.insert( shader ).second ) + { + for( const auto &connection : network.outputConnections( shader ) ) + { + checkForCycleWalkDownstream( network, connection.destination.shader, dependentShaders ); + } + } +} + +void checkForCycle( const ShaderNetwork &network, const IECore::InternedString &destShader, std::unordered_set &dependentShadersCache, const IECore::InternedString &sourceShader ) +{ + if( !dependentShadersCache.size() ) + { + checkForCycleWalkDownstream( network, destShader, dependentShadersCache ); + } + + if( dependentShadersCache.find( sourceShader ) != dependentShadersCache.end() ) + { + throw IECore::Exception( fmt::format( + "Cannot use \"{}\" in ShaderTweakProxy when tweaking \"{}\", this would create cycle in shader network.", + sourceShader.string(), destShader.string() + ) ); + } +} + } // namespace GAFFER_NODE_DEFINE_TYPE( ShaderTweaks ); @@ -286,19 +387,35 @@ bool ShaderTweaks::applyTweaks( IECoreScene::ShaderNetwork *shaderNetwork, Tweak const TweakPlug::Mode mode = static_cast( tweakPlug->modePlug()->getValue() ); - if( auto input = shaderNetwork->input( parameter ) ) + ShaderNetwork::Parameter originalInput = shaderNetwork->input( parameter ); + if( originalInput ) { if( mode != TweakPlug::Mode::Replace ) { throw IECore::Exception( fmt::format( "Cannot apply tweak to \"{}\" : Mode must be \"Replace\" when a previous connection exists", name ) ); } - shaderNetwork->removeConnection( { input, parameter } ); + shaderNetwork->removeConnection( { originalInput, parameter } ); removedConnections = true; } const auto shaderOutput = ::shaderOutput( tweakPlug.get() ); if( shaderOutput.first ) { + if( !shader->parametersData()->member( parameter.name ) ) + { + if( missingMode != TweakPlug::MissingMode::Ignore ) + { + throw IECore::Exception( fmt::format( + "Cannot apply tweak \"{}\" because shader \"{}\" does not have parameter \"{}\"", + name, parameter.shader.string(), parameter.name.string() + ) ); + } + else + { + continue; + } + } + // New connection ConstCompoundObjectPtr shaderAttributes = shaderOutput.first->attributes( shaderOutput.second ); const ShaderNetwork *inputNetwork = nullptr; @@ -316,8 +433,86 @@ bool ShaderTweaks::applyTweaks( IECoreScene::ShaderNetwork *shaderNetwork, Tweak { throw IECore::Exception( fmt::format( "Cannot apply tweak to \"{}\" : Mode must be \"Replace\" when inserting a connection", name ) ); } + const auto inputParameter = ShaderNetworkAlgo::addShaders( shaderNetwork, inputNetwork ); shaderNetwork->addConnection( { inputParameter, parameter } ); + + static IECore::InternedString hasProxyNodesIdentifier( "__hasProxyNodes" ); + + const BoolData* hasProxyNodes = inputNetwork->blindData()->member( hasProxyNodesIdentifier ); + if( hasProxyNodes && hasProxyNodes->readable() ) + { + // It would be more efficient to search for and process tweak sources just in + // `inputNetwork` before merging it to `shaderNetwork` ... but this would require + // dealing with weird connections where the input node handle is relative to `shaderNetwork`, + // but the output handle is relative to `inputNetwork`. This can't currenty be done if there + // are nodes in the two networks with the same name, which get uniquified during addShaders. + // This could be solved with an optional output unordered_map + // from addShaders(). For the moment, however, Doing this after merging simplifies all that. + + // If we need to check for cycles, we will need to populate a set of dependent shaders. + // We cache it in case there are multiple proxies connected to the same tweak. + std::unordered_set dependentShadersCache; + + for( const auto &i : shaderNetwork->shaders() ) + { + if( !ShaderTweakProxy::isProxy( i.second.get() ) ) + { + continue; + } + + const StringData* targetShaderData = + i.second->parametersData()->member( "targetShader" ); + if( !targetShaderData ) + { + throw IECore::Exception( "Cannot find target shader parameter on ShaderTweakProxy" ); + } + const std::string &sourceShader = targetShaderData->readable(); + + ShaderNetwork::ConnectionRange range = shaderNetwork->outputConnections( i.first ); + const std::vector outputConnections( range.begin(), range.end() ); + + + for( const auto &c : outputConnections ) + { + + shaderNetwork->removeConnection( c ); + removedConnections = true; + + if( sourceShader == "" ) + { + if( originalInput ) + { + shaderNetwork->addConnection( { originalInput, c.destination } ); + } + else + { + const IECoreScene::Shader *proxyConnectedShader = shaderNetwork->getShader( c.destination.shader ); + if( !proxyConnectedShader ) + { + throw IECore::Exception( fmt::format( "ShaderTweakProxy connected to non-existent shader \"{}\"", c.destination.shader.string() ) ); + } + + // Regular tweak + auto modifiedShader = modifiedShaders.insert( { c.destination.shader, nullptr } ); + if( modifiedShader.second ) + { + modifiedShader.first->second = proxyConnectedShader->copy(); + } + + const IECore::Data *origDestParameter = modifiedShader.first->second->parametersData()->member(c.destination.name, /* throwExceptions = */ true ); + modifiedShader.first->second->parameters()[c.destination.name] = castDataToType( shader->parametersData()->member( parameter.name, /* throwExceptions = */ true ), origDestParameter ); + } + } + else + { + checkForCycle( *shaderNetwork, parameter.shader, dependentShadersCache, sourceShader ); + shaderNetwork->addConnection( { { sourceShader, c.source.name }, c.destination } ); + } + } + } + } + appliedTweaks = true; } } diff --git a/src/GafferSceneModule/TweaksBinding.cpp b/src/GafferSceneModule/TweaksBinding.cpp index ba3c7233cbb..3ac1cf1871c 100644 --- a/src/GafferSceneModule/TweaksBinding.cpp +++ b/src/GafferSceneModule/TweaksBinding.cpp @@ -43,6 +43,7 @@ #include "GafferScene/CameraTweaks.h" #include "GafferScene/OptionTweaks.h" #include "GafferScene/ShaderTweaks.h" +#include "GafferScene/ShaderTweakProxy.h" #include "GafferBindings/DependencyNodeBinding.h" #include "GafferBindings/PlugBinding.h" @@ -60,4 +61,8 @@ void GafferSceneModule::bindTweaks() DependencyNodeClass(); DependencyNodeClass(); DependencyNodeClass(); + + DependencyNodeClass() + .def( "setupAutoProxy", &ShaderTweakProxy::setupAutoProxy ) + ; } diff --git a/src/GafferSceneTest/TestShader.cpp b/src/GafferSceneTest/TestShader.cpp index d34f5c58e5d..6298bad6c89 100644 --- a/src/GafferSceneTest/TestShader.cpp +++ b/src/GafferSceneTest/TestShader.cpp @@ -36,6 +36,8 @@ #include "GafferSceneTest/TestShader.h" +#include "GafferScene/ShaderTweakProxy.h" + #include "Gaffer/CompoundNumericPlug.h" #include "Gaffer/OptionalValuePlug.h" #include "Gaffer/PlugAlgo.h" @@ -95,7 +97,8 @@ Plug *setupOptionalValuePlug( return plug.get(); } -} // namespace +GafferScene::ShaderTweakProxy::ShaderLoaderDescription g_testShaderTweakProxyLoaderRegistration( "test" ); +} // namespace GAFFER_NODE_DEFINE_TYPE( TestShader ) diff --git a/startup/GafferSceneUI/gafferSceneExamples.py b/startup/GafferSceneUI/gafferSceneExamples.py index 859db22dcad..f91a01314f4 100644 --- a/startup/GafferSceneUI/gafferSceneExamples.py +++ b/startup/GafferSceneUI/gafferSceneExamples.py @@ -14,3 +14,15 @@ GafferScene.Transform ] ) + +GafferUI.Examples.registerExample( + "Scene Processing/ShaderTweaks", + "$GAFFER_ROOT/resources/examples/sceneProcessing/shaderTweaks.gfr", + description = """ + Demonstrates using ShaderTweaks for modifying shader networks. + """, + notableNodes = [ + GafferScene.ShaderTweaks, + GafferScene.ShaderTweakProxy + ] +)