Skip to content

Commit

Permalink
Merge pull request #5398 from johnhaddon/optionalValuePlug
Browse files Browse the repository at this point in the history
OptionalValuePlug : Add new plug type for representing optional values
  • Loading branch information
johnhaddon authored Jul 18, 2023
2 parents 82b6b62 + 2f5ba2a commit dcfb2b4
Show file tree
Hide file tree
Showing 18 changed files with 615 additions and 31 deletions.
6 changes: 6 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ Fixes
- Fixed translation of `vector` typed outputs defined as `vector <name>` in an output definition.
- Fixed translation of `shadow:enable` and `shadow:color` parameters on UsdLux lights, which were previously ignored.

API
---

- OptionalValuePlug : Added a new plug type that pairs an `enabled` BoolPlug with a `value` ValuePlug.
- Shader : Added support for using OptionalValuePlug to represent optional parameters.

1.3.0.0 (relative to 1.2.10.0)
=======

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

#pragma once

#include "Gaffer/TypeIds.h"

#include "Gaffer/TypedPlug.h"

namespace Gaffer
{

class GAFFER_API OptionalValuePlug : public Gaffer::ValuePlug
{

public :

GAFFER_PLUG_DECLARE_TYPE( Gaffer::OptionalValuePlug, OptionalValuePlugTypeId, Gaffer::ValuePlug );

OptionalValuePlug(
IECore::InternedString name,
const Gaffer::ValuePlugPtr &valuePlug,
bool enabledPlugDefaultValue = false,
Direction direction = In,
unsigned flags = Default
);

Gaffer::BoolPlug *enabledPlug();
const Gaffer::BoolPlug *enabledPlug() const;

template<typename T = Gaffer::ValuePlug>
T *valuePlug();
template<typename T = Gaffer::ValuePlug>
const T *valuePlug() const;

bool acceptsChild( const Gaffer::GraphComponent *potentialChild ) const override;
Gaffer::PlugPtr createCounterpart( const std::string &name, Direction direction ) const override;

};

IE_CORE_DECLAREPTR( OptionalValuePlug );

} // namespace Gaffer

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

#pragma once

namespace Gaffer
{

template<typename T>
T *OptionalValuePlug::valuePlug()
{
return getChild<T>( 1 );
}

template<typename T>
const T *OptionalValuePlug::valuePlug() const
{
return getChild<T>( 1 );
}

} // namespace Gaffer
1 change: 1 addition & 0 deletions include/Gaffer/TypeIds.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ enum TypeId
ContextVariableTweaksTypeId = 110104,
HiddenFilePathFilterTypeId = 110105,
Color4fVectorDataPlugTypeId = 110106,
OptionalValuePlugTypeId = 110107,

LastTypeId = 110159,

Expand Down
2 changes: 2 additions & 0 deletions include/GafferImage/OpenColorIOContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class GAFFERIMAGE_API OpenColorIOContext : public Gaffer::ContextProcessor
explicit OpenColorIOContext( const std::string &name=GraphComponent::defaultName<OpenColorIOContext>() );
~OpenColorIOContext() override;

/// \todo Return OptionalValuePlug, and remove `configEnabledPlug()` and
/// `configValuePlug()` methods. Do the same for `workingSpace` plugs.
Gaffer::ValuePlug *configPlug();
const Gaffer::ValuePlug *configPlug() const;

Expand Down
18 changes: 0 additions & 18 deletions python/GafferImageUI/OpenColorIOContextUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@
""",

"nodule:type", "",
"plugValueWidget:type", "GafferUI.LayoutPlugValueWidget",
"layoutPlugValueWidget:orientation", "horizontal",

],

Expand All @@ -93,10 +91,6 @@
to be specified.
""",

"nodule:type", "",
"boolPlugValueWidget:displayMode", "switch",
"label", "",

],

"config.value" : [
Expand All @@ -106,9 +100,6 @@
Specifies the OpenColorIO config to be used.
""",

"nodule:type", "",
"label", "",
"layout:activator", lambda plug : plug.parent()["enabled"].getValue(),
"plugValueWidget:type", "GafferUI.PresetsPlugValueWidget",
"presetsPlugValueWidget:allowCustom", True,

Expand All @@ -127,8 +118,6 @@
""",

"nodule:type", "",
"plugValueWidget:type", "GafferUI.LayoutPlugValueWidget",
"layoutPlugValueWidget:orientation", "horizontal",

],

Expand All @@ -140,10 +129,6 @@
to be specified.
""",

"nodule:type", "",
"boolPlugValueWidget:displayMode", "switch",
"label", "",

],

"workingSpace.value" : [
Expand All @@ -153,9 +138,6 @@
Specifies the working color space to be used.
""",

