Skip to content
Open
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
4 changes: 4 additions & 0 deletions include/Gaffer/PlugAlgo.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ namespace Gaffer
IE_CORE_FORWARDDECLARE( Context )
IE_CORE_FORWARDDECLARE( GraphComponent )
IE_CORE_FORWARDDECLARE( ValuePlug )
IE_CORE_FORWARDDECLARE( ArrayPlug )

namespace PlugAlgo
{
Expand Down Expand Up @@ -97,6 +98,9 @@ GAFFER_API ValuePlugPtr createPlugFromData( const std::string &name, Plug::Direc
/// Returns a Data value from a plug.
GAFFER_API IECore::DataPtr getValueAsData( const ValuePlug *plug );

/// Returns a VectorData value from an array plug.
GAFFER_API IECore::DataPtr getArrayAsVectorData( const ArrayPlug *plug );

/// Sets the value of an existing plug to the specified data.
/// Returns `true` on success and `false` on failure.
GAFFER_API bool setValueFromData( ValuePlug *plug, const IECore::Data *value );
Expand Down
24 changes: 24 additions & 0 deletions python/GafferArnoldTest/ArnoldShaderTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -989,6 +989,30 @@ def testUSDRoundTrip( self ) :
ignoreBlindData = True
)

def testArrayConnections ( self ):

ramp = GafferArnold.ArnoldShader("ramp")
ramp.loadShader( "ramp_rgb" )
ramp['parameters']['position'].resize(2)
ramp['parameters']['color'].resize(2)
ramp['parameters']['interpolation'].resize(2)

userDataRgb = GafferArnold.ArnoldShader()
userDataRgb.loadShader("user_data_rgb")

userDataFloat = GafferArnold.ArnoldShader()
userDataFloat.loadShader( "user_data_float" )

ramp['parameters']['position'][1].setInput(userDataFloat['out'])
ramp['parameters']['color'][1].setInput(userDataRgb['out'])

n = ramp.attributes()

self.assertTrue( ramp["parameters"]["color"][1].acceptsInput( userDataRgb["out"] ) )
self.assertTrue( ramp["parameters"]["position"][1].acceptsInput( userDataFloat["out"] ) )
# 3 RGB connections and 1 float connection
self.assertTrue(len(n["ai:surface"].inputConnections("ramp")) == 4)

def testStandardVolumeType( self ) :

shader = GafferArnold.ArnoldShader()
Expand Down
104 changes: 104 additions & 0 deletions python/GafferUI/ArrayDataWidget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import Gaffer
import GafferUI
from GafferUI.VectorDataWidget import _Model as _VectorDataWidgetModel
from GafferUI.VectorDataWidget import _Delegate as _VectorDataWidgetDelegate

from Qt import QtCore
from Qt import QtWidgets
from Qt import QtCompat


class ArrayDataWidget( GafferUI.VectorDataWidget ) :
def __init__( self, data=None, **kwargs ) :
self.__rowEditability = None
super( ArrayDataWidget, self ).__init__( data, **kwargs )


def setData( self, data ) :

if data is not None :
if not isinstance( data, list ) :
data = [ data ]
self._VectorDataWidget__model = _Model( data, self._VectorDataWidget__tableView, self.getEditable(), self._VectorDataWidget__header, self._VectorDataWidget__toolTips, self._VectorDataWidget__columnEditability, self.__rowEditability )
self._VectorDataWidget__model.dataChanged.connect( Gaffer.WeakMethod( self._VectorDataWidget__modelDataChanged ) )
self._VectorDataWidget__model.rowsInserted.connect( Gaffer.WeakMethod( self._VectorDataWidget__emitDataChangedSignal ) )
self._VectorDataWidget__model.rowsRemoved.connect( Gaffer.WeakMethod( self._VectorDataWidget__emitDataChangedSignal ) )
else :
self._VectorDataWidget__model = None

self._VectorDataWidget__tableView.setModel( self._VectorDataWidget__model )

if self._VectorDataWidget__model :

columnIndex = 0
haveResizeableContents = False
for accessor in self._VectorDataWidget__model.vectorDataAccessors() :
for i in range( 0, accessor.numColumns() ) :
delegate = _VectorDataWidgetDelegate.create( accessor.data() )
delegate.setParent( self._VectorDataWidget__model )
self._VectorDataWidget__tableView.setItemDelegateForColumn( columnIndex, delegate )
canStretch = delegate.canStretch()
haveResizeableContents = haveResizeableContents or canStretch
columnIndex += 1

QtCompat.setSectionResizeMode(
self._VectorDataWidget__tableView.horizontalHeader(),
QtWidgets.QHeaderView.ResizeToContents if haveResizeableContents else QtWidgets.QHeaderView.Fixed
)

self._VectorDataWidget__tableView.horizontalHeader().setStretchLastSection( canStretch )
horizontalSizePolicy = QtWidgets.QSizePolicy.Expanding
if self._VectorDataWidget__tableView.horizontalScrollMode() == QtCore.Qt.ScrollBarAlwaysOff and not canStretch :
horizontalSizePolicy = QtWidgets.QSizePolicy.Fixed

self._VectorDataWidget__tableView.setSizePolicy(
QtWidgets.QSizePolicy(
horizontalSizePolicy,
QtWidgets.QSizePolicy.Maximum
)
)

selectionModel = self._VectorDataWidget__tableView.selectionModel()
selectionModel.selectionChanged.connect( Gaffer.WeakMethod( self._VectorDataWidget__selectionChanged ) )

self._VectorDataWidget__updateRemoveButtonEnabled()

self._VectorDataWidget__tableView.verticalHeader().setUpdatesEnabled( True )
self._VectorDataWidget__tableView.updateGeometry()

def setRowEditability( self, rowEditability ) :

self.__rowEditability = rowEditability

def getRowEditability( self ) :

return self.__rowEditability


class _Model( _VectorDataWidgetModel ) :

def __init__( self, data, parent=None, editable=True, header=None, toolTips=None, columnEditability=None, rowEditability=None ) :
_VectorDataWidgetModel.__init__( self, data, parent=parent, editable=editable, header=header, toolTips=toolTips, columnEditability=columnEditability )

self.__rowEditability = rowEditability

def flags( self, index ) :
result = (
QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemIsDragEnabled
)

if self.__editable :
rowEditable = (
self.__rowEditability is None or
index.row() >= len( self.__rowEditability ) or
self.__rowEditability[index.row()]
)
if rowEditable :
result |= QtCore.Qt.ItemIsEnabled
if self.__columnEditability is None or self.__columnEditability[index.column()] :
result |= QtCore.Qt.ItemIsEditable
else :
result |= QtCore.Qt.ItemIsEnabled

return result
97 changes: 97 additions & 0 deletions python/GafferUI/ArrayPlugValueWidget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import IECore

import Gaffer
import GafferUI


class ArrayPlugValueWidget( GafferUI.PlugValueWidget ) :

def __init__( self, plug, **kw ) :

sizeEditable = plug.minSize() != plug.maxSize()

self.__dataWidget = GafferUI.ArrayDataWidget(
header = True,
sizeEditable = sizeEditable,
)

GafferUI.PlugValueWidget.__init__( self, self.__dataWidget, plug, **kw )

self.__dataWidget.dataChangedSignal().connect( Gaffer.WeakMethod( self.__dataChanged ) )

def setHighlighted( self, highlighted ) :

GafferUI.PlugValueWidget.setHighlighted( self, highlighted )
self.__dataWidget.setHighlighted( highlighted )


@staticmethod
def _valuesForUpdate( plugs, auxiliaryPlugs ) :

assert( len( plugs ) == 1 )
plug = next( iter( plugs ) )
children = list( plug )
return {
"values" : [ c.getValue() for c in children ],
"rowEditable" : [ c.getInput() is None for c in children ],
}

def _updateFromValues( self, values, exception ) :

if not values :
return

vectorDataType = None

plug = self.getPlug()
if plug is not None :
vectorDataType = self.__vectorDataType( plug )

if vectorDataType is not None :
self.__dataWidget.setRowEditability( values["rowEditable"] )
self.__dataWidget.setData( vectorDataType( values["values"] ) )
self.__dataWidget.setErrored( exception is not None )

def _updateFromEditable( self ) :

self.__dataWidget.setEditable( self._editable() )

def __dataChanged( self, widget ) :

plug = self.getPlug()
if plug is None :
return

with self._blockedUpdateFromValues() :
with Gaffer.UndoScope( plug.ancestor( Gaffer.ScriptNode ) ) :
data = self.__dataWidget.getData()[0]
targetSize = max( plug.minSize(), min( plug.maxSize(), len( data ) ) )
plug.resize( targetSize )
for i, child in enumerate( plug ) :
if i < len( data ) and child.getInput() is None :
child.setValue( data[i] )

self._requestUpdateFromValues()

@staticmethod
def __vectorDataType( plug ) :

elementPrototype = plug.elementPrototype()
if elementPrototype is None :
return None
return ArrayPlugValueWidget.__plugTypeToVectorDataType.get( type( elementPrototype ) )

__plugTypeToVectorDataType = {
Gaffer.BoolPlug : IECore.BoolVectorData,
Gaffer.IntPlug : IECore.IntVectorData,
Gaffer.FloatPlug : IECore.FloatVectorData,
Gaffer.StringPlug : IECore.StringVectorData,
Gaffer.V2iPlug : IECore.V2iVectorData,
Gaffer.V2fPlug : IECore.V2fVectorData,
Gaffer.V3iPlug : IECore.V3iVectorData,
Gaffer.V3fPlug : IECore.V3fVectorData,
Gaffer.Color3fPlug : IECore.Color3fVectorData,
Gaffer.Color4fPlug : IECore.Color4fVectorData,
}

GafferUI.PlugValueWidget.registerType( Gaffer.ArrayPlug, ArrayPlugValueWidget )
2 changes: 2 additions & 0 deletions python/GafferUI/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ def __shiboken() :
from .ErrorDialogue import ErrorDialogue
from ._Variant import _Variant
from .VectorDataWidget import VectorDataWidget
from .ArrayDataWidget import ArrayDataWidget
from .PathVectorDataWidget import PathVectorDataWidget
from .ProgressBar import ProgressBar
from .SelectionMenu import SelectionMenu
Expand Down Expand Up @@ -203,6 +204,7 @@ def __shiboken() :
from .PathPlugValueWidget import PathPlugValueWidget
from .FileSystemPathPlugValueWidget import FileSystemPathPlugValueWidget
from .VectorDataPlugValueWidget import VectorDataPlugValueWidget
from .ArrayPlugValueWidget import ArrayPlugValueWidget
from .PathVectorDataPlugValueWidget import PathVectorDataPlugValueWidget
from .FileSystemPathVectorDataPlugValueWidget import FileSystemPathVectorDataPlugValueWidget
from .PlugWidget import PlugWidget
Expand Down
104 changes: 104 additions & 0 deletions python/GafferUITest/ArrayDataWidgetTest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import unittest
import imath

import IECore

import GafferTest
import GafferUI
import GafferUITest
from Qt import QtCore

class ArrayDataWidgetTest( GafferUITest.TestCase ) :

def testIndexing( self ) :

data = [
IECore.FloatVectorData( range( 0, 3 ) ),
IECore.Color3fVectorData( [ imath.Color3f( x ) for x in range( 0, 3 ) ] ),
IECore.StringVectorData( [ str( x ) for x in range( 0, 3 ) ] ),
IECore.IntVectorData( range( 0, 3 ) ),
IECore.V3fVectorData( [ imath.V3f( x ) for x in range( 0, 3 ) ] ),
]

w = GafferUI.ArrayDataWidget( data )

self.assertEqual( w.columnToDataIndex( 0 ), ( 0, -1 ) )
self.assertEqual( w.columnToDataIndex( 1 ), ( 1, 0 ) )
self.assertEqual( w.columnToDataIndex( 2 ), ( 1, 1 ) )
self.assertEqual( w.columnToDataIndex( 3 ), ( 1, 2 ) )
self.assertEqual( w.columnToDataIndex( 4 ), ( 1, 3 ) )
self.assertEqual( w.columnToDataIndex( 5 ), ( 2, -1 ) )
self.assertEqual( w.columnToDataIndex( 6 ), ( 3, -1 ) )
self.assertEqual( w.columnToDataIndex( 7 ), ( 4, 0 ) )
self.assertEqual( w.columnToDataIndex( 8 ), ( 4, 1 ) )
self.assertEqual( w.columnToDataIndex( 9 ), ( 4, 2 ) )

self.assertRaises( IndexError, w.columnToDataIndex, 10 )

self.assertEqual( w.dataToColumnIndex( 0, -1 ), 0 )
self.assertEqual( w.dataToColumnIndex( 1, 0 ), 1 )
self.assertEqual( w.dataToColumnIndex( 1, 1 ), 2 )
self.assertEqual( w.dataToColumnIndex( 1, 2 ), 3 )
self.assertEqual( w.dataToColumnIndex( 1, 3 ), 4 )
self.assertEqual( w.dataToColumnIndex( 2, -1 ), 5 )
self.assertEqual( w.dataToColumnIndex( 3, -1 ), 6 )
self.assertEqual( w.dataToColumnIndex( 4, 0 ), 7 )
self.assertEqual( w.dataToColumnIndex( 4, 1 ), 8 )
self.assertEqual( w.dataToColumnIndex( 4, 2 ), 9 )

self.assertRaises( IndexError, w.dataToColumnIndex, 6, 0 )

def testColumnEditability( self ) :

data = [
IECore.FloatVectorData( range( 0, 3 ) ),
IECore.Color3fVectorData( [ imath.Color3f( x ) for x in range( 0, 3 ) ] ),
IECore.StringVectorData( [ str( x ) for x in range( 0, 3 ) ] ),
]

w = GafferUI.ArrayDataWidget( data )

for i in range( 0, 6 ) :
self.assertEqual( w.getColumnEditable( i ), True )

self.assertRaises( IndexError, w.getColumnEditable, 7 )
self.assertRaises( IndexError, w.getColumnEditable, -1 )

w.setColumnEditable( 1, False )
self.assertEqual( w.getColumnEditable( 1 ), False )

data[0][0] += 1.0
w.setData( data )

for i in range( 0, 6 ) :
self.assertEqual( w.getColumnEditable( i ), i != 1 )

cs = GafferTest.CapturingSlot( w.dataChangedSignal() )
self.assertEqual( len( cs ), 0 )

w.setColumnEditable( 0, False )
w.setColumnEditable( 1, True )

# changing editability shouldn't emit dataChangedSignal.
self.assertEqual( len( cs ), 0 )

def testRowEditability( self ) :

data = [ IECore.IntVectorData( range( 0, 3 ) ) ]

w = GafferUI.ArrayDataWidget()
w.setRowEditability( [ False, True, False ] )
w.setData( data )

model = w._VectorDataWidget__model

index0 = model.index( 0, 0 )
index1 = model.index( 1, 0 )
index2 = model.index( 2, 0 )

self.assertFalse( bool( model.flags( index0 ) & QtCore.Qt.ItemIsEditable ) )
self.assertTrue( bool( model.flags( index1 ) & QtCore.Qt.ItemIsEditable ) )
self.assertFalse( bool( model.flags( index2 ) & QtCore.Qt.ItemIsEditable ) )

if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions python/GafferUITest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,6 @@
from .PopupWindowTest import PopupWindowTest
from .ColorChooserTest import ColorChooserTest
from .ContextTrackerTest import ContextTrackerTest
from .ArrayDataWidgetTest import ArrayDataWidgetTest
from .MetadataAlgoTest import MetadataAlgoTest
from .BreadCrumbsWidgetTest import BreadCrumbsWidgetTest
Loading