Skip to content

Commit

Permalink
Merge pull request #4049 from johnhaddon/extraAttributesForwardCompat…
Browse files Browse the repository at this point in the history
…ibility

Forward compatibility for Gaffer 0.59's `Attributes.extraAttributes`
  • Loading branch information
johnhaddon authored Dec 3, 2020
2 parents 2786889 + 8291ada commit 980d919
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 0 deletions.
1 change: 1 addition & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Fixes
- LRUCache : Fixed bug which could cause hangs during scene generation (#4016).
- ArnoldShader : Moved the toon shader's `rim_light_tint` and `aov_prefix` parameters to appropriate sections in the UI.
- Spreadsheet : Fixed bug that caused sections to overflow the available space.
- Attributes : Added support for loading `extraAttributes` values and connections from Gaffer 0.59 and above.

API
---
Expand Down
28 changes: 28 additions & 0 deletions python/GafferSceneTest/CustomAttributesTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#
##########################################################################

import os
import unittest

import IECore
Expand Down Expand Up @@ -386,5 +387,32 @@ def testGlobalsDirtyPropagation( self ) :
self.assertIn( attributes["out"]["globals"], { x[0] for x in cs } )
self.assertEqual( attributes["out"].globals(), IECore.CompoundObject( { "option:render:camera" : IECore.StringData( "" ) } ) )

def testLoadExtraAttributesFrom0_59( self ) :

script = Gaffer.ScriptNode()
script["fileName"].setValue( os.path.join( os.path.dirname( __file__ ), "scripts", "extraAttributes-0.59.0.0.gfr" ) )
script.load()

self.assertEqual(
script["CustomAttributesWithExpression"]["out"].attributes( "/sphere" ),
IECore.CompoundObject( {
"a" : IECore.StringData( "a" )
} )
)

self.assertEqual(
script["CustomAttributesWithValue"]["out"].attributes( "/sphere" ),
IECore.CompoundObject( {
"b" : IECore.StringData( "b" )
} )
)

self.assertEqual(
script["CustomAttributesWithConnection"]["out"].attributes( "/sphere" ),
IECore.CompoundObject( {
"c" : IECore.StringData( "c" )
} )
)

if __name__ == "__main__":
unittest.main()
65 changes: 65 additions & 0 deletions python/GafferSceneTest/scripts/extraAttributes-0.59.0.0.gfr
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import Gaffer
import GafferImage
import GafferScene
import IECore
import imath

Gaffer.Metadata.registerValue( parent, "serialiser:milestoneVersion", 0, persistent=False )
Gaffer.Metadata.registerValue( parent, "serialiser:majorVersion", 59, persistent=False )
Gaffer.Metadata.registerValue( parent, "serialiser:minorVersion", 0, 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["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["CustomAttributesWithExpression"] = GafferScene.CustomAttributes( "CustomAttributesWithExpression" )
parent.addChild( __children["CustomAttributesWithExpression"] )
__children["CustomAttributesWithExpression"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__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["PathFilter"] = GafferScene.PathFilter( "PathFilter" )
parent.addChild( __children["PathFilter"] )
__children["PathFilter"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["Expression"] = Gaffer.Expression( "Expression" )
parent.addChild( __children["Expression"] )
__children["Expression"]["__out"].addChild( Gaffer.CompoundObjectPlug( "p0", direction = Gaffer.Plug.Direction.Out, defaultValue = IECore.CompoundObject(), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["Expression"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["CustomAttributesWithValue"] = GafferScene.CustomAttributes( "CustomAttributesWithValue" )
parent.addChild( __children["CustomAttributesWithValue"] )
__children["CustomAttributesWithValue"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["CustomAttributesWithConnection"] = GafferScene.CustomAttributes( "CustomAttributesWithConnection" )
parent.addChild( __children["CustomAttributesWithConnection"] )
__children["CustomAttributesWithConnection"]["user"].addChild( Gaffer.CompoundObjectPlug( "compoundObject", defaultValue = IECore.CompoundObject(), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["CustomAttributesWithConnection"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
parent["variables"]["imageCataloguePort"]["value"].setValue( 40634 )
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["CustomAttributesWithExpression"]["in"].setInput( __children["Sphere"]["out"] )
__children["CustomAttributesWithExpression"]["filter"].setInput( __children["PathFilter"]["out"] )
__children["CustomAttributesWithExpression"]["extraAttributes"].setInput( __children["Expression"]["__out"]["p0"] )
__children["CustomAttributesWithExpression"]["__uiPosition"].setValue( imath.V2f( -1.65157163, -1.72723973 ) )
__children["Sphere"]["__uiPosition"].setValue( imath.V2f( -2.18329573, 6.43682289 ) )
__children["PathFilter"]["paths"].setValue( IECore.StringVectorData( [ '/sphere' ] ) )
__children["PathFilter"]["__uiPosition"].setValue( imath.V2f( 37.323204, 4.35479259 ) )
__children["Expression"]["__uiPosition"].setValue( imath.V2f( -38.8122177, -1.72792196 ) )
__children["CustomAttributesWithValue"]["in"].setInput( __children["Sphere"]["out"] )
__children["CustomAttributesWithValue"]["filter"].setInput( __children["PathFilter"]["out"] )
__children["CustomAttributesWithValue"]["extraAttributes"].setValue( IECore.CompoundObject( { 'b' : IECore.StringData( 'b' )} ) )
__children["CustomAttributesWithValue"]["__uiPosition"].setValue( imath.V2f( 20.4943466, -1.72723961 ) )
__children["CustomAttributesWithConnection"]["user"]["compoundObject"].setValue( IECore.CompoundObject( { 'c' : IECore.StringData( 'c' )} ) )
__children["CustomAttributesWithConnection"]["in"].setInput( __children["Sphere"]["out"] )
__children["CustomAttributesWithConnection"]["filter"].setInput( __children["PathFilter"]["out"] )
__children["CustomAttributesWithConnection"]["extraAttributes"].setInput( __children["CustomAttributesWithConnection"]["user"]["compoundObject"] )
__children["CustomAttributesWithConnection"]["__uiPosition"].setValue( imath.V2f( -25.3940048, -1.72723961 ) )
__children["Expression"]["__engine"].setValue( 'python' )
__children["Expression"]["__expression"].setValue( 'parent["__out"]["p0"] = IECore.CompoundObject( { "a" : IECore.StringData( "a" ) } )' )


del __children

73 changes: 73 additions & 0 deletions src/Gaffer/TypedObjectPlug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

#include "Gaffer/TypedObjectPlug.h"

#include "Gaffer/Node.h"
#include "Gaffer/TypedObjectPlug.inl"

namespace Gaffer
Expand All @@ -57,6 +58,78 @@ GAFFER_PLUG_DEFINE_TEMPLATE_TYPE( Gaffer::CompoundObjectPlug, CompoundObjectPlug
GAFFER_PLUG_DEFINE_TEMPLATE_TYPE( Gaffer::AtomicCompoundDataPlug, AtomicCompoundDataPlugTypeId )
GAFFER_PLUG_DEFINE_TEMPLATE_TYPE( Gaffer::PathMatcherDataPlug, PathMatcherDataPlugTypeId )

// Specialise AtomicCompoundDataPlug to provide forward compatibility for connections
// from `Attributes.extraAttributes` in Gaffer 0.59.

const IECore::InternedString g_extraAttributes( "extraAttributes" );

bool supportCompoundObjectInput( const AtomicCompoundDataPlug *plug )
{
if( plug->getName() != g_extraAttributes )
{
return false;
}

const Node *node = plug->node();
if( !node )
{
return false;
}

return node->isInstanceOf( "GafferScene::Attributes" );
}

template<>
bool AtomicCompoundDataPlug::acceptsInput( const Plug *input ) const
{
if( !ValuePlug::acceptsInput( input ) )
{
return false;
}

if( input )
{
return
input->isInstanceOf( staticTypeId() ) ||
( input->isInstanceOf( CompoundObjectPlug::staticTypeId() ) && supportCompoundObjectInput( this ) )
;
}
return true;
}

template<>
void AtomicCompoundDataPlug::setFrom( const ValuePlug *other )
{
switch( static_cast<Gaffer::TypeId>( other->typeId() ) )
{
case AtomicCompoundDataPlugTypeId :
setValue( static_cast<const AtomicCompoundDataPlug *>( other )->getValue() );
break;
case CompoundObjectPlugTypeId : {
if( supportCompoundObjectInput( this ) )
{
IECore::ConstCompoundObjectPtr o = static_cast<const CompoundObjectPlug *>( other )->getValue();
IECore::CompoundDataPtr d = new IECore::CompoundData;
for( const auto &e : o->members() )
{
if( auto *ed = IECore::runTimeCast<IECore::Data>( e.second.get() ) )
{
d->writable()[e.first] = ed;
}
else
{
throw IECore::Exception( "Unsupported object type" );
}
}
setValue( d );
break;
}
}
default :
throw IECore::Exception( "Unsupported plug type" );
}
}

// explicit instantiation
template class TypedObjectPlug<IECore::Object>;
template class TypedObjectPlug<IECore::BoolVectorData>;
Expand Down
67 changes: 67 additions & 0 deletions startup/GafferScene/attributesCompatibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
##########################################################################
#
# Copyright (c) 2020, 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 types

import IECore
import Gaffer
import GafferScene

# The `extraAttributes` plug is an AtomicCompoundDataPlug in Gaffer 0.58, but
# it is a CompoundObjectPlug in Gaffer 0.59. Here we monkey-patch `setValue()` so
# that we can load files from the future containing CompoundObject values.

def __extraAttributesSetValue( self, value ) :

if isinstance( value, IECore.CompoundObject ) :
value = IECore.CompoundData( {
k : v for k, v in value.items()
} )

Gaffer.AtomicCompoundDataPlug.setValue( self, value )

def __attributesGetItem( originalGetItem ) :

def getItem( self, key ) :

result = originalGetItem( self, key )
if key == "extraAttributes" :
result.setValue = types.MethodType( __extraAttributesSetValue, result )
return result

return getItem

GafferScene.Attributes.__getitem__ = __attributesGetItem( GafferScene.Attributes.__getitem__ )

0 comments on commit 980d919

Please sign in to comment.