From c17364150d4eacc869dace552ad53938c1491f2e Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 8 Mar 2017 16:11:21 +0000 Subject: [PATCH 01/25] Gaffer : Add BoxIO node This provides the base class for convenience nodes to allow the graphical promotion of plugs within the NodeGraph. --- include/Gaffer/BoxIO.h | 143 ++++++++++++++++++ include/Gaffer/BoxIO.inl | 95 ++++++++++++ include/Gaffer/TypeIds.h | 3 + src/Gaffer/BoxIO.cpp | 319 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 560 insertions(+) create mode 100644 include/Gaffer/BoxIO.h create mode 100644 include/Gaffer/BoxIO.inl create mode 100644 src/Gaffer/BoxIO.cpp diff --git a/include/Gaffer/BoxIO.h b/include/Gaffer/BoxIO.h new file mode 100644 index 00000000000..a973c29e213 --- /dev/null +++ b/include/Gaffer/BoxIO.h @@ -0,0 +1,143 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2017, 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef GAFFER_BOXIO_H +#define GAFFER_BOXIO_H + +#include "Gaffer/Node.h" +#include "Gaffer/Plug.h" + +namespace Gaffer +{ + +IE_CORE_FORWARDDECLARE( StringPlug ) + +/// Utility node for representing plug promotion +/// graphically in the NodeGraph. Note that this has +/// no special priviledges or meaning in the Box API; +/// it is merely a convenience for the user. +/// +/// In terms of structure, BoxIO is much like +/// a Dot, with an internal pass-through connection +/// between a single input plug and a single output plug. +/// It differs in that one of these plugs is +/// always private and managed such that it is +/// automatically promoted to any parent Box. Which plug +/// is promoted is determined by the BoxIO's direction, +/// which specifies whether it provides an input or +/// output for the box. +/// +/// The BoxIO constructor is protected. Construct +/// the derived BoxIn and BoxOut classes rather than +/// attempt to construct BoxIO itself. +class BoxIO : public Node +{ + + public : + + virtual ~BoxIO(); + + IE_CORE_DECLARERUNTIMETYPEDEXTENSION( Gaffer::BoxIO, BoxIOTypeId, Node ); + + StringPlug *namePlug(); + const StringPlug *namePlug() const; + + /// Sets this node up using `plug` + /// as a prototype. Call this after + /// construction to determine what + /// sort of plug this node will promote. + void setup( const Plug *plug ); + + /// The internal plug which + /// can be used within the box. + /// Will be NULL unless `setup()` + /// has been called. + template + T *plug(); + template + const T *plug() const; + + /// The external plug which has + /// been promoted to the outside + /// of the box. Will be NULL unless + /// `setup()` has been called. + template + T *promotedPlug(); + template + const T *promotedPlug() const; + + Plug::Direction direction() const; + + protected : + + BoxIO( Plug::Direction direction, const std::string &name=defaultName() ); + + Gaffer::Plug *inPlugInternal(); + const Gaffer::Plug *inPlugInternal() const; + + Gaffer::Plug *outPlugInternal(); + const Gaffer::Plug *outPlugInternal() const; + + virtual void parentChanging( Gaffer::GraphComponent *newParent ); + + private : + + IECore::InternedString inPlugName() const; + IECore::InternedString outPlugName() const; + + Plug::Direction m_direction; + + boost::signals::scoped_connection m_nameChangedConnection; + + void plugSet( Plug *plug ); + void parentChanged( GraphComponent *oldParent ); + void plugInputChanged( Plug *plug ); + void nameChanged( GraphComponent *graphComponent ); + + static size_t g_firstPlugIndex; + +}; + +IE_CORE_DECLAREPTR( BoxIO ) + +typedef FilteredChildIterator > BoxIOIterator; +typedef FilteredRecursiveChildIterator > RecursiveBoxIOIterator; + +} // namespace Gaffer + +#include "Gaffer/BoxIO.inl" + +#endif // GAFFER_BOXIO_H diff --git a/include/Gaffer/BoxIO.inl b/include/Gaffer/BoxIO.inl new file mode 100644 index 00000000000..7df739f9eec --- /dev/null +++ b/include/Gaffer/BoxIO.inl @@ -0,0 +1,95 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2017, 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef GAFFER_BOXIO_INL +#define GAFFER_BOXIO_INL + +#include "Gaffer/Node.h" +#include "Gaffer/Plug.h" + +namespace Gaffer +{ + +template +T *BoxIO::plug() +{ + return IECore::runTimeCast( + m_direction == Plug::In ? outPlugInternal() : inPlugInternal() + ); +} + +template +const T *BoxIO::plug() const +{ + return IECore::runTimeCast( + m_direction == Plug::In ? outPlugInternal() : inPlugInternal() + ); +} + +template +T *BoxIO::promotedPlug() +{ + if( m_direction == Plug::In ) + { + if( Plug *p = inPlugInternal() ) + { + return p->getInput(); + } + } + else + { + if( Plug *p = outPlugInternal() ) + { + const Plug::OutputContainer &outputs = p->outputs(); + if( !outputs.empty() ) + { + return outputs.front(); + } + } + } + return NULL; +} + +template +const T *BoxIO::promotedPlug() const +{ + // Prefer cast over maintaining identical copies of function + return const_cast( this )->promotedPlug(); +} + +} // namespace Gaffer + +#endif // GAFFER_BOXIO_INL diff --git a/include/Gaffer/TypeIds.h b/include/Gaffer/TypeIds.h index dad6fe864e3..936a288da65 100644 --- a/include/Gaffer/TypeIds.h +++ b/include/Gaffer/TypeIds.h @@ -129,6 +129,9 @@ enum TypeId FileSequencePathFilterTypeId = 110082, M44fVectorDataPlugTypeId = 110083, V2iVectorDataPlugTypeId = 110084, + BoxIOTypeId = 110085, + BoxInTypeId = 110086, + BoxOutTypeId = 110087, LastTypeId = 110159, diff --git a/src/Gaffer/BoxIO.cpp b/src/Gaffer/BoxIO.cpp new file mode 100644 index 00000000000..a0f4491aa73 --- /dev/null +++ b/src/Gaffer/BoxIO.cpp @@ -0,0 +1,319 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2017, 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 "boost/bind.hpp" + +#include "Gaffer/StringPlug.h" +#include "Gaffer/BoxIO.h" +#include "Gaffer/Box.h" +#include "Gaffer/Metadata.h" +#include "Gaffer/MetadataAlgo.h" +#include "Gaffer/PlugAlgo.h" + +using namespace IECore; +using namespace Gaffer; + +////////////////////////////////////////////////////////////////////////// +// Internal constants +////////////////////////////////////////////////////////////////////////// + +namespace +{ + +InternedString g_inName( "in" ); +InternedString g_inNamePrivate( "__in" ); +InternedString g_outName( "out" ); +InternedString g_outNamePrivate( "__out" ); +InternedString g_sectionName( "noduleLayout:section" ); + +std::string oppositeSection( const std::string §ion ) +{ + if( section == "left" ) + { + return "right"; + } + else if( section == "right" ) + { + return "left"; + } + else if( section == "bottom" ) + { + return "top"; + } + else + { + return "bottom"; + } +} + +void setupNoduleSectionMetadata( Plug *dst, const Plug *src ) +{ + ConstStringDataPtr sectionData; + for( const Plug *metadataPlug = src; metadataPlug; metadataPlug = metadataPlug->parent() ) + { + if( ( sectionData = Metadata::value( metadataPlug, g_sectionName ) ) ) + { + break; + } + } + + if( !sectionData ) + { + return; + } + + std::string section = sectionData->readable(); + if( src->direction() != dst->direction() ) + { + section = oppositeSection( section ); + } + + Metadata::registerValue( dst, g_sectionName, new StringData( section ) ); + +} + +} // namespace + +////////////////////////////////////////////////////////////////////////// +// BoxIO +////////////////////////////////////////////////////////////////////////// + +IE_CORE_DEFINERUNTIMETYPED( BoxIO ); + +size_t BoxIO::g_firstPlugIndex = 0; + +BoxIO::BoxIO( Plug::Direction direction, const std::string &name ) + : Node( name ), m_direction( direction ) +{ + storeIndexOfNextChild( g_firstPlugIndex ); + // Must not accept inputs because the name is syncronised with the promoted + // plug name and must therefore not be context-varying. + addChild( new StringPlug( "name", Plug::In, direction == Plug::In ? "in" : "out", Plug::Default & ~Plug::AcceptsInputs ) ); + + // Connect to the signals we need to syncronise the namePlug() value + // with the name of the promotedPlug(). + plugSetSignal().connect( boost::bind( &BoxIO::plugSet, this, ::_1 ) ); + if( direction == Plug::In ) + { + plugInputChangedSignal().connect( boost::bind( &BoxIO::plugInputChanged, this, ::_1 ) ); + } + else + { + parentChangedSignal().connect( boost::bind( &BoxIO::parentChanged, this, ::_2 ) ); + } +} + +BoxIO::~BoxIO() +{ +} + +StringPlug *BoxIO::namePlug() +{ + return getChild( g_firstPlugIndex ); +} + +const StringPlug *BoxIO::namePlug() const +{ + return getChild( g_firstPlugIndex ); +} + +void BoxIO::setup( const Plug *plug ) +{ + if( plug ) + { + if( inPlugInternal() ) + { + throw IECore::Exception( "Plugs already set up" ); + } + addChild( plug->createCounterpart( inPlugName(), Plug::In ) ); + addChild( plug->createCounterpart( outPlugName(), Plug::Out ) ); + + inPlugInternal()->setFlags( Plug::Dynamic, true ); + outPlugInternal()->setFlags( Plug::Dynamic, true ); + outPlugInternal()->setInput( inPlugInternal() ); + + MetadataAlgo::copy( + plug, + m_direction == Plug::In ? inPlugInternal() : outPlugInternal(), + /* exclude = */ "layout:*" + ); + + setupNoduleSectionMetadata( + m_direction == Plug::In ? outPlugInternal() : inPlugInternal(), + plug + ); + } + + if( promotedPlug() ) + { + throw IECore::Exception( "Promoted plug already set up" ); + } + + if( parent() ) + { + Plug *toPromote = m_direction == Plug::In ? inPlugInternal() : outPlugInternal(); + Plug *promoted = PlugAlgo::promote( toPromote ); + promoted->setName( namePlug()->getValue() ); + } +} + +Plug::Direction BoxIO::direction() const +{ + return m_direction; +} + +Gaffer::Plug *BoxIO::inPlugInternal() +{ + return getChild( inPlugName() ); +} + +const Gaffer::Plug *BoxIO::inPlugInternal() const +{ + return getChild( inPlugName() ); +} + +Gaffer::Plug *BoxIO::outPlugInternal() +{ + return getChild( outPlugName() ); +} + +const Gaffer::Plug *BoxIO::outPlugInternal() const +{ + return getChild( outPlugName() ); +} + +void BoxIO::parentChanging( Gaffer::GraphComponent *newParent ) +{ + // We're being deleted or moved to another parent. Delete + // the promoted versions of our input and output plugs. + // This allows the user to remove all trace of us by simply + // deleting the BoxInOut node. We must do this in parentChanging() + // rather than parentChanged() because we need a current parent + // in order for the operations below to be undoable. + + if( parent() ) + { + if( Plug *i = inPlugInternal() ) + { + if( PlugAlgo::isPromoted( i ) ) + { + PlugAlgo::unpromote( i ); + } + } + if( Plug *o = outPlugInternal() ) + { + if( PlugAlgo::isPromoted( o ) ) + { + PlugAlgo::unpromote( o ); + } + } + } + + Node::parentChanging( newParent ); +} + +IECore::InternedString BoxIO::inPlugName() const +{ + return m_direction == Plug::In ? g_inNamePrivate : g_inName; +} + +IECore::InternedString BoxIO::outPlugName() const +{ + return m_direction == Plug::Out ? g_outNamePrivate : g_outName; +} + +void BoxIO::plugSet( Plug *plug ) +{ + if( plug == namePlug() ) + { + if( Plug *p = promotedPlug() ) + { + const InternedString newName = p->setName( namePlug()->getValue() ); + // Name may have been adjusted due to + // not being unique. Update the plug to + // use the adjusted name. + namePlug()->setValue( newName ); + } + } +} + +void BoxIO::parentChanged( GraphComponent *oldParent ) +{ + // Manage inputChanged connections on our parent box, + // so we can discover our promoted plug when an output + // connection is made to it. + if( Box *box = runTimeCast( oldParent ) ) + { + box->plugInputChangedSignal().disconnect( + boost::bind( &BoxIO::plugInputChanged, this, ::_1 ) + ); + } + if( Box *box = parent() ) + { + box->plugInputChangedSignal().connect( + boost::bind( &BoxIO::plugInputChanged, this, ::_1 ) + ); + } +} + +void BoxIO::plugInputChanged( Plug *plug ) +{ + if( m_direction == Plug::In && plug == inPlugInternal() ) + { + m_nameChangedConnection.disconnect(); + if( Plug *p = promotedPlug() ) + { + m_nameChangedConnection = p->nameChangedSignal().connect( + boost::bind( &BoxIO::nameChanged, this, ::_1 ) + ); + } + } + else if( m_direction == Plug::Out && plug == promotedPlug() ) + { + m_nameChangedConnection.disconnect(); + m_nameChangedConnection = plug->nameChangedSignal().connect( + boost::bind( &BoxIO::nameChanged, this, ::_1 ) + ); + } +} + +void BoxIO::nameChanged( GraphComponent *graphComponent ) +{ + if( graphComponent == promotedPlug() ) + { + namePlug()->setValue( graphComponent->getName() ); + } +} From fedf572690eb1acf4e201084ef09521c97926751 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 8 Mar 2017 16:12:05 +0000 Subject: [PATCH 02/25] Gaffer : Add BoxIn node --- include/Gaffer/BoxIn.h | 64 ++++++++++++++++++++++++++++++++++++++++++ src/Gaffer/BoxIn.cpp | 50 +++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 include/Gaffer/BoxIn.h create mode 100644 src/Gaffer/BoxIn.cpp diff --git a/include/Gaffer/BoxIn.h b/include/Gaffer/BoxIn.h new file mode 100644 index 00000000000..6a89b46aec0 --- /dev/null +++ b/include/Gaffer/BoxIn.h @@ -0,0 +1,64 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2017, 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef GAFFER_BOXIN_H +#define GAFFER_BOXIN_H + +#include "Gaffer/BoxIO.h" + +namespace Gaffer +{ + +class BoxIn : public BoxIO +{ + + public : + + BoxIn( const std::string &name=defaultName() ); + virtual ~BoxIn(); + + IE_CORE_DECLARERUNTIMETYPEDEXTENSION( Gaffer::BoxIn, BoxInTypeId, BoxIO ); + +}; + +IE_CORE_DECLAREPTR( BoxIn ) + +typedef FilteredChildIterator > BoxInIterator; +typedef FilteredRecursiveChildIterator > RecursiveBoxInIterator; + +} // namespace Gaffer + +#endif // GAFFER_BOXIN_H diff --git a/src/Gaffer/BoxIn.cpp b/src/Gaffer/BoxIn.cpp new file mode 100644 index 00000000000..ad847f72f46 --- /dev/null +++ b/src/Gaffer/BoxIn.cpp @@ -0,0 +1,50 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2017, 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 "Gaffer/BoxIn.h" + +using namespace Gaffer; + +IE_CORE_DEFINERUNTIMETYPED( BoxIn ) + +BoxIn::BoxIn( const std::string &name ) + : BoxIO( Plug::In, name ) +{ +} + +BoxIn::~BoxIn() +{ +} From ae8adb90fcb7bdfcf6ce1ea4345ee2d633b0a1f0 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 8 Mar 2017 16:12:37 +0000 Subject: [PATCH 03/25] Gaffer : Add BoxOut node --- include/Gaffer/BoxOut.h | 64 +++++++++++++++++++++++++++++++++++++++++ src/Gaffer/BoxOut.cpp | 50 ++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 include/Gaffer/BoxOut.h create mode 100644 src/Gaffer/BoxOut.cpp diff --git a/include/Gaffer/BoxOut.h b/include/Gaffer/BoxOut.h new file mode 100644 index 00000000000..2f87cda9c59 --- /dev/null +++ b/include/Gaffer/BoxOut.h @@ -0,0 +1,64 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2017, 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef GAFFER_BOXOUT_H +#define GAFFER_BOXOUT_H + +#include "Gaffer/BoxIO.h" + +namespace Gaffer +{ + +class BoxOut : public BoxIO +{ + + public : + + BoxOut( const std::string &name=defaultName() ); + virtual ~BoxOut(); + + IE_CORE_DECLARERUNTIMETYPEDEXTENSION( Gaffer::BoxOut, BoxOutTypeId, BoxIO ); + +}; + +IE_CORE_DECLAREPTR( BoxOut ) + +typedef FilteredChildIterator > BoxOutIterator; +typedef FilteredRecursiveChildIterator > RecursiveBoxOutIterator; + +} // namespace Gaffer + +#endif // GAFFER_BOXOUT_H diff --git a/src/Gaffer/BoxOut.cpp b/src/Gaffer/BoxOut.cpp new file mode 100644 index 00000000000..73d771f35de --- /dev/null +++ b/src/Gaffer/BoxOut.cpp @@ -0,0 +1,50 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2017, 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 "Gaffer/BoxOut.h" + +using namespace Gaffer; + +IE_CORE_DEFINERUNTIMETYPED( BoxOut ) + +BoxOut::BoxOut( const std::string &name ) + : BoxIO( Plug::Out, name ) +{ +} + +BoxOut::~BoxOut() +{ +} From 7f25832cc4727247c1b6b58c810a1cd0f2d49c14 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 8 Mar 2017 16:44:17 +0000 Subject: [PATCH 04/25] Box binding : Move serialiser to anonymous namespace --- include/Gaffer/Box.h | 7 ------- src/GafferBindings/BoxBinding.cpp | 9 +++++---- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/include/Gaffer/Box.h b/include/Gaffer/Box.h index 4f7d8c00619..d8913883782 100644 --- a/include/Gaffer/Box.h +++ b/include/Gaffer/Box.h @@ -39,13 +39,6 @@ #include "Gaffer/SubGraph.h" -namespace GafferBindings -{ - -class BoxSerialiser; - -} // namespace GafferBindings - namespace Gaffer { diff --git a/src/GafferBindings/BoxBinding.cpp b/src/GafferBindings/BoxBinding.cpp index 70a3f3819ed..781cf0f7a7f 100644 --- a/src/GafferBindings/BoxBinding.cpp +++ b/src/GafferBindings/BoxBinding.cpp @@ -45,8 +45,9 @@ using namespace boost::python; using namespace IECorePython; using namespace Gaffer; +using namespace GafferBindings; -namespace GafferBindings +namespace { class BoxSerialiser : public NodeSerialiser @@ -72,7 +73,9 @@ class BoxSerialiser : public NodeSerialiser }; -void bindBox() +} // namespace + +void GafferBindings::bindBox() { typedef DependencyNodeWrapper BoxWrapper; @@ -89,5 +92,3 @@ void bindBox() Serialisation::registerSerialiser( Box::staticTypeId(), new BoxSerialiser ); } - -} // namespace GafferBindings From f96d2ff554399a7e8375fdeb727c9cfd9a6a26c8 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 8 Mar 2017 16:50:32 +0000 Subject: [PATCH 05/25] GafferBindings : Add BoxIO/BoxIn/BoxOut bindings --- include/GafferBindings/BoxIOBinding.h | 47 +++++++++++ src/GafferBindings/BoxIOBinding.cpp | 117 ++++++++++++++++++++++++++ src/GafferModule/GafferModule.cpp | 2 + 3 files changed, 166 insertions(+) create mode 100644 include/GafferBindings/BoxIOBinding.h create mode 100644 src/GafferBindings/BoxIOBinding.cpp diff --git a/include/GafferBindings/BoxIOBinding.h b/include/GafferBindings/BoxIOBinding.h new file mode 100644 index 00000000000..c927a3cc518 --- /dev/null +++ b/include/GafferBindings/BoxIOBinding.h @@ -0,0 +1,47 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2017, 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef GAFFERBINDINGS_BOXIOBINDING_H +#define GAFFERBINDINGS_BOXIOBINDING_H + +namespace GafferBindings +{ + +void bindBoxIO(); + +} // namespace GafferBindings + +#endif // GAFFERBINDINGS_BOXIOBINDING_H diff --git a/src/GafferBindings/BoxIOBinding.cpp b/src/GafferBindings/BoxIOBinding.cpp new file mode 100644 index 00000000000..9b007f7a2b5 --- /dev/null +++ b/src/GafferBindings/BoxIOBinding.cpp @@ -0,0 +1,117 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2017, 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 "boost/python.hpp" // must be the first include + +#include "Gaffer/BoxIn.h" +#include "Gaffer/BoxOut.h" +#include "Gaffer/Plug.h" + +#include "GafferBindings/DependencyNodeBinding.h" +#include "GafferBindings/BoxIOBinding.h" + +using namespace boost::python; +using namespace IECorePython; +using namespace Gaffer; +using namespace GafferBindings; + +namespace +{ + +class BoxIOSerialiser : public NodeSerialiser +{ + + virtual std::string postScript( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const + { + std::string result = NodeSerialiser::postScript( graphComponent, identifier, serialisation ); + + const BoxIO *boxIO = static_cast( graphComponent ); + if( !boxIO->plug() ) + { + // BoxIO::setup() hasn't been called yet. + return result; + } + + const Plug *promoted = boxIO->promotedPlug(); + if( promoted && serialisation.identifier( promoted ) != "" ) + { + return result; + } + + // The BoxIO node has been set up, but its promoted plug isn't + // being serialised (for instance, because someone is copying a + // selection from inside a box). Add a setup() call to the + // serialisation so that the promoted plug will be created upon + // pasting into another box. + + if( !result.empty() ) + { + result += "\n"; + } + result += identifier + ".setup()\n"; + + return result; + } + +}; + +PlugPtr plug( BoxIO &b ) +{ + return b.plug(); +} + +PlugPtr promotedPlug( BoxIO &b ) +{ + return b.promotedPlug(); +} + +} // namespace + +void GafferBindings::bindBoxIO() +{ + + NodeClass( NULL, no_init ) + .def( "setup", &BoxIO::setup, ( arg( "plug" ) = object() ) ) + .def( "plug", &plug ) + .def( "promotedPlug", &promotedPlug ) + ; + + Serialisation::registerSerialiser( BoxIO::staticTypeId(), new BoxIOSerialiser ); + + NodeClass(); + NodeClass(); + +} diff --git a/src/GafferModule/GafferModule.cpp b/src/GafferModule/GafferModule.cpp index 314298643fe..fd603f9e28a 100644 --- a/src/GafferModule/GafferModule.cpp +++ b/src/GafferModule/GafferModule.cpp @@ -93,6 +93,7 @@ #include "GafferBindings/MetadataAlgoBinding.h" #include "GafferBindings/SwitchBinding.h" #include "GafferBindings/PlugAlgoBinding.h" +#include "GafferBindings/BoxIOBinding.h" using namespace boost::python; using namespace Gaffer; @@ -200,6 +201,7 @@ BOOST_PYTHON_MODULE( _Gaffer ) bindMetadataAlgo(); bindSwitch(); bindPlugAlgo(); + bindBoxIO(); NodeClass(); From cafa6eda21825d488613bb8673f83b9d24b01a50 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 8 Mar 2017 16:51:27 +0000 Subject: [PATCH 06/25] GafferTest : Add tests for BoxIO nodes --- python/GafferTest/BoxInTest.py | 223 ++++++++++++++++++++++++++++++++ python/GafferTest/BoxOutTest.py | 182 ++++++++++++++++++++++++++ python/GafferTest/__init__.py | 2 + 3 files changed, 407 insertions(+) create mode 100644 python/GafferTest/BoxInTest.py create mode 100644 python/GafferTest/BoxOutTest.py diff --git a/python/GafferTest/BoxInTest.py b/python/GafferTest/BoxInTest.py new file mode 100644 index 00000000000..a3fd9101789 --- /dev/null +++ b/python/GafferTest/BoxInTest.py @@ -0,0 +1,223 @@ +########################################################################## +# +# Copyright (c) 2017, 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 unittest + +import IECore + +import Gaffer +import GafferTest + +class BoxInTest( GafferTest.TestCase ) : + + def testSetup( self ) : + + s = Gaffer.ScriptNode() + s["b"] = Gaffer.Box() + s["b"]["n"] = GafferTest.AddNode() + + s["b"]["i"] = Gaffer.BoxIn() + self.assertEqual( s["b"]["i"]["name"].getValue(), "in" ) + self.assertEqual( s["b"]["i"]["name"].defaultValue(), "in" ) + + s["b"]["i"]["name"].setValue( "op1" ) + s["b"]["i"].setup( s["b"]["n"]["op1"] ) + s["b"]["n"]["op1"].setInput( s["b"]["i"]["out"] ) + + self.assertTrue( "op1" in s["b"] ) + self.assertTrue( s["b"]["n"]["op1"].source().isSame( s["b"]["op1"] ) ) + + def testNameTracking( self ) : + + s = Gaffer.ScriptNode() + s["b"] = Gaffer.Box() + s["b"]["n"] = GafferTest.AddNode() + + s["b"]["i"] = Gaffer.BoxIn() + s["b"]["i"]["name"].setValue( "test" ) + s["b"]["i"].setup( s["b"]["n"]["op1"] ) + promoted = s["b"]["i"].promotedPlug() + + self.assertEqual( s["b"]["i"]["name"].getValue(), "test" ) + self.assertEqual( promoted.getName(), s["b"]["i"]["name"].getValue() ) + + with Gaffer.UndoContext( s ) : + promoted.setName( "bob" ) + + self.assertEqual( promoted.getName(), "bob" ) + self.assertEqual( s["b"]["i"]["name"].getValue(), "bob" ) + + s.undo() + + self.assertEqual( s["b"]["i"]["name"].getValue(), "test" ) + self.assertEqual( promoted.getName(), s["b"]["i"]["name"].getValue() ) + + with Gaffer.UndoContext( s ) : + s["b"]["i"]["name"].setValue( "jim" ) + + self.assertEqual( promoted.getName(), "jim" ) + self.assertEqual( s["b"]["i"]["name"].getValue(), "jim" ) + + s.undo() + + self.assertEqual( s["b"]["i"]["name"].getValue(), "test" ) + self.assertEqual( promoted.getName(), s["b"]["i"]["name"].getValue() ) + + def testSerialisation( self ) : + + s = Gaffer.ScriptNode() + s["b"] = Gaffer.Box() + s["b"]["n"] = GafferTest.AddNode() + + s["b"]["i"] = Gaffer.BoxIn() + s["b"]["i"]["name"].setValue( "op1" ) + s["b"]["i"].setup( s["b"]["n"]["op1"] ) + s["b"]["n"]["op1"].setInput( s["b"]["i"]["out"] ) + + s2 = Gaffer.ScriptNode() + s2.execute( s.serialise() ) + + self.assertTrue( "op1" in s2["b"] ) + self.assertTrue( s2["b"]["n"]["op1"].source().isSame( s2["b"]["op1"] ) ) + + def testDeleteRemovesPromotedPlugs( self ) : + + s = Gaffer.ScriptNode() + + s["b"] = Gaffer.Box() + s["b"]["n"] = GafferTest.AddNode() + + s["b"]["i"] = Gaffer.BoxIn() + s["b"]["i"]["name"].setValue( "op1" ) + s["b"]["i"].setup( s["b"]["n"]["op1"] ) + s["b"]["n"]["op1"].setInput( s["b"]["i"]["out"] ) + + self.assertTrue( "op1" in s["b"] ) + self.assertTrue( s["b"]["n"]["op1"].source().isSame( s["b"]["op1"] ) ) + + with Gaffer.UndoContext( s ) : + del s["b"]["i"] + + self.assertFalse( "op1" in s["b"] ) + self.assertTrue( s["b"]["n"]["op1"].getInput() is None ) + + s.undo() + + self.assertTrue( "op1" in s["b"] ) + self.assertTrue( s["b"]["n"]["op1"].source().isSame( s["b"]["op1"] ) ) + + def testDuplicateNames( self ) : + + b = Gaffer.Box() + b["n1"] = GafferTest.AddNode() + b["n2"] = GafferTest.AddNode() + + b["i1"] = Gaffer.BoxIn() + b["i2"] = Gaffer.BoxIn() + + b["i1"].setup( b["n1"]["op1"] ) + b["i2"].setup( b["n2"]["op1"] ) + + self.assertEqual( b["i1"].promotedPlug().getName(), "in" ) + self.assertEqual( b["i1"]["name"].getValue(), "in" ) + + self.assertEqual( b["i2"].promotedPlug().getName(), "in1" ) + self.assertEqual( b["i2"]["name"].getValue(), "in1" ) + + b["i2"]["name"].setValue( "in" ) + self.assertEqual( b["i2"].promotedPlug().getName(), "in1" ) + self.assertEqual( b["i2"]["name"].getValue(), "in1" ) + + def testPaste( self ) : + + s = Gaffer.ScriptNode() + s["b1"] = Gaffer.Box() + s["b1"]["n"] = GafferTest.AddNode() + + s["b1"]["i"] = Gaffer.BoxIn() + s["b1"]["i"]["name"].setValue( "test" ) + s["b1"]["i"].setup( s["b1"]["n"]["op1"] ) + s["b1"]["n"]["op1"].setInput( s["b1"]["i"]["out"] ) + + s["b2"] = Gaffer.Box() + s.execute( + s.serialise( parent = s["b1"], filter = Gaffer.StandardSet( [ s["b1"]["n"], s["b1"]["i"] ] ) ), + parent = s["b2"], + ) + + self.assertTrue( "test" in s["b2"] ) + self.assertTrue( s["b2"]["n"]["op1"].source().isSame( s["b2"]["test"] ) ) + + def testMetadata( self ) : + + s = Gaffer.ScriptNode() + s["b1"] = Gaffer.Box() + s["b1"]["n"] = GafferTest.AddNode() + + Gaffer.Metadata.registerValue( s["b1"]["n"]["op1"], "test", "testValue" ) + Gaffer.Metadata.registerValue( s["b1"]["n"]["op1"], "layout:section", "sectionName" ) + + s["b1"]["i"] = Gaffer.BoxIn() + s["b1"]["i"].setup( s["b1"]["n"]["op1"] ) + + self.assertEqual( Gaffer.Metadata.value( s["b1"]["i"].promotedPlug(), "test" ), "testValue" ) + self.assertEqual( Gaffer.Metadata.value( s["b1"]["i"].promotedPlug(), "layout:section" ), None ) + + s["b2"] = Gaffer.Box() + s.execute( + s.serialise( parent = s["b1"], filter = Gaffer.StandardSet( [ s["b1"]["i"] ] ) ), + parent = s["b2"], + ) + + self.assertEqual( Gaffer.Metadata.value( s["b2"]["i"].promotedPlug(), "test" ), "testValue" ) + self.assertEqual( Gaffer.Metadata.value( s["b2"]["i"].promotedPlug(), "layout:section" ), None ) + + def testNoduleSectionMetadata( self ) : + + s = Gaffer.ScriptNode() + s["b"] = Gaffer.Box() + s["b"]["n"] = GafferTest.AddNode() + + Gaffer.Metadata.registerValue( s["b"]["n"]["op1"], "noduleLayout:section", "left" ) + + s["b"]["i"] = Gaffer.BoxIn() + s["b"]["i"].setup( s["b"]["n"]["op1"] ) + + self.assertEqual( Gaffer.Metadata.value( s["b"]["i"].promotedPlug(), "noduleLayout:section" ), "left" ) + self.assertEqual( Gaffer.Metadata.value( s["b"]["i"].plug(), "noduleLayout:section" ), "right" ) + +if __name__ == "__main__": + unittest.main() diff --git a/python/GafferTest/BoxOutTest.py b/python/GafferTest/BoxOutTest.py new file mode 100644 index 00000000000..4203df5e433 --- /dev/null +++ b/python/GafferTest/BoxOutTest.py @@ -0,0 +1,182 @@ +########################################################################## +# +# Copyright (c) 2017, 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 unittest + +import IECore + +import Gaffer +import GafferTest + +class BoxOutTest( GafferTest.TestCase ) : + + def testSetup( self ) : + + s = Gaffer.ScriptNode() + s["b"] = Gaffer.Box() + s["b"]["n"] = GafferTest.AddNode() + + s["b"]["o"] = Gaffer.BoxOut() + self.assertEqual( s["b"]["o"]["name"].getValue(), "out" ) + self.assertEqual( s["b"]["o"]["name"].defaultValue(), "out" ) + + s["b"]["o"]["name"].setValue( "sum" ) + s["b"]["o"].setup( s["b"]["n"]["sum"] ) + s["b"]["o"]["in"].setInput( s["b"]["n"]["sum"] ) + + self.assertTrue( "sum" in s["b"] ) + self.assertTrue( s["b"]["sum"].source().isSame( s["b"]["n"]["sum"] ) ) + + def testNameTracking( self ) : + + s = Gaffer.ScriptNode() + s["b"] = Gaffer.Box() + s["b"]["n"] = GafferTest.AddNode() + + s["b"]["o"] = Gaffer.BoxOut() + s["b"]["o"]["name"].setValue( "test" ) + s["b"]["o"].setup( s["b"]["n"]["sum"] ) + promoted = s["b"]["o"].promotedPlug() + + self.assertEqual( s["b"]["o"]["name"].getValue(), "test" ) + self.assertEqual( promoted.getName(), s["b"]["o"]["name"].getValue() ) + + with Gaffer.UndoContext( s ) : + promoted.setName( "bob" ) + + self.assertEqual( promoted.getName(), "bob" ) + self.assertEqual( s["b"]["o"]["name"].getValue(), "bob" ) + + s.undo() + + self.assertEqual( s["b"]["o"]["name"].getValue(), "test" ) + self.assertEqual( promoted.getName(), s["b"]["o"]["name"].getValue() ) + + with Gaffer.UndoContext( s ) : + s["b"]["o"]["name"].setValue( "jim" ) + + self.assertEqual( promoted.getName(), "jim" ) + self.assertEqual( s["b"]["o"]["name"].getValue(), "jim" ) + + s.undo() + + self.assertEqual( s["b"]["o"]["name"].getValue(), "test" ) + self.assertEqual( promoted.getName(), s["b"]["o"]["name"].getValue() ) + + def testDeleteRemovesPromotedPlugs( self ) : + + s = Gaffer.ScriptNode() + + s["b"] = Gaffer.Box() + s["b"]["n"] = GafferTest.AddNode() + + s["b"]["o"] = Gaffer.BoxOut() + s["b"]["o"].setup( s["b"]["n"]["sum"] ) + s["b"]["o"]["in"].setInput( s["b"]["n"]["sum"] ) + + self.assertTrue( "out" in s["b"] ) + self.assertTrue( s["b"]["out"].source().isSame( s["b"]["n"]["sum"] ) ) + + with Gaffer.UndoContext( s ) : + del s["b"]["o"] + + self.assertFalse( "out" in s["b"] ) + + s.undo() + + self.assertTrue( "out" in s["b"] ) + self.assertTrue( s["b"]["out"].source().isSame( s["b"]["n"]["sum"] ) ) + + def testPaste( self ) : + + s = Gaffer.ScriptNode() + s["b1"] = Gaffer.Box() + s["b1"]["n"] = GafferTest.AddNode() + + s["b1"]["o"] = Gaffer.BoxOut() + s["b1"]["o"]["name"].setValue( "test" ) + s["b1"]["o"].setup( s["b1"]["n"]["sum"] ) + s["b1"]["o"]["in"].setInput( s["b1"]["n"]["sum"] ) + + s["b2"] = Gaffer.Box() + s.execute( + s.serialise( parent = s["b1"], filter = Gaffer.StandardSet( [ s["b1"]["n"], s["b1"]["o"] ] ) ), + parent = s["b2"], + ) + + self.assertTrue( "test" in s["b2"] ) + self.assertTrue( s["b2"]["test"].source().isSame( s["b2"]["n"]["sum"] ) ) + + def testMetadata( self ) : + + s = Gaffer.ScriptNode() + s["b1"] = Gaffer.Box() + s["b1"]["n"] = GafferTest.AddNode() + + Gaffer.Metadata.registerValue( s["b1"]["n"]["sum"], "test", "testValue" ) + Gaffer.Metadata.registerValue( s["b1"]["n"]["sum"], "layout:section", "sectionName" ) + + s["b1"]["o"] = Gaffer.BoxIn() + s["b1"]["o"].setup( s["b1"]["n"]["sum"] ) + + self.assertEqual( Gaffer.Metadata.value( s["b1"]["o"].promotedPlug(), "test" ), "testValue" ) + self.assertEqual( Gaffer.Metadata.value( s["b1"]["o"].promotedPlug(), "layout:section" ), None ) + + s["b2"] = Gaffer.Box() + s.execute( + s.serialise( parent = s["b1"], filter = Gaffer.StandardSet( [ s["b1"]["o"] ] ) ), + parent = s["b2"], + ) + + self.assertEqual( Gaffer.Metadata.value( s["b2"]["o"].promotedPlug(), "test" ), "testValue" ) + self.assertEqual( Gaffer.Metadata.value( s["b2"]["o"].promotedPlug(), "layout:section" ), None ) + + def testNoduleSectionMetadata( self ) : + + s = Gaffer.ScriptNode() + s["b"] = Gaffer.Box() + s["b"]["n"] = GafferTest.AddNode() + + Gaffer.Metadata.registerValue( s["b"]["n"]["sum"], "noduleLayout:section", "right" ) + + s["b"]["o"] = Gaffer.BoxOut() + s["b"]["o"].setup( s["b"]["n"]["sum"] ) + + self.assertEqual( Gaffer.Metadata.value( s["b"]["o"].promotedPlug(), "noduleLayout:section" ), "right" ) + self.assertEqual( Gaffer.Metadata.value( s["b"]["o"].plug(), "noduleLayout:section" ), "left" ) + +if __name__ == "__main__": + unittest.main() diff --git a/python/GafferTest/__init__.py b/python/GafferTest/__init__.py index f4c03a776b1..471e0a6c537 100644 --- a/python/GafferTest/__init__.py +++ b/python/GafferTest/__init__.py @@ -132,6 +132,8 @@ def wrapper( self ) : from MetadataAlgoTest import MetadataAlgoTest from ContextMonitorTest import ContextMonitorTest from PlugAlgoTest import PlugAlgoTest +from BoxInTest import BoxInTest +from BoxOutTest import BoxOutTest if __name__ == "__main__": import unittest From c9dbbabd8f672498eb3edf668317849d97808e60 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 15 Mar 2017 12:03:28 +0000 Subject: [PATCH 07/25] StandardNodeGadget : Remove unused implementation detail This became irrelevant when we introduced the NoduleLayout class. --- src/GafferUI/StandardNodeGadget.cpp | 33 ----------------------------- 1 file changed, 33 deletions(-) diff --git a/src/GafferUI/StandardNodeGadget.cpp b/src/GafferUI/StandardNodeGadget.cpp index 57bee47fc9b..1916d6e18f4 100644 --- a/src/GafferUI/StandardNodeGadget.cpp +++ b/src/GafferUI/StandardNodeGadget.cpp @@ -151,39 +151,6 @@ class StandardNodeGadget::ErrorGadget : public Gadget }; -////////////////////////////////////////////////////////////////////////// -// Utilities -////////////////////////////////////////////////////////////////////////// - -namespace -{ - -/// Used for sorting nodules for layout -struct IndexAndNodule -{ - - IndexAndNodule() - : index( 0 ), nodule( NULL ) - { - } - - IndexAndNodule( int index, Nodule *nodule ) - : index( index ), nodule( nodule ) - { - } - - bool operator < ( const IndexAndNodule &rhs ) const - { - return index < rhs.index; - } - - int index; - Nodule *nodule; - -}; - -} // namespace - ////////////////////////////////////////////////////////////////////////// // StandardNodeGadget implementation ////////////////////////////////////////////////////////////////////////// From 61c3ff57a160a32d83960781851c4d70ab702a82 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 15 Mar 2017 14:28:25 +0000 Subject: [PATCH 08/25] StandardNodeGadget : Add support for icon metadata --- include/GafferUI/StandardNodeGadget.h | 8 +++ src/GafferUI/StandardNodeGadget.cpp | 74 +++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/include/GafferUI/StandardNodeGadget.h b/include/GafferUI/StandardNodeGadget.h index dad54fd8f41..1f87f4b9efe 100644 --- a/include/GafferUI/StandardNodeGadget.h +++ b/include/GafferUI/StandardNodeGadget.h @@ -57,6 +57,7 @@ class NoduleLayout; /// - "nodeGadget:minimumWidth" : a node entry with a float value /// - "nodeGadget:padding" : a node entry with a float value /// - "nodeGadget:color" : Color3f +/// - "icon" : string naming an image to be used with ImageGadget class StandardNodeGadget : public NodeGadget { @@ -114,6 +115,12 @@ class StandardNodeGadget : public NodeGadget NoduleLayout *noduleLayout( Edge edge ); const NoduleLayout *noduleLayout( Edge edge ) const; + LinearContainer *paddingRow(); + const LinearContainer *paddingRow() const; + + IndividualContainer *iconContainer(); + const IndividualContainer *iconContainer() const; + IndividualContainer *contentsContainer(); const IndividualContainer *contentsContainer() const; @@ -137,6 +144,7 @@ class StandardNodeGadget : public NodeGadget bool updateUserColor(); void updatePadding(); void updateNodeEnabled( const Gaffer::Plug *dirtiedPlug = NULL ); + void updateIcon(); IE_CORE_FORWARDDECLARE( ErrorGadget ); ErrorGadget *errorGadget( bool createIfMissing = true ); diff --git a/src/GafferUI/StandardNodeGadget.cpp b/src/GafferUI/StandardNodeGadget.cpp index 1916d6e18f4..d2fde6cd8d3 100644 --- a/src/GafferUI/StandardNodeGadget.cpp +++ b/src/GafferUI/StandardNodeGadget.cpp @@ -40,6 +40,8 @@ #include "OpenEXR/ImathBoxAlgo.h" +#include "IECore/MessageHandler.h" + #include "IECoreGL/Selector.h" #include "Gaffer/TypedObjectPlug.h" @@ -163,6 +165,7 @@ static const float g_borderWidth = 0.5f; static IECore::InternedString g_minWidthKey( "nodeGadget:minWidth" ); static IECore::InternedString g_paddingKey( "nodeGadget:padding" ); static IECore::InternedString g_colorKey( "nodeGadget:color" ); +static IECore::InternedString g_iconKey( "icon" ); static IECore::InternedString g_errorGadgetName( "__error" ); StandardNodeGadget::StandardNodeGadget( Gaffer::NodePtr node ) @@ -242,11 +245,23 @@ StandardNodeGadget::StandardNodeGadget( Gaffer::NodePtr node ) ); row->addChild( contentsColumn ); + LinearContainerPtr contentsRow = new LinearContainer( + "paddingRow", + LinearContainer::X, + LinearContainer::Centre, + 0.5f + ); + + IndividualContainerPtr iconContainer = new IndividualContainer(); + iconContainer->setName( "iconContainer" ); + contentsRow->addChild( iconContainer ); + IndividualContainerPtr contentsContainer = new IndividualContainer(); contentsContainer->setName( "contentsContainer" ); + contentsRow->addChild( contentsContainer ); contentsColumn->addChild( new SpacerGadget( Box3f( V3f( 0 ), V3f( minWidth, 0, 0 ) ) ) ); - contentsColumn->addChild( contentsContainer ); + contentsColumn->addChild( contentsRow ); contentsColumn->addChild( new SpacerGadget( Box3f( V3f( 0 ), V3f( minWidth, 0, 0 ) ) ) ); row->addChild( rightNoduleContainer ); @@ -281,6 +296,7 @@ StandardNodeGadget::StandardNodeGadget( Gaffer::NodePtr node ) updateUserColor(); updatePadding(); updateNodeEnabled(); + updateIcon(); } StandardNodeGadget::~StandardNodeGadget() @@ -409,17 +425,38 @@ const NoduleLayout *StandardNodeGadget::noduleLayout( Edge edge ) const return noduleContainer( edge )->getChild( 1 ); } -IndividualContainer *StandardNodeGadget::contentsContainer() +LinearContainer *StandardNodeGadget::paddingRow() { return getChild( 0 ) // column ->getChild( 1 ) // row ->getChild( 1 ) // contentsColumn - ->getChild( 1 ); + ->getChild( 1 ) + ; +} + +const LinearContainer *StandardNodeGadget::paddingRow() const +{ + return const_cast( this )->paddingRow(); +} + +IndividualContainer *StandardNodeGadget::iconContainer() +{ + return paddingRow()->getChild( 0 ); +} + +const IndividualContainer *StandardNodeGadget::iconContainer() const +{ + return paddingRow()->getChild( 0 ); +} + +IndividualContainer *StandardNodeGadget::contentsContainer() +{ + return paddingRow()->getChild( 1 ); } const IndividualContainer *StandardNodeGadget::contentsContainer() const { - return const_cast( this )->contentsContainer(); + return paddingRow()->getChild( 1 ); } void StandardNodeGadget::setContents( GadgetPtr contents ) @@ -697,6 +734,10 @@ void StandardNodeGadget::nodeMetadataChanged( IECore::TypeId nodeTypeId, IECore: { updatePadding(); } + else if( key == g_iconKey ) + { + updateIcon(); + } } bool StandardNodeGadget::updateUserColor() @@ -724,7 +765,7 @@ void StandardNodeGadget::updatePadding() padding = d->readable(); } - contentsContainer()->setPadding( Box3f( V3f( -padding ), V3f( padding ) ) ); + paddingRow()->setPadding( Box3f( V3f( -padding ), V3f( padding ) ) ); } void StandardNodeGadget::updateNodeEnabled( const Gaffer::Plug *dirtiedPlug ) @@ -767,6 +808,29 @@ void StandardNodeGadget::updateNodeEnabled( const Gaffer::Plug *dirtiedPlug ) requestRender(); } +void StandardNodeGadget::updateIcon() +{ + ImageGadgetPtr image; + if( IECore::ConstStringDataPtr d = Metadata::value( node(), g_iconKey ) ) + { + try + { + image = new ImageGadget( d->readable() ); + } + catch( const std::exception &e ) + { + IECore::msg( IECore::Msg::Error, "StandardNodeGadget::updateIcon", e.what() ); + } + } + + if( image ) + { + image->setTransform( M44f().scale( V3f( 1.5 ) / image->bound().size().y ) ); + } + + iconContainer()->setChild( image ); +} + StandardNodeGadget::ErrorGadget *StandardNodeGadget::errorGadget( bool createIfMissing ) { if( ErrorGadget *result = getChild( g_errorGadgetName ) ) From 202e3dc55a886de7f410e3b6ec6c5464674446bb Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 15 Mar 2017 15:26:13 +0000 Subject: [PATCH 09/25] StandardNodeGadget : Fix comment --- include/GafferUI/StandardNodeGadget.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/GafferUI/StandardNodeGadget.h b/include/GafferUI/StandardNodeGadget.h index 1f87f4b9efe..5eac3ac729b 100644 --- a/include/GafferUI/StandardNodeGadget.h +++ b/include/GafferUI/StandardNodeGadget.h @@ -54,7 +54,7 @@ class NoduleLayout; /// centrally and the nodules arranged at the sides. Supports the following /// Metadata entries : /// -/// - "nodeGadget:minimumWidth" : a node entry with a float value +/// - "nodeGadget:minWidth" : a node entry with a float value /// - "nodeGadget:padding" : a node entry with a float value /// - "nodeGadget:color" : Color3f /// - "icon" : string naming an image to be used with ImageGadget From d192872c4577719039bb5826bc891f4d2176d830 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 15 Mar 2017 15:37:33 +0000 Subject: [PATCH 10/25] StandardNodeGadget : Support "nodeGadget:shape" metadata Valid values are "rectangle" (previous behaviour) and "oval". --- include/GafferUI/StandardNodeGadget.h | 3 ++ src/GafferUI/StandardNodeGadget.cpp | 40 ++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/include/GafferUI/StandardNodeGadget.h b/include/GafferUI/StandardNodeGadget.h index 5eac3ac729b..2f1a2ba8f2e 100644 --- a/include/GafferUI/StandardNodeGadget.h +++ b/include/GafferUI/StandardNodeGadget.h @@ -57,6 +57,7 @@ class NoduleLayout; /// - "nodeGadget:minWidth" : a node entry with a float value /// - "nodeGadget:padding" : a node entry with a float value /// - "nodeGadget:color" : Color3f +/// - "nodeGadget:shape" : StringData containing "rectangle" or "oval" /// - "icon" : string naming an image to be used with ImageGadget class StandardNodeGadget : public NodeGadget { @@ -145,6 +146,7 @@ class StandardNodeGadget : public NodeGadget void updatePadding(); void updateNodeEnabled( const Gaffer::Plug *dirtiedPlug = NULL ); void updateIcon(); + bool updateShape(); IE_CORE_FORWARDDECLARE( ErrorGadget ); ErrorGadget *errorGadget( bool createIfMissing = true ); @@ -160,6 +162,7 @@ class StandardNodeGadget : public NodeGadget // to hit. Gadget *m_dragDestinationProxy; boost::optional m_userColor; + bool m_oval; }; diff --git a/src/GafferUI/StandardNodeGadget.cpp b/src/GafferUI/StandardNodeGadget.cpp index d2fde6cd8d3..5c34a24ba51 100644 --- a/src/GafferUI/StandardNodeGadget.cpp +++ b/src/GafferUI/StandardNodeGadget.cpp @@ -165,6 +165,7 @@ static const float g_borderWidth = 0.5f; static IECore::InternedString g_minWidthKey( "nodeGadget:minWidth" ); static IECore::InternedString g_paddingKey( "nodeGadget:padding" ); static IECore::InternedString g_colorKey( "nodeGadget:color" ); +static IECore::InternedString g_shapeKey( "nodeGadget:shape" ); static IECore::InternedString g_iconKey( "icon" ); static IECore::InternedString g_errorGadgetName( "__error" ); @@ -173,7 +174,8 @@ StandardNodeGadget::StandardNodeGadget( Gaffer::NodePtr node ) m_nodeEnabled( true ), m_labelsVisibleOnHover( true ), m_dragDestinationProxy( 0 ), - m_userColor( 0 ) + m_userColor( 0 ), + m_oval( false ) { // build our ui structure @@ -297,6 +299,7 @@ StandardNodeGadget::StandardNodeGadget( Gaffer::NodePtr node ) updatePadding(); updateNodeEnabled(); updateIcon(); + updateShape(); } StandardNodeGadget::~StandardNodeGadget() @@ -322,10 +325,17 @@ void StandardNodeGadget::doRender( const Style *style ) const Style::State state = getHighlighted() ? Style::HighlightedState : Style::NormalState; // draw our background frame - Box3f b = bound(); + const Box3f b = bound(); + float borderWidth = g_borderWidth; + if( m_oval ) + { + const V3f s = b.size(); + borderWidth = std::min( s.x, s.y ) / 2.0f; + } + style->renderNodeFrame( - Box2f( V2f( b.min.x, b.min.y ) + V2f( g_borderWidth ), V2f( b.max.x, b.max.y ) - V2f( g_borderWidth ) ), - g_borderWidth, + Box2f( V2f( b.min.x, b.min.y ) + V2f( borderWidth ), V2f( b.max.x, b.max.y ) - V2f( borderWidth ) ), + borderWidth, state, m_userColor.get_ptr() ); @@ -738,6 +748,13 @@ void StandardNodeGadget::nodeMetadataChanged( IECore::TypeId nodeTypeId, IECore: { updateIcon(); } + else if( key == g_shapeKey ) + { + if( updateShape() ) + { + requestRender(); + } + } } bool StandardNodeGadget::updateUserColor() @@ -831,6 +848,21 @@ void StandardNodeGadget::updateIcon() iconContainer()->setChild( image ); } +bool StandardNodeGadget::updateShape() +{ + bool oval = false; + if( IECore::ConstStringDataPtr s = Metadata::value( node(), g_shapeKey ) ) + { + oval = s->readable() == "oval"; + } + if( oval == m_oval ) + { + return false; + } + m_oval = oval; + return true; +} + StandardNodeGadget::ErrorGadget *StandardNodeGadget::errorGadget( bool createIfMissing ) { if( ErrorGadget *result = getChild( g_errorGadgetName ) ) From a0d6f1018d538c58da23219334c6b9e2b726a816 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 15 Mar 2017 15:39:25 +0000 Subject: [PATCH 11/25] Graphics : Add icons for BoxIO nodes --- resources/graphics.svg | 88 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 10 deletions(-) diff --git a/resources/graphics.svg b/resources/graphics.svg index 3096a756d70..db8b38c767c 100644 --- a/resources/graphics.svg +++ b/resources/graphics.svg @@ -120,6 +120,20 @@ style="fill:#787878;fill-opacity:1;fill-rule:evenodd;stroke:#3c3c3c;stroke-width:4.66092873;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" sodipodi:type="arc" /> + + + + + + + + + + + + + From 3b3b4b0000bb8065dd7debca1ff58ff7f4065c06 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 8 Mar 2017 17:23:03 +0000 Subject: [PATCH 12/25] GafferUI : Add UI files for BoxIO nodes --- python/GafferUI/BoxIOUI.py | 85 +++++++++++++++++++++++++++++++++++++ python/GafferUI/BoxInUI.py | 52 +++++++++++++++++++++++ python/GafferUI/BoxOutUI.py | 52 +++++++++++++++++++++++ python/GafferUI/__init__.py | 3 ++ 4 files changed, 192 insertions(+) create mode 100644 python/GafferUI/BoxIOUI.py create mode 100644 python/GafferUI/BoxInUI.py create mode 100644 python/GafferUI/BoxOutUI.py diff --git a/python/GafferUI/BoxIOUI.py b/python/GafferUI/BoxIOUI.py new file mode 100644 index 00000000000..85bf06864a2 --- /dev/null +++ b/python/GafferUI/BoxIOUI.py @@ -0,0 +1,85 @@ +########################################################################## +# +# Copyright (c) 2017, 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 IECore + +import Gaffer + +Gaffer.Metadata.registerNode( + + Gaffer.BoxIO, + + "description", + """ + Convenience node for representing promoted plugs + visually in the internal node graph of a Box. Don't + create BoxIO nodes directly, instead use the BoxIn + and BoxOut derived classes. + """, + + "nodeGadget:minWidth", 0.0, + "nodeGadget:shape", "oval", + + plugs = { + + "name" : [ + + "description", + """ + The name given to the external plug that + this node represents. + """, + + "nodule:type", "" + + ], + + "in" : [ + + "plugValueWidget:type", "", + + ], + + "out" : [ + + "plugValueWidget:type", "", + + ], + + } + +) + diff --git a/python/GafferUI/BoxInUI.py b/python/GafferUI/BoxInUI.py new file mode 100644 index 00000000000..7d7c8cd9750 --- /dev/null +++ b/python/GafferUI/BoxInUI.py @@ -0,0 +1,52 @@ +########################################################################## +# +# Copyright (c) 2017, 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 + +Gaffer.Metadata.registerNode( + + Gaffer.BoxIn, + + "description", + """ + Convenience node for representing input plugs + visually in the internal node graph of a Box. + """, + + "icon", "boxInNode.png", + +) + diff --git a/python/GafferUI/BoxOutUI.py b/python/GafferUI/BoxOutUI.py new file mode 100644 index 00000000000..f92ad78e6c5 --- /dev/null +++ b/python/GafferUI/BoxOutUI.py @@ -0,0 +1,52 @@ +########################################################################## +# +# Copyright (c) 2017, 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 + +Gaffer.Metadata.registerNode( + + Gaffer.BoxOut, + + "description", + """ + Convenience node for representing output plugs + visually in the internal node graph of a Box. + """, + + "icon", "boxOutNode.png", + +) + diff --git a/python/GafferUI/__init__.py b/python/GafferUI/__init__.py index a939e0384f2..1b0dfecebe2 100644 --- a/python/GafferUI/__init__.py +++ b/python/GafferUI/__init__.py @@ -290,6 +290,9 @@ def _qtObject( address, type ) : import TimeWarpUI import LoopUI import AnimationUI +import BoxIOUI +import BoxInUI +import BoxOutUI # backwards compatibility ## \todo Remove me From 43269ada65a207fb7e7c432a58b9f2fad3ffb4dd Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 8 Mar 2017 16:54:13 +0000 Subject: [PATCH 13/25] GafferUI : Add custom node gadget for BoxIO This uses PlugAdders to allow the user to set up the node with an appropriate plug, and replaces the usual node name label with a label showing the name of the promoted plug. --- src/GafferUI/BoxIONodeGadget.cpp | 200 +++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 src/GafferUI/BoxIONodeGadget.cpp diff --git a/src/GafferUI/BoxIONodeGadget.cpp b/src/GafferUI/BoxIONodeGadget.cpp new file mode 100644 index 00000000000..2f4749fa507 --- /dev/null +++ b/src/GafferUI/BoxIONodeGadget.cpp @@ -0,0 +1,200 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2017, 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 "boost/bind.hpp" +#include "boost/algorithm/string/replace.hpp" + +#include "Gaffer/BoxIO.h" +#include "Gaffer/UndoScope.h" +#include "Gaffer/ScriptNode.h" +#include "Gaffer/Metadata.h" +#include "Gaffer/StringPlug.h" + +#include "GafferUI/StandardNodeGadget.h" +#include "GafferUI/PlugAdder.h" +#include "GafferUI/SpacerGadget.h" +#include "GafferUI/TextGadget.h" + +using namespace IECore; +using namespace Gaffer; +using namespace GafferUI; + +////////////////////////////////////////////////////////////////////////// +// PlugAdder +////////////////////////////////////////////////////////////////////////// + +namespace +{ + +class BoxIOPlugAdder : public PlugAdder +{ + + public : + + BoxIOPlugAdder( BoxIOPtr boxIO, StandardNodeGadget::Edge edge ) + : PlugAdder( edge ), m_boxIO( boxIO ) + { + m_boxIO->childAddedSignal().connect( boost::bind( &BoxIOPlugAdder::childAdded, this ) ); + m_boxIO->childRemovedSignal().connect( boost::bind( &BoxIOPlugAdder::childRemoved, this ) ); + updateVisibility(); + } + + protected : + + virtual bool acceptsPlug( const Plug *connectionEndPoint ) const + { + return connectionEndPoint->direction() == m_boxIO->direction(); + } + + virtual void addPlug( Plug *connectionEndPoint ) + { + UndoScope undoScope( m_boxIO->ancestor() ); + + std::string name = connectionEndPoint->relativeName( connectionEndPoint->node() ); + boost::replace_all( name, ".", "_" ); + m_boxIO->namePlug()->setValue( name ); + + m_boxIO->setup( connectionEndPoint ); + + applyEdgeMetadata( m_boxIO->plug() ); + if( m_boxIO->promotedPlug() ) + { + applyEdgeMetadata( m_boxIO->promotedPlug(), /* opposite = */ true ); + } + + if( m_boxIO->direction() == Plug::In ) + { + connectionEndPoint->setInput( m_boxIO->plug() ); + } + else + { + m_boxIO->plug()->setInput( connectionEndPoint ); + } + } + + private : + + void childAdded() + { + updateVisibility(); + } + + void childRemoved() + { + updateVisibility(); + } + + void updateVisibility() + { + setVisible( !m_boxIO->plug() ); + } + + BoxIOPtr m_boxIO; + +}; + +} // namespace + +////////////////////////////////////////////////////////////////////////// +// StringPlugValueGadget +////////////////////////////////////////////////////////////////////////// + +namespace +{ + +class NameGadget : public TextGadget +{ + + public : + + NameGadget( BoxIOPtr boxIO ) + : TextGadget( boxIO->namePlug()->getValue() ), m_boxIO( boxIO ) + { + boxIO->plugSetSignal().connect( boost::bind( &NameGadget::plugSet, this, ::_1 ) ); + } + + private : + + void plugSet( const Plug *plug ) + { + if( plug == m_boxIO->namePlug() ) + { + setText( m_boxIO->namePlug()->getValue() ); + } + } + + BoxIOPtr m_boxIO; + +}; + +} // namespace + +////////////////////////////////////////////////////////////////////////// +// NodeGadget +////////////////////////////////////////////////////////////////////////// + +namespace +{ + +struct BoxIONodeGadgetCreator +{ + + BoxIONodeGadgetCreator() + { + NodeGadget::registerNodeGadget( BoxIO::staticTypeId(), *this ); + } + + NodeGadgetPtr operator()( NodePtr node ) + { + BoxIOPtr boxIO = runTimeCast( node ); + if( !boxIO ) + { + throw Exception( "Expected a BoxIO node" ); + } + StandardNodeGadgetPtr result = new StandardNodeGadget( node ); + result->setEdgeGadget( StandardNodeGadget::LeftEdge, new BoxIOPlugAdder( boxIO, StandardNodeGadget::LeftEdge ) ); + result->setEdgeGadget( StandardNodeGadget::RightEdge, new BoxIOPlugAdder( boxIO, StandardNodeGadget::RightEdge ) ); + result->setEdgeGadget( StandardNodeGadget::BottomEdge, new BoxIOPlugAdder( boxIO, StandardNodeGadget::BottomEdge ) ); + result->setEdgeGadget( StandardNodeGadget::TopEdge, new BoxIOPlugAdder( boxIO, StandardNodeGadget::TopEdge ) ); + result->setContents( new NameGadget( boxIO ) ); + return result; + } + +}; + +BoxIONodeGadgetCreator g_boxIONodeGadgetCreator; + +} // namespace From d23f8988e99b5aad8b2818f0d222c25a7793b83e Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 8 Mar 2017 17:16:51 +0000 Subject: [PATCH 14/25] BoxUI : Add a PlugAdder on each edge This create internal BoxIO nodes to represent the external plugs. --- python/GafferUI/BoxUI.py | 10 +++ src/GafferUI/BoxUI.cpp | 136 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 src/GafferUI/BoxUI.cpp diff --git a/python/GafferUI/BoxUI.py b/python/GafferUI/BoxUI.py index 2bb7223a3fa..249212bdc47 100644 --- a/python/GafferUI/BoxUI.py +++ b/python/GafferUI/BoxUI.py @@ -66,6 +66,16 @@ "layout:customWidget:addButton:section", "Settings", "layout:customWidget:addButton:index", -2, + # Add + buttons for creating new plugs in the NodeGraph + "noduleLayout:customGadget:addButtonTop:gadgetType", "GafferUI.BoxUI.PlugAdder.Top", + "noduleLayout:customGadget:addButtonTop:section", "top", + "noduleLayout:customGadget:addButtonBottom:gadgetType", "GafferUI.BoxUI.PlugAdder.Bottom", + "noduleLayout:customGadget:addButtonBottom:section", "bottom", + "noduleLayout:customGadget:addButtonLeft:gadgetType", "GafferUI.BoxUI.PlugAdder.Left", + "noduleLayout:customGadget:addButtonLeft:section", "left", + "noduleLayout:customGadget:addButtonRight:gadgetType", "GafferUI.BoxUI.PlugAdder.Right", + "noduleLayout:customGadget:addButtonRight:section", "right", + plugs = { "*" : [ diff --git a/src/GafferUI/BoxUI.cpp b/src/GafferUI/BoxUI.cpp new file mode 100644 index 00000000000..5058b7b5be8 --- /dev/null +++ b/src/GafferUI/BoxUI.cpp @@ -0,0 +1,136 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2017, 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 "boost/bind.hpp" + +#include "Gaffer/Box.h" +#include "Gaffer/BoxIn.h" +#include "Gaffer/BoxOut.h" +#include "Gaffer/UndoScope.h" +#include "Gaffer/ScriptNode.h" + +#include "GafferUI/StandardNodeGadget.h" +#include "GafferUI/PlugAdder.h" +#include "GafferUI/NoduleLayout.h" + +using namespace IECore; +using namespace Gaffer; +using namespace GafferUI; + +namespace +{ + +class BoxPlugAdder : public PlugAdder +{ + + public : + + BoxPlugAdder( BoxPtr box, StandardNodeGadget::Edge edge ) + : PlugAdder( edge ), m_box( box ) + { + } + + protected : + + virtual bool acceptsPlug( const Plug *connectionEndPoint ) const + { + return true; + } + + virtual void addPlug( Plug *connectionEndPoint ) + { + UndoScope undoScope( m_box->ancestor() ); + + BoxIOPtr boxIO; + if( connectionEndPoint->direction() == Plug::In ) + { + boxIO = new BoxOut; + } + else + { + boxIO = new BoxIn; + } + + m_box->addChild( boxIO ); + boxIO->setup( connectionEndPoint ); + + if( connectionEndPoint->direction() == Plug::In ) + { + connectionEndPoint->setInput( boxIO->promotedPlug() ); + } + else + { + boxIO->promotedPlug()->setInput( connectionEndPoint ); + } + + applyEdgeMetadata( boxIO->promotedPlug() ); + applyEdgeMetadata( boxIO->plug(), /* opposite = */ true ); + } + + private : + + BoxPtr m_box; + +}; + +struct Registration +{ + + Registration() + { + NoduleLayout::registerCustomGadget( "GafferUI.BoxUI.PlugAdder.Top", boost::bind( &create, ::_1, StandardNodeGadget::TopEdge ) ); + NoduleLayout::registerCustomGadget( "GafferUI.BoxUI.PlugAdder.Bottom", boost::bind( &create, ::_1, StandardNodeGadget::BottomEdge ) ); + NoduleLayout::registerCustomGadget( "GafferUI.BoxUI.PlugAdder.Left", boost::bind( &create, ::_1, StandardNodeGadget::LeftEdge ) ); + NoduleLayout::registerCustomGadget( "GafferUI.BoxUI.PlugAdder.Right", boost::bind( &create, ::_1, StandardNodeGadget::RightEdge ) ); + } + + private : + + static GadgetPtr create( GraphComponentPtr parent, StandardNodeGadget::Edge edge ) + { + if( BoxPtr box = runTimeCast( parent ) ) + { + return new BoxPlugAdder( box, edge ); + } + throw IECore::Exception( "Expected a Box" ); + } + +}; + +Registration g_registration; + +} // namespace + From f079e5a2d93c6458446873f42cfb5fa83dc4ca4a Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 8 Mar 2017 16:52:37 +0000 Subject: [PATCH 15/25] StandardGraphLayout : Support auto-connect of BoxOut nodes --- src/GafferUI/StandardGraphLayout.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/GafferUI/StandardGraphLayout.cpp b/src/GafferUI/StandardGraphLayout.cpp index 336ab01c3b7..79fe0f8eb23 100644 --- a/src/GafferUI/StandardGraphLayout.cpp +++ b/src/GafferUI/StandardGraphLayout.cpp @@ -50,6 +50,7 @@ #include "Gaffer/StandardSet.h" #include "Gaffer/Dot.h" #include "Gaffer/Switch.h" +#include "Gaffer/BoxOut.h" #include "GafferUI/StandardGraphLayout.h" #include "GafferUI/GraphGadget.h" @@ -1046,7 +1047,8 @@ bool StandardGraphLayout::connectNodeInternal( GraphGadget *graph, Gaffer::Node return false; } - // if we're trying to connect a dot or switch, then we may need to give it plugs first + // If we're trying to connect a Dot, Switch or BoxOut, then we may need + // to give it plugs first. if( Dot *dot = runTimeCast( node ) ) { if( !dot->inPlug() ) @@ -1061,6 +1063,13 @@ bool StandardGraphLayout::connectNodeInternal( GraphGadget *graph, Gaffer::Node switchNode->setup( outputPlugs.front() ); } } + else if( BoxOut *boxOut = runTimeCast( node ) ) + { + if( !boxOut->plug() ) + { + boxOut->setup( outputPlugs.front() ); + } + } // iterate over the output plugs, connecting them in to the node if we can From fddc229f1576fcd1cc894333fd3d0bd1311e99b6 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 15 Mar 2017 15:39:13 +0000 Subject: [PATCH 16/25] GUI config : Add colour for BoxIO nodes --- startup/gui/nodeGraph.py | 1 + 1 file changed, 1 insertion(+) diff --git a/startup/gui/nodeGraph.py b/startup/gui/nodeGraph.py index 0e2e6330d20..90d6a9c63e9 100644 --- a/startup/gui/nodeGraph.py +++ b/startup/gui/nodeGraph.py @@ -57,6 +57,7 @@ Gaffer.Metadata.registerPlugValue( GafferDispatch.TaskNode, "postTasks.*", "connectionGadget:color", IECore.Color3f( 0.315, 0.0787, 0.0787 ) ) Gaffer.Metadata.registerNodeValue( Gaffer.SubGraph, "nodeGadget:color", IECore.Color3f( 0.225 ) ) +Gaffer.Metadata.registerNodeValue( Gaffer.BoxIO, "nodeGadget:color", IECore.Color3f( 0.225 ) ) Gaffer.Metadata.registerPlugValue( GafferScene.SceneNode, "in*", "nodule:color", IECore.Color3f( 0.2401, 0.3394, 0.485 ) ) Gaffer.Metadata.registerPlugValue( GafferScene.SceneNode, "out", "nodule:color", IECore.Color3f( 0.2401, 0.3394, 0.485 ) ) From be1e4d12c519453f208b8ea555cb5142427852e6 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 8 Mar 2017 17:09:12 +0000 Subject: [PATCH 17/25] Node menu : Add BoxIn/BoxOut nodes --- startup/gui/menus.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/startup/gui/menus.py b/startup/gui/menus.py index d4a2d4a5640..c7f22b33efb 100644 --- a/startup/gui/menus.py +++ b/startup/gui/menus.py @@ -396,6 +396,8 @@ def __shaderNodeCreator( nodeName, shaderName ) : nodeMenu.append( "/Utility/Node", Gaffer.Node ) nodeMenu.append( "/Utility/Random", Gaffer.Random ) nodeMenu.append( "/Utility/Box", GafferUI.BoxUI.nodeMenuCreateCommand ) +nodeMenu.append( "/Utility/BoxIn", Gaffer.BoxIn ) +nodeMenu.append( "/Utility/BoxOut", Gaffer.BoxOut ) nodeMenu.append( "/Utility/Reference", GafferUI.ReferenceUI.nodeMenuCreateCommand ) nodeMenu.definition().append( "/Utility/Backdrop", { "command" : GafferUI.BackdropUI.nodeMenuCreateCommand } ) nodeMenu.append( "/Utility/Dot", Gaffer.Dot ) From 40295c8d33d1b3e09e48b6457f81ce96b47f51d0 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Fri, 31 Mar 2017 15:38:07 +0100 Subject: [PATCH 18/25] BoxIO : Delete automatically when promoted plugs are deleted --- include/Gaffer/BoxIO.h | 6 ++- python/GafferTest/BoxInTest.py | 82 +++++++++++++++++++++++++++++++++ python/GafferTest/BoxOutTest.py | 40 ++++++++++++++++ src/Gaffer/BoxIO.cpp | 59 +++++++++++++++++++----- 4 files changed, 174 insertions(+), 13 deletions(-) diff --git a/include/Gaffer/BoxIO.h b/include/Gaffer/BoxIO.h index a973c29e213..43bd61e1ea1 100644 --- a/include/Gaffer/BoxIO.h +++ b/include/Gaffer/BoxIO.h @@ -120,12 +120,14 @@ class BoxIO : public Node Plug::Direction m_direction; - boost::signals::scoped_connection m_nameChangedConnection; + boost::signals::scoped_connection m_promotedPlugNameChangedConnection; + boost::signals::scoped_connection m_promotedPlugParentChangedConnection; void plugSet( Plug *plug ); void parentChanged( GraphComponent *oldParent ); void plugInputChanged( Plug *plug ); - void nameChanged( GraphComponent *graphComponent ); + void promotedPlugNameChanged( GraphComponent *graphComponent ); + void promotedPlugParentChanged( GraphComponent *graphComponent ); static size_t g_firstPlugIndex; diff --git a/python/GafferTest/BoxInTest.py b/python/GafferTest/BoxInTest.py index a3fd9101789..30514e45553 100644 --- a/python/GafferTest/BoxInTest.py +++ b/python/GafferTest/BoxInTest.py @@ -219,5 +219,87 @@ def testNoduleSectionMetadata( self ) : self.assertEqual( Gaffer.Metadata.value( s["b"]["i"].promotedPlug(), "noduleLayout:section" ), "left" ) self.assertEqual( Gaffer.Metadata.value( s["b"]["i"].plug(), "noduleLayout:section" ), "right" ) + def testPromotedPlugRemovalDeletesBoxIn( self ) : + + s = Gaffer.ScriptNode() + + s["b"] = Gaffer.Box() + s["b"]["n"] = GafferTest.AddNode() + + s["b"]["i"] = Gaffer.BoxIn() + s["b"]["i"]["name"].setValue( "op1" ) + s["b"]["i"].setup( s["b"]["n"]["op1"] ) + s["b"]["n"]["op1"].setInput( s["b"]["i"]["out"] ) + + def assertPreconditions() : + + self.assertTrue( "op1" in s["b"] ) + self.assertTrue( "i" in s["b"] ) + self.assertTrue( len( s["b"]["i"]["out"].outputs() ), 1 ) + self.assertTrue( s["b"]["n"]["op1"].source().isSame( s["b"]["op1"] ) ) + + assertPreconditions() + + with Gaffer.UndoContext( s ) : + del s["b"]["op1"] + + def assertPostconditions() : + + self.assertFalse( "op1" in s["b"] ) + self.assertFalse( "i" in s["b"] ) + self.assertTrue( s["b"]["n"]["op1"].getInput() is None ) + + assertPostconditions() + + s.undo() + + assertPreconditions() + + s.redo() + + assertPostconditions() + + def testUndoCreation( self ) : + + s = Gaffer.ScriptNode() + + s["b"] = Gaffer.Box() + s["a"] = GafferTest.AddNode() + + def assertPreconditions() : + + self.assertEqual( len( s["b"].children( Gaffer.Node ) ), 0 ) + self.assertEqual( len( s["b"].children( Gaffer.Plug ) ), 1 ) + self.assertEqual( len( s["a"]["sum"].outputs() ), 0 ) + + assertPreconditions() + + with Gaffer.UndoContext( s ) : + + s["b"]["i"] = Gaffer.BoxIn() + s["b"]["i"].setup( s["a"]["sum"] ) + s["b"]["i"].promotedPlug().setInput( s["a"]["sum"] ) + + def assertPostconditions() : + + self.assertEqual( len( s["b"].children( Gaffer.Node ) ), 1 ) + self.assertEqual( len( s["b"].children( Gaffer.Plug ) ), 2 ) + self.assertTrue( isinstance( s["b"]["i"], Gaffer.BoxIn ) ) + self.assertTrue( s["b"]["i"]["out"].source().isSame( s["a"]["sum"] ) ) + + assertPostconditions() + + s.undo() + assertPreconditions() + + s.redo() + assertPostconditions() + + s.undo() + assertPreconditions() + + s.redo() + assertPostconditions() + if __name__ == "__main__": unittest.main() diff --git a/python/GafferTest/BoxOutTest.py b/python/GafferTest/BoxOutTest.py index 4203df5e433..3bc56c44dd8 100644 --- a/python/GafferTest/BoxOutTest.py +++ b/python/GafferTest/BoxOutTest.py @@ -178,5 +178,45 @@ def testNoduleSectionMetadata( self ) : self.assertEqual( Gaffer.Metadata.value( s["b"]["o"].promotedPlug(), "noduleLayout:section" ), "right" ) self.assertEqual( Gaffer.Metadata.value( s["b"]["o"].plug(), "noduleLayout:section" ), "left" ) + def testPromotedPlugRemovalDeletesBoxOut( self ) : + + s = Gaffer.ScriptNode() + + s["b"] = Gaffer.Box() + s["b"]["n"] = GafferTest.AddNode() + + s["b"]["o"] = Gaffer.BoxOut() + s["b"]["o"]["name"].setValue( "sum" ) + s["b"]["o"].setup( s["b"]["n"]["sum"] ) + s["b"]["o"]["in"].setInput( s["b"]["n"]["sum"] ) + + def assertPreconditions() : + + self.assertTrue( "sum" in s["b"] ) + self.assertTrue( "o" in s["b"] ) + self.assertTrue( s["b"]["o"]["in"].getInput().isSame( s["b"]["n"]["sum"] ) ) + self.assertTrue( s["b"]["sum"].source().isSame( s["b"]["n"]["sum"] ) ) + + assertPreconditions() + + with Gaffer.UndoContext( s ) : + del s["b"]["sum"] + + def assertPostconditions() : + + self.assertFalse( "sum" in s["b"] ) + self.assertFalse( "o" in s["b"] ) + self.assertEqual( len( s["b"]["n"]["sum"].outputs() ), 0 ) + + assertPostconditions() + + s.undo() + + assertPreconditions() + + s.redo() + + assertPostconditions() + if __name__ == "__main__": unittest.main() diff --git a/src/Gaffer/BoxIO.cpp b/src/Gaffer/BoxIO.cpp index a0f4491aa73..2b339faa597 100644 --- a/src/Gaffer/BoxIO.cpp +++ b/src/Gaffer/BoxIO.cpp @@ -42,6 +42,7 @@ #include "Gaffer/Metadata.h" #include "Gaffer/MetadataAlgo.h" #include "Gaffer/PlugAlgo.h" +#include "Gaffer/ScriptNode.h" using namespace IECore; using namespace Gaffer; @@ -226,6 +227,8 @@ void BoxIO::parentChanging( Gaffer::GraphComponent *newParent ) if( parent() ) { + m_promotedPlugNameChangedConnection.disconnect(); + m_promotedPlugParentChangedConnection.disconnect(); if( Plug *i = inPlugInternal() ) { if( PlugAlgo::isPromoted( i ) ) @@ -291,29 +294,63 @@ void BoxIO::parentChanged( GraphComponent *oldParent ) void BoxIO::plugInputChanged( Plug *plug ) { + // An input has changed either on this node or on + // the parent box node. This gives us the opportunity + // to discover our promoted plug and connect to its + // signals. + Plug *promoted = NULL; if( m_direction == Plug::In && plug == inPlugInternal() ) { - m_nameChangedConnection.disconnect(); - if( Plug *p = promotedPlug() ) - { - m_nameChangedConnection = p->nameChangedSignal().connect( - boost::bind( &BoxIO::nameChanged, this, ::_1 ) - ); - } + promoted = promotedPlug(); } else if( m_direction == Plug::Out && plug == promotedPlug() ) { - m_nameChangedConnection.disconnect(); - m_nameChangedConnection = plug->nameChangedSignal().connect( - boost::bind( &BoxIO::nameChanged, this, ::_1 ) + promoted = plug; + } + + if( promoted ) + { + m_promotedPlugNameChangedConnection = promoted->nameChangedSignal().connect( + boost::bind( &BoxIO::promotedPlugNameChanged, this, ::_1 ) + ); + m_promotedPlugParentChangedConnection = promoted->parentChangedSignal().connect( + boost::bind( &BoxIO::promotedPlugParentChanged, this, ::_1 ) ); } } -void BoxIO::nameChanged( GraphComponent *graphComponent ) +void BoxIO::promotedPlugNameChanged( GraphComponent *graphComponent ) { if( graphComponent == promotedPlug() ) { namePlug()->setValue( graphComponent->getName() ); } } + +void BoxIO::promotedPlugParentChanged( GraphComponent *graphComponent ) +{ + // Promoted plug is being deleted. Since we exist only + // to represent it as a node inside the box, delete + // ourselves too. + if( const ScriptNode *script = scriptNode() ) + { + if( script->currentActionStage() == Action::Undo || + script->currentActionStage() == Action::Redo + ) + { + // We don't need to do anything during undo/redo + // since in those cases our previous actions are + // already recorded. + return; + } + } + + if( !graphComponent->parent() ) + { + if( GraphComponent *p = parent() ) + { + p->removeChild( this ); + } + } +} + From 3d1893fe16b4704c82613665327f01dcb598d4f4 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Fri, 31 Mar 2017 15:56:42 +0100 Subject: [PATCH 19/25] Box : Ignore BoxIO nodes in `create()` --- python/GafferTest/BoxTest.py | 35 +++++++++++++++++++++++++++++++++++ src/Gaffer/Box.cpp | 15 +++++++++++---- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/python/GafferTest/BoxTest.py b/python/GafferTest/BoxTest.py index b009c54247f..7b59330c366 100644 --- a/python/GafferTest/BoxTest.py +++ b/python/GafferTest/BoxTest.py @@ -1007,5 +1007,40 @@ def testPromotionIncludesArbitraryChildMetadata( self ) : self.assertEqual( Gaffer.Metadata.value( s2["b"]["p"], "testInt" ), 10 ) self.assertEqual( Gaffer.Metadata.value( s2["b"]["p"]["i"], "testString" ), "test" ) + def testCreateWithBoxIOInSelection( self ) : + + s = Gaffer.ScriptNode() + + # Make a Box containing BoxIn -> n -> BoxOut + + s["b"] = Gaffer.Box() + s["b"]["i"] = Gaffer.BoxIn() + s["b"]["n"] = GafferTest.AddNode() + s["b"]["o"] = Gaffer.BoxOut() + + s["b"]["i"]["name"].setValue( "op1" ) + s["b"]["i"].setup( s["b"]["n"]["op1"] ) + s["b"]["n"]["op1"].setInput( s["b"]["i"]["out"] ) + + s["b"]["o"]["name"].setValue( "sum" ) + s["b"]["o"].setup( s["b"]["n"]["sum"] ) + s["b"]["o"]["in"].setInput( s["b"]["n"]["sum"] ) + + # Ask to move all that (including the BoxIOs) into a + # nested Box. This doesn't really make sense, because + # the BoxIOs exist purely to build a bridge to the + # outer parent. So we expect them to remain where they + # were. + + innerBox = Gaffer.Box.create( s["b"], Gaffer.StandardSet( s["b"].children( Gaffer.Node ) ) ) + + self.assertEqual( len( innerBox.children( Gaffer.Node ) ), 1 ) + self.assertTrue( "n" in innerBox ) + self.assertFalse( "n" in s["b"] ) + self.assertTrue( "i" in s["b"] ) + self.assertTrue( "o" in s["b"] ) + self.assertTrue( s["b"]["sum"].source().isSame( innerBox["n"]["sum"] ) ) + self.assertTrue( innerBox["n"]["op1"].source().isSame( s["b"]["op1"] ) ) + if __name__ == "__main__": unittest.main() diff --git a/src/Gaffer/Box.cpp b/src/Gaffer/Box.cpp index 83ca6fab758..53c06bc7433 100644 --- a/src/Gaffer/Box.cpp +++ b/src/Gaffer/Box.cpp @@ -37,6 +37,7 @@ #include "boost/regex.hpp" #include "Gaffer/Box.h" +#include "Gaffer/BoxIO.h" #include "Gaffer/StandardSet.h" #include "Gaffer/NumericPlug.h" #include "Gaffer/ScriptNode.h" @@ -140,15 +141,21 @@ BoxPtr Box::create( Node *parent, const Set *childNodes ) // It's pretty natural to call this function passing childNodes == ScriptNode::selection(). // unfortunately nodes will be removed from the selection as we reparent // them, so we have to make a copy of childNodes so our iteration isn't befuddled by - // the changing contents. we can use this opportunity to weed out anything in childNodes - // which isn't a direct child of parent though. + // the changing contents. We can use this opportunity to weed out anything in childNodes + // which isn't a direct child of parent though, and also to skip over BoxIO nodes + // which should remain where they are. StandardSetPtr verifiedChildNodes = new StandardSet(); for( NodeIterator nodeIt( parent ); !nodeIt.done(); ++nodeIt ) { - if( childNodes->contains( nodeIt->get() ) ) + if( !childNodes->contains( nodeIt->get() ) ) { - verifiedChildNodes->add( *nodeIt ); + continue; } + if( IECore::runTimeCast( *nodeIt ) ) + { + continue; + } + verifiedChildNodes->add( *nodeIt ); } // When a node we're putting in the box has connections to From 86b293d4b24e94e4e5156d07cbc5b73fb31b7014 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Thu, 13 Apr 2017 16:49:04 +0100 Subject: [PATCH 20/25] BoxIO : Add static utility methods These can be used to promote plugs via BoxIO, and to upgrade old Boxes which were created prior to the BoxIO era. --- include/Gaffer/BoxIO.h | 24 ++++ python/GafferTest/BoxInTest.py | 46 +++++++ src/Gaffer/BoxIO.cpp | 184 ++++++++++++++++++++++++++++ src/GafferBindings/BoxIOBinding.cpp | 7 ++ 4 files changed, 261 insertions(+) diff --git a/include/Gaffer/BoxIO.h b/include/Gaffer/BoxIO.h index 43bd61e1ea1..9dff68ad7d0 100644 --- a/include/Gaffer/BoxIO.h +++ b/include/Gaffer/BoxIO.h @@ -44,6 +44,7 @@ namespace Gaffer { IE_CORE_FORWARDDECLARE( StringPlug ) +IE_CORE_FORWARDDECLARE( Box ) /// Utility node for representing plug promotion /// graphically in the NodeGraph. Note that this has @@ -101,6 +102,29 @@ class BoxIO : public Node Plug::Direction direction() const; + /// Static utility methods + /// ====================== + /// + /// Equivalent to `PlugAlgo::promote()`, but + /// inserting an intermediate BoxIO node where + /// relevant (based on querying nodule layout + /// metadata). + /// \undoable + static Plug *promote( Plug *plug ); + /// Inserts intermediate BoxIO nodes for any + /// promoted plugs that require them (based + /// on querying nodule layout metadata). This + /// can be used to upgrade boxes that were + /// either authored in the pre-BoxIO era, or + /// were created by automated scripts that + /// are not BoxIO savvy. + /// \undoable + static void insert( Box *box ); + /// Returns true if `insert( box )` would + /// do anything. + /// \undoable + static bool canInsert( const Box *box ); + protected : BoxIO( Plug::Direction direction, const std::string &name=defaultName() ); diff --git a/python/GafferTest/BoxInTest.py b/python/GafferTest/BoxInTest.py index 30514e45553..f4fea249c2b 100644 --- a/python/GafferTest/BoxInTest.py +++ b/python/GafferTest/BoxInTest.py @@ -301,5 +301,51 @@ def assertPostconditions() : s.redo() assertPostconditions() + def testPromote( self ) : + + s = Gaffer.ScriptNode() + s["b"] = Gaffer.Box() + s["b"]["n"] = GafferTest.AddNode() + + Gaffer.Metadata.registerValue( s["b"]["n"]["op2"], "nodule:type", "" ) + + Gaffer.BoxIO.promote( s["b"]["n"]["op1"] ) + Gaffer.BoxIO.promote( s["b"]["n"]["op2"] ) + Gaffer.BoxIO.promote( s["b"]["n"]["sum"] ) + + self.assertTrue( isinstance( s["b"]["n"]["op1"].getInput().node(), Gaffer.BoxIn ) ) + self.assertTrue( s["b"]["n"]["op1"].source().node().isSame( s["b"] ) ) + self.assertTrue( s["b"]["n"]["op2"].getInput().node().isSame( s["b"] ) ) + self.assertEqual( len( s["b"]["n"]["sum"].outputs() ), 1 ) + self.assertTrue( isinstance( s["b"]["n"]["sum"].outputs()[0].parent(), Gaffer.BoxOut ) ) + + def testInsert( self ) : + + s = Gaffer.ScriptNode() + s["b"] = Gaffer.Box() + s["b"]["n"] = GafferTest.AddNode() + + op1Promoted = Gaffer.PlugAlgo.promote( s["b"]["n"]["op1"] ) + sumPromoted = Gaffer.PlugAlgo.promote( s["b"]["n"]["sum"] ) + s["b"]["n"]["op2"].setInput( s["b"]["n"]["op1"].getInput() ) + self.assertEqual( len( s["b"].children( Gaffer.BoxIO ) ), 0 ) + + self.assertEqual( Gaffer.BoxIO.canInsert( s["b"] ), True ) + Gaffer.BoxIO.insert( s["b"] ) + + self.assertEqual( len( s["b"].children( Gaffer.BoxIn ) ), 1 ) + self.assertEqual( len( s["b"].children( Gaffer.BoxOut ) ), 1 ) + + self.assertTrue( isinstance( s["b"]["n"]["op1"].getInput().node(), Gaffer.BoxIn ) ) + self.assertTrue( isinstance( s["b"]["n"]["op2"].getInput().node(), Gaffer.BoxIn ) ) + self.assertTrue( s["b"]["n"]["op1"].source().isSame( op1Promoted ) ) + self.assertTrue( s["b"]["n"]["op2"].source().isSame( op1Promoted ) ) + + self.assertEqual( len( s["b"]["n"]["sum"].outputs() ), 1 ) + self.assertTrue( isinstance( s["b"]["n"]["sum"].outputs()[0].parent(), Gaffer.BoxOut ) ) + self.assertTrue( sumPromoted.source().isSame( s["b"]["n"]["sum"] ) ) + + self.assertEqual( Gaffer.BoxIO.canInsert( s["b"] ), False ) + if __name__ == "__main__": unittest.main() diff --git a/src/Gaffer/BoxIO.cpp b/src/Gaffer/BoxIO.cpp index 2b339faa597..9b56841f8a8 100644 --- a/src/Gaffer/BoxIO.cpp +++ b/src/Gaffer/BoxIO.cpp @@ -35,6 +35,7 @@ ////////////////////////////////////////////////////////////////////////// #include "boost/bind.hpp" +#include "boost/algorithm/string/replace.hpp" #include "Gaffer/StringPlug.h" #include "Gaffer/BoxIO.h" @@ -43,6 +44,9 @@ #include "Gaffer/MetadataAlgo.h" #include "Gaffer/PlugAlgo.h" #include "Gaffer/ScriptNode.h" +#include "Gaffer/BoxIn.h" +#include "Gaffer/BoxOut.h" +#include "Gaffer/Box.h" using namespace IECore; using namespace Gaffer; @@ -354,3 +358,183 @@ void BoxIO::promotedPlugParentChanged( GraphComponent *graphComponent ) } } +////////////////////////////////////////////////////////////////////////// +// Static utilities +////////////////////////////////////////////////////////////////////////// + +namespace +{ + +/// \todo Perhaps this could be moved to PlugAlgo and +/// (along with a matching canConnect()) be used to +/// address the todo in GraphBookmarksUI.__connection? +void connect( Plug *plug1, Plug *plug2 ) +{ + if( plug1->direction() == plug2->direction() ) + { + throw IECore::Exception( "Ambiguous connection" ); + } + + if( plug1->direction() == Plug::In ) + { + plug1->setInput( plug2 ); + } + else + { + plug2->setInput( plug1 ); + } +} + +InternedString g_noduleTypeName( "nodule:type" ); + +bool hasNodule( const Plug *plug ) +{ + for( const Plug *p = plug; p; p = p->parent() ) + { + ConstStringDataPtr d = Metadata::plugValue( p, g_noduleTypeName ); + if( d && d->readable() == "" ) + { + return false; + } + if( p != plug ) + { + if( !d || d->readable() == "GafferUI::StandardNodule" ) + { + return false; + } + } + } + + return true; +} + +Box *enclosingBox( Plug *plug ) +{ + Node *node = plug->node(); + if( !node ) + { + return NULL; + } + return node->parent(); +} + +std::string promotedName( const Plug *plug ) +{ + std::string result = plug->relativeName( plug->node() ); + boost::replace_all( result, ".", "_" ); + return result; +} + +} // namespace + +Plug *BoxIO::promote( Plug *plug ) +{ + Box *box = enclosingBox( plug ); + if( !box || !hasNodule( plug ) ) + { + return PlugAlgo::promote( plug ); + } + + BoxIOPtr boxIO; + if( plug->direction() == Plug::In ) + { + boxIO = new BoxIn; + } + else + { + boxIO = new BoxOut; + } + + box->addChild( boxIO ); + boxIO->namePlug()->setValue( promotedName( plug ) ); + boxIO->setup( plug ); + + connect( plug, boxIO->plug() ); + return boxIO->promotedPlug(); +} + +bool BoxIO::canInsert( const Box *box ) +{ + for( PlugIterator it( box ); !it.done(); ++it ) + { + const Plug *plug = it->get(); + if( plug->direction() == Plug::In ) + { + const Plug::OutputContainer &outputs = plug->outputs(); + for( Plug::OutputContainer::const_iterator oIt = outputs.begin(), oeIt = outputs.end(); oIt != oeIt; ++oIt ) + { + if( hasNodule( *oIt ) && !runTimeCast( (*oIt)->node() ) ) + { + return true; + } + } + } + else + { + const Plug *input = plug->getInput(); + if( input && hasNodule( input ) && !runTimeCast( input->node() ) ) + { + return true; + } + } + } + + return false; +} + +void BoxIO::insert( Box *box ) +{ + // Must take a copy of children because adding a child + // would invalidate our PlugIterator. + GraphComponent::ChildContainer children = box->children(); + for( PlugIterator it( children ); !it.done(); ++it ) + { + Plug *plug = it->get(); + if( plug->direction() == Plug::In ) + { + std::vector outputsNeedingBoxIn; + const Plug::OutputContainer &outputs = plug->outputs(); + for( Plug::OutputContainer::const_iterator oIt = outputs.begin(), oeIt = outputs.end(); oIt != oeIt; ++oIt ) + { + if( hasNodule( *oIt ) && !runTimeCast( (*oIt)->node() ) ) + { + outputsNeedingBoxIn.push_back( *oIt ); + } + } + + if( outputsNeedingBoxIn.empty() ) + { + continue; + } + + BoxInPtr boxIn = new BoxIn; + boxIn->namePlug()->setValue( plug->getName() ); + boxIn->setup( plug ); + boxIn->inPlugInternal()->setInput( plug ); + for( std::vector::const_iterator oIt = outputsNeedingBoxIn.begin(), oeIt = outputsNeedingBoxIn.end(); oIt != oeIt; ++oIt ) + { + (*oIt)->setInput( boxIn->plug() ); + } + + box->addChild( boxIn ); + } + else + { + // Output plug + + Plug *input = plug->getInput(); + if( !input || !hasNodule( input ) || runTimeCast( input->node() ) ) + { + continue; + } + + BoxOutPtr boxOut = new BoxOut; + boxOut->namePlug()->setValue( plug->getName() ); + boxOut->setup( plug ); + boxOut->plug()->setInput( input ); + plug->setInput( boxOut->outPlugInternal() ); + box->addChild( boxOut ); + } + } + +} diff --git a/src/GafferBindings/BoxIOBinding.cpp b/src/GafferBindings/BoxIOBinding.cpp index 9b007f7a2b5..740eef7e337 100644 --- a/src/GafferBindings/BoxIOBinding.cpp +++ b/src/GafferBindings/BoxIOBinding.cpp @@ -39,6 +39,7 @@ #include "Gaffer/BoxIn.h" #include "Gaffer/BoxOut.h" #include "Gaffer/Plug.h" +#include "Gaffer/Box.h" #include "GafferBindings/DependencyNodeBinding.h" #include "GafferBindings/BoxIOBinding.h" @@ -107,6 +108,12 @@ void GafferBindings::bindBoxIO() .def( "setup", &BoxIO::setup, ( arg( "plug" ) = object() ) ) .def( "plug", &plug ) .def( "promotedPlug", &promotedPlug ) + .def( "promote", &BoxIO::promote, return_value_policy() ) + .staticmethod( "promote" ) + .def( "insert", &BoxIO::insert ) + .staticmethod( "insert" ) + .def( "canInsert", &BoxIO::canInsert ) + .staticmethod( "canInsert" ) ; Serialisation::registerSerialiser( BoxIO::staticTypeId(), new BoxIOSerialiser ); From 1874e0162ec34e682cc600602fa04ed5a8dad487 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Thu, 13 Apr 2017 16:50:10 +0100 Subject: [PATCH 21/25] BoxUI : Use BoxIO for promotion Also add NodeEditor tool menu item for upgrading old boxes. --- python/GafferUI/BoxUI.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/python/GafferUI/BoxUI.py b/python/GafferUI/BoxUI.py index 249212bdc47..0075a277979 100644 --- a/python/GafferUI/BoxUI.py +++ b/python/GafferUI/BoxUI.py @@ -113,7 +113,9 @@ def nodeMenuCreateCommand( menu ) : script = nodeGraph.scriptNode() graphGadget = nodeGraph.graphGadget() - return Gaffer.Box.create( graphGadget.getRoot(), script.selection() ) + box = Gaffer.Box.create( graphGadget.getRoot(), script.selection() ) + Gaffer.BoxIO.insert( box ) + return box ## \deprecated Use NodeGraph.appendSubGraphMenuDefinitions() def appendNodeContextMenuDefinitions( nodeGraph, node, menuDefinition ) : @@ -137,6 +139,10 @@ def appendNodeEditorToolMenuDefinitions( nodeEditor, node, menuDefinition ) : menuDefinition.append( "/Export reference...", { "command" : functools.partial( __exportForReferencing, node = node ) } ) menuDefinition.append( "/Import reference...", { "command" : functools.partial( __importReference, node = node ) } ) + if Gaffer.BoxIO.canInsert( node ) : + menuDefinition.append( "/UpgradeDivider", { "divider" : True } ) + menuDefinition.append( "/Upgrade to use BoxIO", { "command" : functools.partial( __upgradeToUseBoxIO, node = node ) } ) + def __showContents( nodeGraph, box ) : GafferUI.NodeGraph.acquire( box ) @@ -183,6 +189,11 @@ def __importReference( menu, node ) : with Gaffer.UndoScope( scriptNode ) : scriptNode.executeFile( str( path ), parent = node, continueOnError = True ) +def __upgradeToUseBoxIO( node ) : + + with Gaffer.UndoScope( node.scriptNode() ) : + Gaffer.BoxIO.insert( node ) + # PlugValueWidget registrations ########################################################################## @@ -261,7 +272,7 @@ def __appendPlugDeletionMenuItems( menuDefinition, plug, readOnly = False ) : def __promote( plug ) : with Gaffer.UndoScope( plug.ancestor( Gaffer.ScriptNode ) ) : - Gaffer.PlugAlgo.promote( plug ) + Gaffer.BoxIO.promote( plug ) def __unpromote( plug ) : From e6ce64532404a229152777ff47a2a9fb4f39507f Mon Sep 17 00:00:00 2001 From: John Haddon Date: Thu, 13 Apr 2017 17:44:39 +0100 Subject: [PATCH 22/25] BoxUI : Only allow deletion of top level plugs --- python/GafferUI/BoxUI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/GafferUI/BoxUI.py b/python/GafferUI/BoxUI.py index 0075a277979..d2bb23974e3 100644 --- a/python/GafferUI/BoxUI.py +++ b/python/GafferUI/BoxUI.py @@ -260,7 +260,7 @@ def __deletePlug( plug ) : def __appendPlugDeletionMenuItems( menuDefinition, plug, readOnly = False ) : - if not isinstance( plug.node(), Gaffer.Box ) : + if not isinstance( plug.parent(), Gaffer.Box ) : return menuDefinition.append( "/DeleteDivider", { "divider" : True } ) From 269918e1cbbeb5243f0d607eaf0c5e5af3126253 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Thu, 13 Apr 2017 18:16:02 +0100 Subject: [PATCH 23/25] BoxInTest : Add test for deletion of promoted ArrayPlug I was expecting this to expose a bug, but it turns out the BoxIn is fine, and the bug is in the UI. --- python/GafferTest/BoxInTest.py | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/python/GafferTest/BoxInTest.py b/python/GafferTest/BoxInTest.py index f4fea249c2b..ac78189f8e0 100644 --- a/python/GafferTest/BoxInTest.py +++ b/python/GafferTest/BoxInTest.py @@ -259,6 +259,46 @@ def assertPostconditions() : assertPostconditions() + def testPromotedArrayPlugRemovalDeletesBoxIn( self ) : + + s = Gaffer.ScriptNode() + + s["b"] = Gaffer.Box() + s["b"]["n"] = GafferTest.ArrayPlugNode() + + s["b"]["i"] = Gaffer.BoxIn() + s["b"]["i"]["name"].setValue( "in" ) + s["b"]["i"].setup( s["b"]["n"]["in"] ) + s["b"]["n"]["in"].setInput( s["b"]["i"]["out"] ) + + def assertPreconditions() : + + self.assertTrue( "in" in s["b"] ) + self.assertTrue( "i" in s["b"] ) + self.assertTrue( len( s["b"]["i"]["out"].outputs() ), 1 ) + self.assertTrue( s["b"]["n"]["in"].source().isSame( s["b"]["in"] ) ) + + assertPreconditions() + + with Gaffer.UndoContext( s ) : + del s["b"]["in"] + + def assertPostconditions() : + + self.assertFalse( "in" in s["b"] ) + self.assertFalse( "i" in s["b"] ) + self.assertTrue( s["b"]["n"]["in"].getInput() is None ) + + assertPostconditions() + + s.undo() + + assertPreconditions() + + s.redo() + + assertPostconditions() + def testUndoCreation( self ) : s = Gaffer.ScriptNode() From 9c9a0207937d3811c0451542135745776c011c30 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Thu, 13 Apr 2017 18:39:17 +0100 Subject: [PATCH 24/25] BoxUI : Use top level parent in Nodule context menu Otherwise we end up trying to move/rename/delete the elements of promoted array plugs, rather than the array plug itself. --- python/GafferUI/BoxUI.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/python/GafferUI/BoxUI.py b/python/GafferUI/BoxUI.py index d2bb23974e3..d5ddf8d226c 100644 --- a/python/GafferUI/BoxUI.py +++ b/python/GafferUI/BoxUI.py @@ -386,38 +386,47 @@ def __reorderPlugs( plugs, plug, newIndex ) : def __nodeGraphPlugContextMenu( nodeGraph, plug, menuDefinition ) : + # The context menu might be showing for the child nodule + # of a CompoundNodule, but most of our operations only + # make sense on the top level parent plug, so find that + # and use it. + parentPlug = plug + while isinstance( parentPlug.parent(), Gaffer.Plug ) : + parentPlug = parentPlug.parent() + readOnly = Gaffer.MetadataAlgo.readOnly( plug ) + if isinstance( plug.node(), Gaffer.Box ) : menuDefinition.append( "/Rename...", { - "command" : functools.partial( __renamePlug, plug = plug ), + "command" : functools.partial( __renamePlug, plug = parentPlug ), "active" : not readOnly, } ) menuDefinition.append( "/MoveDivider", { "divider" : True } ) - currentEdge = Gaffer.Metadata.value( plug, "noduleLayout:section" ) + currentEdge = Gaffer.Metadata.value( parentPlug, "noduleLayout:section" ) if not currentEdge : - currentEdge = "top" if plug.direction() == plug.Direction.In else "bottom" + currentEdge = "top" if parentPlug.direction() == parentPlug.Direction.In else "bottom" for edge in ( "top", "bottom", "left", "right" ) : menuDefinition.append( "/Move To/" + edge.capitalize(), { - "command" : functools.partial( __setPlugMetadata, plug, "noduleLayout:section", edge ), + "command" : functools.partial( __setPlugMetadata, parentPlug, "noduleLayout:section", edge ), "active" : edge != currentEdge and not readOnly, } ) - edgePlugs = __edgePlugs( nodeGraph, plug ) - edgeIndex = edgePlugs.index( plug ) + edgePlugs = __edgePlugs( nodeGraph, parentPlug ) + edgeIndex = edgePlugs.index( parentPlug ) menuDefinition.append( "/Move " + ( "Up" if currentEdge in ( "left", "right" ) else "Left" ), { - "command" : functools.partial( __reorderPlugs, edgePlugs, plug, edgeIndex - 1 ), + "command" : functools.partial( __reorderPlugs, edgePlugs, parentPlug, edgeIndex - 1 ), "active" : edgeIndex > 0 and not readOnly, } ) @@ -425,12 +434,12 @@ def __nodeGraphPlugContextMenu( nodeGraph, plug, menuDefinition ) : menuDefinition.append( "/Move " + ( "Down" if currentEdge in ( "left", "right" ) else "Right" ), { - "command" : functools.partial( __reorderPlugs, edgePlugs, plug, edgeIndex + 1 ), + "command" : functools.partial( __reorderPlugs, edgePlugs, parentPlug, edgeIndex + 1 ), "active" : edgeIndex < len( edgePlugs ) - 1 and not readOnly, } ) - __appendPlugDeletionMenuItems( menuDefinition, plug, readOnly ) + __appendPlugDeletionMenuItems( menuDefinition, parentPlug, readOnly ) __appendPlugPromotionMenuItems( menuDefinition, plug, readOnly ) __nodeGraphPlugContextMenuConnection = GafferUI.NodeGraph.plugContextMenuSignal().connect( __nodeGraphPlugContextMenu ) From 6e0fb6ba133fd786e7ca481d849f5c6e597bb3a2 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Tue, 18 Apr 2017 10:45:51 +0100 Subject: [PATCH 25/25] BoxIO : Apply StandardNodule metadata when promoting an ArrayPlug --- src/Gaffer/BoxIO.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Gaffer/BoxIO.cpp b/src/Gaffer/BoxIO.cpp index 9b56841f8a8..f278093cbe0 100644 --- a/src/Gaffer/BoxIO.cpp +++ b/src/Gaffer/BoxIO.cpp @@ -47,6 +47,7 @@ #include "Gaffer/BoxIn.h" #include "Gaffer/BoxOut.h" #include "Gaffer/Box.h" +#include "Gaffer/ArrayPlug.h" using namespace IECore; using namespace Gaffer; @@ -450,6 +451,17 @@ Plug *BoxIO::promote( Plug *plug ) boxIO->setup( plug ); connect( plug, boxIO->plug() ); + + if( runTimeCast( plug ) ) + { + // If we allowed the user to edit the connections + // for individual elements, they could break the + // promotion of the parent plug, so hide the + // individual elements. + Metadata::registerValue( plug, g_noduleTypeName, new StringData( "GafferUI::StandardNodule" ) ); + Metadata::registerValue( boxIO->plug(), g_noduleTypeName, new StringData( "GafferUI::StandardNodule" ) ); + } + return boxIO->promotedPlug(); }