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/include/Gaffer/BoxIO.h b/include/Gaffer/BoxIO.h new file mode 100644 index 00000000000..9dff68ad7d0 --- /dev/null +++ b/include/Gaffer/BoxIO.h @@ -0,0 +1,169 @@ +////////////////////////////////////////////////////////////////////////// +// +// 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 ) +IE_CORE_FORWARDDECLARE( Box ) + +/// 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; + + /// 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() ); + + 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_promotedPlugNameChangedConnection; + boost::signals::scoped_connection m_promotedPlugParentChangedConnection; + + void plugSet( Plug *plug ); + void parentChanged( GraphComponent *oldParent ); + void plugInputChanged( Plug *plug ); + void promotedPlugNameChanged( GraphComponent *graphComponent ); + void promotedPlugParentChanged( 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/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/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/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/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/include/GafferUI/StandardNodeGadget.h b/include/GafferUI/StandardNodeGadget.h index dad54fd8f41..2f1a2ba8f2e 100644 --- a/include/GafferUI/StandardNodeGadget.h +++ b/include/GafferUI/StandardNodeGadget.h @@ -54,9 +54,11 @@ 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 +/// - "nodeGadget:shape" : StringData containing "rectangle" or "oval" +/// - "icon" : string naming an image to be used with ImageGadget class StandardNodeGadget : public NodeGadget { @@ -114,6 +116,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 +145,8 @@ class StandardNodeGadget : public NodeGadget bool updateUserColor(); void updatePadding(); void updateNodeEnabled( const Gaffer::Plug *dirtiedPlug = NULL ); + void updateIcon(); + bool updateShape(); IE_CORE_FORWARDDECLARE( ErrorGadget ); ErrorGadget *errorGadget( bool createIfMissing = true ); @@ -152,6 +162,7 @@ class StandardNodeGadget : public NodeGadget // to hit. Gadget *m_dragDestinationProxy; boost::optional m_userColor; + bool m_oval; }; diff --git a/python/GafferTest/BoxInTest.py b/python/GafferTest/BoxInTest.py new file mode 100644 index 00000000000..ac78189f8e0 --- /dev/null +++ b/python/GafferTest/BoxInTest.py @@ -0,0 +1,391 @@ +########################################################################## +# +# 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" ) + + 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 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() + + 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() + + 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/python/GafferTest/BoxOutTest.py b/python/GafferTest/BoxOutTest.py new file mode 100644 index 00000000000..3bc56c44dd8 --- /dev/null +++ b/python/GafferTest/BoxOutTest.py @@ -0,0 +1,222 @@ +########################################################################## +# +# 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" ) + + 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/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/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 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/BoxUI.py b/python/GafferUI/BoxUI.py index 2bb7223a3fa..d5ddf8d226c 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 = { "*" : [ @@ -103,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 ) : @@ -127,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 ) @@ -173,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 ########################################################################## @@ -239,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 } ) @@ -251,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 ) : @@ -365,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, } ) @@ -404,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 ) 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 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" /> + + + + + + + + + + + + + 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 diff --git a/src/Gaffer/BoxIO.cpp b/src/Gaffer/BoxIO.cpp new file mode 100644 index 00000000000..f278093cbe0 --- /dev/null +++ b/src/Gaffer/BoxIO.cpp @@ -0,0 +1,552 @@ +////////////////////////////////////////////////////////////////////////// +// +// 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/StringPlug.h" +#include "Gaffer/BoxIO.h" +#include "Gaffer/Box.h" +#include "Gaffer/Metadata.h" +#include "Gaffer/MetadataAlgo.h" +#include "Gaffer/PlugAlgo.h" +#include "Gaffer/ScriptNode.h" +#include "Gaffer/BoxIn.h" +#include "Gaffer/BoxOut.h" +#include "Gaffer/Box.h" +#include "Gaffer/ArrayPlug.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() ) + { + m_promotedPlugNameChangedConnection.disconnect(); + m_promotedPlugParentChangedConnection.disconnect(); + 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 ) +{ + // 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() ) + { + promoted = promotedPlug(); + } + else if( m_direction == Plug::Out && plug == promotedPlug() ) + { + 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::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 ); + } + } +} + +////////////////////////////////////////////////////////////////////////// +// 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() ); + + 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(); +} + +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/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() +{ +} 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() +{ +} 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 diff --git a/src/GafferBindings/BoxIOBinding.cpp b/src/GafferBindings/BoxIOBinding.cpp new file mode 100644 index 00000000000..740eef7e337 --- /dev/null +++ b/src/GafferBindings/BoxIOBinding.cpp @@ -0,0 +1,124 @@ +////////////////////////////////////////////////////////////////////////// +// +// 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 "Gaffer/Box.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 ) + .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 ); + + 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(); 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 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 + 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 diff --git a/src/GafferUI/StandardNodeGadget.cpp b/src/GafferUI/StandardNodeGadget.cpp index 57bee47fc9b..5c34a24ba51 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" @@ -151,39 +153,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 ////////////////////////////////////////////////////////////////////////// @@ -196,6 +165,8 @@ 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" ); StandardNodeGadget::StandardNodeGadget( Gaffer::NodePtr node ) @@ -203,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 @@ -275,11 +247,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 ); @@ -314,6 +298,8 @@ StandardNodeGadget::StandardNodeGadget( Gaffer::NodePtr node ) updateUserColor(); updatePadding(); updateNodeEnabled(); + updateIcon(); + updateShape(); } StandardNodeGadget::~StandardNodeGadget() @@ -339,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() ); @@ -442,17 +435,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 ) @@ -730,6 +744,17 @@ void StandardNodeGadget::nodeMetadataChanged( IECore::TypeId nodeTypeId, IECore: { updatePadding(); } + else if( key == g_iconKey ) + { + updateIcon(); + } + else if( key == g_shapeKey ) + { + if( updateShape() ) + { + requestRender(); + } + } } bool StandardNodeGadget::updateUserColor() @@ -757,7 +782,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 ) @@ -800,6 +825,44 @@ 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 ); +} + +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 ) ) 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 ) 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 ) )