"nodule:type", "",
"label", "",
"layout:activator", lambda plug : plug.parent()["enabled"].getValue(),
"plugValueWidget:type", "GafferUI.PresetsPlugValueWidget",
"presetNames", GafferImageUI.OpenColorIOTransformUI.colorSpacePresetNames,
"presetValues", GafferImageUI.OpenColorIOTransformUI.colorSpacePresetValues,
Expand Down
22 changes: 22 additions & 0 deletions python/GafferSceneTest/ShaderTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,5 +429,27 @@ def testSpline( self ) :
with self.assertRaisesRegex( RuntimeError, "n1.__outAttributes : Cannot support monotone cubic interpolation for splines with inputs, for plug n1.parameters.spline" ):
network = n1.attributes()["test:surface"]

def testOptionalParameter( self ) :

node = GafferSceneTest.TestShader( "n1" )
node["type"].setValue( "test:surface" )

shader = node.attributes()["test:surface"].outputShader()
self.assertNotIn( "optionalString", shader.parameters )

node["parameters"]["optionalString"]["enabled"].setValue( True )
shader = node.attributes()["test:surface"].outputShader()
self.assertIn( "optionalString", shader.parameters )
self.assertEqual( shader.parameters["optionalString"], IECore.StringData() )

node["parameters"]["optionalString"]["value"].setValue( "test" )
shader = node.attributes()["test:surface"].outputShader()
self.assertIn( "optionalString", shader.parameters )
self.assertEqual( shader.parameters["optionalString"], IECore.StringData( "test" ) )

node["parameters"]["optionalString"]["enabled"].setValue( False )
shader = node.attributes()["test:surface"].outputShader()
self.assertNotIn( "optionalString", shader.parameters )

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

import unittest

import Gaffer
import GafferTest

class OptionalValuePlugTest( GafferTest.TestCase ) :

def testConstruction( self ) :

valuePlug = Gaffer.IntPlug()
plug = Gaffer.OptionalValuePlug( "name", valuePlug, True )

self.assertEqual( plug.getName(), "name" )
self.assertEqual( plug.direction(), Gaffer.Plug.Direction.In )
self.assertEqual( len( plug ), 2 )

self.assertIsInstance( plug["enabled"], Gaffer.BoolPlug )
self.assertEqual( plug["enabled"].defaultValue(), True )
self.assertEqual( plug["enabled"].direction(), Gaffer.Plug.Direction.In )
self.assertTrue( plug["enabled"].isSame( plug[0] ) )

self.assertTrue( plug["value"].isSame( valuePlug ) )
self.assertTrue( plug["value"].isSame( plug[1] ) )

def testAcceptsChild( self ) :

plug = Gaffer.OptionalValuePlug( "name", Gaffer.IntPlug() )
self.assertFalse( plug.acceptsChild( Gaffer.IntPlug() ) )

def testCreateCounterpart( self ) :

plug = Gaffer.OptionalValuePlug( "name", Gaffer.IntPlug() )
plug["enabled"].setValue( True ) # Current values should be ignored by
plug["value"].setValue( 10 ) # `createCounterpart()`.

for direction in ( Gaffer.Plug.Direction.In, Gaffer.Plug.Direction.Out ) :

with self.subTest( direction = direction ) :

plug2 = plug.createCounterpart( "counter", direction )
self.assertEqual( plug2.direction(), direction )
self.assertTrue( plug2.isSetToDefault() )

self.assertEqual( plug2["enabled"].direction(), direction )
self.assertEqual( plug2["enabled"].defaultValue(), plug["enabled"].defaultValue() )

self.assertEqual( plug2["value"].direction(), direction )
self.assertIsInstance( plug2["value"], Gaffer.IntPlug )
self.assertEqual( plug2["value"].defaultValue(), plug["value"].defaultValue() )

def testSerialisation( self ) :

script = Gaffer.ScriptNode()
script["node"] = Gaffer.Node()
script["node"]["user"]["p"] = Gaffer.OptionalValuePlug( valuePlug = Gaffer.IntPlug(), enabledPlugDefaultValue = True, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic )
script["node"]["user"]["p"]["enabled"].setValue( False )
script["node"]["user"]["p"]["value"].setValue( 10 )

script2 = Gaffer.ScriptNode()
script2.execute( script.serialise() )

self.assertEqual( script2["node"]["user"]["p"]["enabled"].defaultValue(), script["node"]["user"]["p"]["enabled"].defaultValue() )
self.assertEqual( script2["node"]["user"]["p"]["enabled"].getValue(), script["node"]["user"]["p"]["enabled"].getValue() )
self.assertEqual( script2["node"]["user"]["p"]["value"].defaultValue(), script["node"]["user"]["p"]["value"].defaultValue() )
self.assertEqual( script2["node"]["user"]["p"]["value"].getValue(), script["node"]["user"]["p"]["value"].getValue() )

if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions python/GafferTest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ def inCI( platforms = set() ) :
from .TweakPlugTest import TweakPlugTest
from .HiddenFilePathFilterTest import HiddenFilePathFilterTest
from .ContextVariableTweaksTest import ContextVariableTweaksTest
from .OptionalValuePlugTest import OptionalValuePlugTest

from .IECorePreviewTest import *

Expand Down
Loading

0 comments on commit dcfb2b4

Please sign in to comment.