Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Displays UI #823

Merged
merged 4 commits into from
May 1, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion include/Gaffer/CompoundDataPlug.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ class CompoundDataPlug : public Gaffer::CompoundPlug
/// this particular member is enabled.
MemberPlug *addOptionalMember( const std::string &name, const IECore::Data *defaultValue, const std::string &plugName = "member1", unsigned plugFlags = Plug::Default | Plug::Dynamic, bool enabled = false );
MemberPlug *addOptionalMember( const std::string &name, ValuePlug *valuePlug, const std::string &plugName = "member1", bool enabled = false );
void addMembers( const IECore::CompoundData *parameters );
/// Adds each member from the specified CompoundData.
void addMembers( const IECore::CompoundData *members, bool useNameAsPlugName = false );

/// Returns the value for the member specified by the child parameterPlug, and fills name with the
/// name for the member. If the user has disabled the member or the name is the empty string, then
Expand Down
9 changes: 8 additions & 1 deletion python/GafferSceneTest/DisplaysTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ def testHashPassThrough( self ) :
displays["in"].setInput( p["out"] )

self.assertSceneHashesEqual( p["out"], displays["out"], childPlugNames = ( "transform", "bound", "attributes", "object", "childNames" ) )


def testParametersHaveUsefulNames( self ) :

displays = GafferScene.Displays()
displays.addDisplay( "test", IECore.Display( "name", "type", "data", { "paramA" : 1, "paramB" : 2 } ) )

self.assertEqual( set( displays["displays"][0]["parameters"].keys() ), set( [ "paramA", "paramB" ] ) )

if __name__ == "__main__":
unittest.main()
149 changes: 113 additions & 36 deletions python/GafferSceneUI/DisplaysUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,30 +46,40 @@
import GafferScene
import GafferSceneUI

## \todo Decide how/where we show the display label.
class DisplaysPlugValueWidget( GafferUI.CompoundPlugValueWidget ) :
##########################################################################
# Custom PlugValueWidgets for listing displays
##########################################################################

class DisplaysPlugValueWidget( GafferUI.PlugValueWidget ) :

def __init__( self, plug ) :

GafferUI.CompoundPlugValueWidget.__init__( self, plug, collapsed = None )
column = GafferUI.ListContainer( spacing = 6 )
GafferUI.PlugValueWidget.__init__( self, column, plug )

self.__footerWidget = None

def _footerWidget( self ) :
with column :

if self.__footerWidget is not None :
return self.__footerWidget

self.__footerWidget = GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 )
# this will take care of laying out our list of displays, as
# each display is represented as a child plug of the main plug.
GafferUI.PlugLayout( plug )

# now we just need a little footer with a button for adding new displays
with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 ) :

addButton = GafferUI.MenuButton(
image="plus.png", hasFrame=False, menu = GafferUI.Menu( Gaffer.WeakMethod( self.__addMenuDefinition ) )
)
self.__footerWidget.append( addButton )
self.__footerWidget.append( GafferUI.Spacer( IECore.V2i( 1 ), maximumSize = IECore.V2i( 100000, 1 ) ), expand = True )
GafferUI.MenuButton(
image="plus.png", hasFrame=False, menu = GafferUI.Menu( Gaffer.WeakMethod( self.__addMenuDefinition ) )
)

return self.__footerWidget

GafferUI.Spacer( IECore.V2i( 1 ), maximumSize = IECore.V2i( 100000, 1 ), parenting = { "expand" : True } )

def hasLabel( self ) :

return True

def _updateFromPlug( self ) :

pass

def __addMenuDefinition( self ) :

node = self.getPlug().node()
Expand Down Expand Up @@ -97,49 +107,116 @@ def __addMenuDefinition( self ) :

return m

# A widget for representing an individual display.
class _ChildPlugWidget( GafferUI.PlugValueWidget ) :

def __init__( self, childPlug ) :

column = GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Vertical, spacing=4 )
GafferUI.PlugValueWidget.__init__( self, column, childPlug )

row = GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing=4 )
column.append( row )

collapseButton = GafferUI.Button( image = "collapsibleArrowRight.png", hasFrame=False )
collapseButton.__clickedConnection = collapseButton.clickedSignal().connect( Gaffer.WeakMethod( self.__collapseButtonClicked ) )
row.append( collapseButton )
with column :

with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing=4 ) as header :

collapseButton = GafferUI.Button( image = "collapsibleArrowRight.png", hasFrame=False )
collapseButton.__clickedConnection = collapseButton.clickedSignal().connect( Gaffer.WeakMethod( self.__collapseButtonClicked ) )

GafferUI.PlugValueWidget.create( childPlug["active"] )
self.__label = GafferUI.Label( childPlug["label"].getValue() )

row.append( GafferUI.PlugValueWidget.create( childPlug["active"] ) )
row.append( GafferUI.PlugValueWidget.create( childPlug["name"] ) )
row.append( GafferUI.PlugValueWidget.create( childPlug["type"] ) )
row.append( GafferUI.PlugValueWidget.create( childPlug["data"] ) )
GafferUI.Spacer( IECore.V2i( 1 ), maximumSize = IECore.V2i( 100000, 1 ), parenting = { "expand" : True } )

parameterList = GafferUI.CompoundDataPlugValueWidget( childPlug["parameters"], collapsed=None )
parameterList.setVisible( False )
column.append( parameterList )
self.__deleteButton = GafferUI.Button( image = "delete.png", hasFrame=False )
self.__deleteButton.__clickedConnection = self.__deleteButton.clickedSignal().connect( Gaffer.WeakMethod( self.__deleteButtonClicked ) )
self.__deleteButton.setVisible( False )

with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Vertical, spacing= 4 ) as self.__detailsColumn :

GafferUI.PlugWidget( childPlug["label"] )
GafferUI.PlugWidget( childPlug["name"] )
GafferUI.PlugWidget( childPlug["type"] )
GafferUI.PlugWidget( childPlug["data"] )
GafferUI.CompoundDataPlugValueWidget( childPlug["parameters"], collapsed=None )

GafferUI.Divider( GafferUI.Divider.Orientation.Horizontal )

self.__detailsColumn.setVisible( False )

self.__enterConnection = header.enterSignal().connect( Gaffer.WeakMethod( self.__enter ) )
self.__leaveConnection = header.leaveSignal().connect( Gaffer.WeakMethod( self.__leave ) )

def hasLabel( self ) :

return True

def _updateFromPlug( self ) :

with self.getContext() :

enabled = self.getPlug()["active"].getValue()
self.__label.setEnabled( enabled )
self.__detailsColumn.setEnabled( enabled )

self.__label.setText( self.getPlug()["label"].getValue() )

def __enter( self, widget ) :

self.__deleteButton.setVisible( True )

def __leave( self, widget ) :

self.__deleteButton.setVisible( False )

def __collapseButtonClicked( self, button ) :

column = button.parent().parent()
parameterList = column[1]
visible = not parameterList.getVisible()
parameterList.setVisible( visible )
visible = not self.__detailsColumn.getVisible()
self.__detailsColumn.setVisible( visible )
button.setImage( "collapsibleArrowDown.png" if visible else "collapsibleArrowRight.png" )

def _updateFromPlug( self ) :
def __deleteButtonClicked( self, button ) :

pass
with Gaffer.UndoContext( self.getPlug().ancestor( Gaffer.ScriptNode.staticTypeId() ) ) :
self.getPlug().parent().removeChild( self.getPlug() )

GafferUI.PlugValueWidget.registerCreator( GafferScene.Displays.staticTypeId(), "displays", DisplaysPlugValueWidget )

## \todo This regex is an interesting case to be considered during the string matching unification for #707. Once that
# is done, intuitively we want to use a "displays.*" glob expression, but because the "*" will match anything
# at all, including ".", it will match the children of what we want too. We might want to prevent wildcards from
# matching "." when we come to use them in this context.
GafferUI.PlugValueWidget.registerCreator( GafferScene.Displays.staticTypeId(), re.compile( "displays\.[^\.]+$" ), _ChildPlugWidget )

##########################################################################
# Simple PlugValueWidget registrations for child plugs of displays
##########################################################################

GafferUI.PlugValueWidget.registerCreator(
GafferScene.Displays.staticTypeId(),
"displays.*.active",
GafferUI.BoolPlugValueWidget,
displayMode = GafferUI.BoolWidget.DisplayMode.Switch,
)

GafferUI.PlugValueWidget.registerCreator(
GafferScene.Displays.staticTypeId(),
re.compile( "displays.*.parameters.quantize" ),
GafferUI.EnumPlugValueWidget,
labelsAndValues = [
( "8 bit", IECore.IntVectorData( [ 0, 255, 0, 255 ] ) ),
( "16 bit", IECore.IntVectorData( [ 0, 65535, 0, 65535 ] ) ),
( "Float", IECore.IntVectorData( [ 0, 0, 0, 0 ] ) ),
]
)

GafferUI.PlugValueWidget.registerCreator(
GafferScene.Displays.staticTypeId(),
"displays.*.name",
lambda plug : GafferUI.PathPlugValueWidget( plug,
path = Gaffer.FileSystemPath( "/", filter = Gaffer.FileSystemPath.createStandardFilter() ),
pathChooserDialogueKeywords = {
"bookmarks" : GafferUI.Bookmarks.acquire( plug, category = "image" ),
"leaf" : True,
},
)
)
25 changes: 25 additions & 0 deletions python/GafferTest/CompoundDataPlugTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,31 @@ def testDefaultValues( self ) :
m = p.addMember( "c", IECore.StringData( "abc" ) )
self.assertTrue( m["value"].defaultValue(), "abc" )
self.assertTrue( m["value"].getValue(), "abc" )

def testAddMembers( self ) :

p = Gaffer.CompoundDataPlug()

p.addMembers( IECore.CompoundData( { "one" : 1, "two" : 2 } ) )
self.assertEqual( len( p ), 2 )
self.assertEqual( p[0].getName(), "member1" )
self.assertEqual( p[1].getName(), "member2" )

c = IECore.CompoundData()
p.fillCompoundData( c )
self.assertEqual( c, IECore.CompoundData( { "one" : 1, "two" : 2 } ) )

def testAddMembersWithSpecificNames( self ) :

p = Gaffer.CompoundDataPlug()
p.addMembers( IECore.CompoundData( { "one" : 1 } ), useNameAsPlugName=True )

self.assertEqual( len( p ), 1 )
self.assertEqual( p[0].getName(), "one" )

o = IECore.CompoundObject()
p.fillCompoundObject( o )
self.assertEqual( o, IECore.CompoundObject( { "one" : IECore.IntData( 1 ) } ) )

if __name__ == "__main__":
unittest.main()
Expand Down
26 changes: 22 additions & 4 deletions resources/graphics.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 7 additions & 2 deletions src/Gaffer/CompoundDataPlug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,16 @@ CompoundDataPlug::MemberPlug *CompoundDataPlug::addOptionalMember( const std::st
return plug;
}

void CompoundDataPlug::addMembers( const IECore::CompoundData *parameters )
void CompoundDataPlug::addMembers( const IECore::CompoundData *parameters, bool useNameAsPlugName )
{
std::string plugName = "member1";
for( CompoundDataMap::const_iterator it = parameters->readable().begin(); it!=parameters->readable().end(); it++ )
{
addMember( it->first, it->second );
if( useNameAsPlugName )
{
plugName = it->first;
}
addMember( it->first, it->second, plugName );
}
}

Expand Down
14 changes: 13 additions & 1 deletion src/GafferBindings/CompoundDataPlugBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ static tuple memberDataAndNameWrapper( CompoundDataPlug &p, const CompoundDataPl
return make_tuple( d, name );
}

static void fillCompoundData( const CompoundDataPlug &p, IECore::CompoundData *d )
{
p.fillCompoundData( d->writable() );
}

static void fillCompoundObject( const CompoundDataPlug &p, IECore::CompoundObject *o )
{
p.fillCompoundObject( o->members() );
}

class MemberPlugSerialiser : public CompoundPlugSerialiser
{

Expand Down Expand Up @@ -151,8 +161,10 @@ void GafferBindings::bindCompoundDataPlug()
.def( "addMember", &addMemberWrapper2, ( arg_( "name" ), arg_( "valuePlug" ), arg_( "plugName" ) = "member1" ) )
.def( "addOptionalMember", &addOptionalMemberWrapper, ( arg_( "name" ), arg_( "defaultValue" ), arg_( "plugName" ) = "member1", arg_( "plugFlags" ) = Plug::Default | Plug::Dynamic, arg_( "enabled" ) = false ) )
.def( "addOptionalMember", &addOptionalMemberWrapper2, ( arg_( "name" ), arg_( "valuePlug" ), arg_( "plugName" ) = "member1", arg_( "enabled" ) = false ) )
.def( "addMembers", &CompoundDataPlug::addMembers )
.def( "addMembers", &CompoundDataPlug::addMembers, ( arg_( "members" ), arg_( "useNameAsPlugName" ) = false ) )
.def( "memberDataAndName", &memberDataAndNameWrapper )
.def( "fillCompoundData", &fillCompoundData )
.def( "fillCompoundObject", &fillCompoundObject )
;

IECorePython::RunTimeTypedClass<CompoundDataPlug::MemberPlug>()
Expand Down
2 changes: 1 addition & 1 deletion src/GafferScene/Displays.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ Gaffer::CompoundPlug *Displays::addDisplay( const std::string &label, const IECo

CompoundDataPlugPtr parametersPlug = new CompoundDataPlug( "parameters" );
parametersPlug->setFlags( Plug::Dynamic, true );
parametersPlug->addMembers( const_cast<Display *>( display )->parametersData() );
parametersPlug->addMembers( const_cast<Display *>( display )->parametersData(), /* useNameAsPlugName = */ true );
displayPlug->addChild( parametersPlug );

displaysPlug()->addChild( displayPlug );
Expand Down