diff --git a/Changes.md b/Changes.md index 5727b48fbfb..fd43ddcc33c 100644 --- a/Changes.md +++ b/Changes.md @@ -6,6 +6,7 @@ Improvements - Group : Added `sets` plug, to control what sets the group belongs to. - USD : Added automatic render-time translation of UsdPreviewSurface shaders to Cycles. +- SetEditor : Added support for dragging a set name onto a node in the Graph Editor to create or modify a connected `SetFilter` node. Holding Shift while dragging will add to the set expression. Holding Control will remove from the set expression. Only set expressions with a simple list of sets are supported. Expressions with boolean or hierarchy operators are not supported. 1.4.0.0 (relative to 1.3.16.0) ======= diff --git a/python/GafferSceneUI/FilteredSceneProcessorUI.py b/python/GafferSceneUI/FilteredSceneProcessorUI.py index ef3265b3407..7e84dd651db 100644 --- a/python/GafferSceneUI/FilteredSceneProcessorUI.py +++ b/python/GafferSceneUI/FilteredSceneProcessorUI.py @@ -88,6 +88,7 @@ def __nodeGadget( node ) : nodeGadget = GafferUI.StandardNodeGadget( node ) GafferSceneUI.PathFilterUI.addObjectDropTarget( nodeGadget ) + GafferSceneUI.SetFilterUI.addSetDropTarget( nodeGadget ) return nodeGadget diff --git a/python/GafferSceneUI/PathFilterUI.py b/python/GafferSceneUI/PathFilterUI.py index 55e5b0097b6..637eb33e462 100644 --- a/python/GafferSceneUI/PathFilterUI.py +++ b/python/GafferSceneUI/PathFilterUI.py @@ -316,6 +316,9 @@ def __dragLeave( nodeGadget, event ) : global __originalDragPointer + if __originalDragPointer is None : + return False + GafferUI.Pointer.setCurrent( __originalDragPointer ) __originalDragPointer = None diff --git a/python/GafferSceneUI/SetEditor.py b/python/GafferSceneUI/SetEditor.py index 6dc344ecaa3..26012815f95 100644 --- a/python/GafferSceneUI/SetEditor.py +++ b/python/GafferSceneUI/SetEditor.py @@ -189,8 +189,12 @@ def __dragBegin( self, widget, event ) : # prevent the path itself from being dragged return IECore.StringVectorData() - GafferUI.Pointer.setCurrent( "paths" ) column = self.__pathListing.columnAt( imath.V2f( event.line.p0.x, event.line.p0.y ) ) + if isinstance( column, _GafferSceneUI._SetEditor.SetNameColumn ) : + GafferUI.Pointer.setCurrent( "sets" ) + else : + GafferUI.Pointer.setCurrent( "paths" ) + if column == self.__setMembersColumn : return IECore.StringVectorData( self.__getSetMembers( setNames ).paths() ) elif column == self.__selectedSetMembersColumn : diff --git a/python/GafferSceneUI/SetFilterUI.py b/python/GafferSceneUI/SetFilterUI.py index 3a84460ce88..f0842900120 100644 --- a/python/GafferSceneUI/SetFilterUI.py +++ b/python/GafferSceneUI/SetFilterUI.py @@ -34,9 +34,15 @@ # ########################################################################## +import enum +import imath + import Gaffer import GafferUI import GafferScene +import GafferSceneUI + +import IECore ########################################################################## # Metadata @@ -94,3 +100,163 @@ } ) + +########################################################################## +# NodeGadget drop handler +########################################################################## + +GafferUI.Pointer.registerPointer( "sets", GafferUI.Pointer( "pointerSets.png", imath.V2i( 53, 14 ) ) ) +GafferUI.Pointer.registerPointer( "addSets", GafferUI.Pointer( "pointerAddSets.png", imath.V2i( 53, 14 ) ) ) +GafferUI.Pointer.registerPointer( "removeSets", GafferUI.Pointer( "pointerRemoveSets.png", imath.V2i( 53, 14 ) ) ) +GafferUI.Pointer.registerPointer( "replaceSets", GafferUI.Pointer( "pointerReplaceSets.png", imath.V2i( 53, 14 ) ) ) + +__DropMode = enum.Enum( "__DropMode", [ "None_", "Add", "Remove", "Replace" ] ) + +__originalDragPointer = None + +def __setsPlug( node ) : + + for plug in Gaffer.Plug.InputRange( node ) : + if Gaffer.Metadata.value( plug, "ui:scene:acceptsSetNames" ) or Gaffer.Metadata.value( plug, "ui:scene:acceptsSetExpression" ) : + return plug + + return None + +def __editable( plug ) : + if Gaffer.MetadataAlgo.readOnly( plug ) or not plug.settable() : + return False + + if Gaffer.Metadata.value( plug, "ui:scene:acceptsSetNames" ) or Gaffer.Metadata.value( plug, "ui:scene:acceptsSetExpression" ) : + plugValue = plug.getValue() + if any( i in plugValue for i in [ "(", ")", "|", "-", "&"] ) : + return False + + plugTokens = plugValue.split( " " ) + if any( i in plugTokens for i in [ "in", "containing" ] ) : + return False + + return True + +def __dropMode( nodeGadget, event ) : + + setsPlug = __setsPlug( nodeGadget.node() ) + if setsPlug is None : + filter = None + if nodeGadget.node()["filter"].getInput() is not None : + filter = nodeGadget.node()["filter"].source().node() + if filter is None : + return __DropMode.Replace if __editable( nodeGadget.node()["filter"] ) else __DropMode.None_ + elif not isinstance( filter, GafferScene.SetFilter ) : + return __DropMode.None_ + setsPlug = filter["setExpression"] + + if not __editable( setsPlug ) : + return __DropMode.None_ + + if event.modifiers & event.Modifiers.Shift : + return __DropMode.Add + elif event.modifiers & event.Modifiers.Control : + return __DropMode.Remove + else : + return __DropMode.Replace + +def __dragEnter( nodeGadget, event ) : + + if not isinstance( event.data, IECore.StringVectorData ) : + return False + + if not ( + all( i.startswith( "/" ) for i in event.data ) or + event.sourceWidget.ancestor( GafferSceneUI.SetEditor ) is not None + ) : + return False + + if not len ( event.data ) : + return False + + if __dropMode( nodeGadget, event ) == __DropMode.None_ : + return False + + global __originalDragPointer + __originalDragPointer = GafferUI.Pointer.getCurrent() + + return True + +def __dragLeave( nodeGadget, event ) : + + global __originalDragPointer + + if __originalDragPointer is None : + return False + + GafferUI.Pointer.setCurrent( __originalDragPointer ) + __originalDragPointer = None + + return True + +def __dragMove( nodeGadget, event ) : + + global __originalDragPointer + if __originalDragPointer is None : + return False + + GafferUI.Pointer.setCurrent( __dropMode( nodeGadget, event ).name.lower() + "Sets" ) + + return True + +def __drop( nodeGadget, event ) : + + global __originalDragPointer + if __originalDragPointer is None : + return False + + setsPlug = __setsPlug( nodeGadget.node() ) + if setsPlug is None : + setsPlug = __setsPlug( nodeGadget.node()["filter"].source().node() ) + + dropSets = event.data + + dropMode = __dropMode( nodeGadget, event ) + if dropMode == __DropMode.Replace : + sets = sorted( dropSets ) + elif dropMode == __DropMode.Add : + sets = set( setsPlug.getValue().split( " " ) ) + sets.update( dropSets ) + sets = sorted( sets ) + else : + sets = set( setsPlug.getValue().split( " " ) ) + sets.difference_update( dropSets ) + sets = sorted( sets ) + + with Gaffer.UndoScope( nodeGadget.node().ancestor( Gaffer.ScriptNode ) ) : + + if setsPlug is None : + + setFilter = GafferScene.SetFilter() + nodeGadget.node().parent().addChild( setFilter ) + nodeGadget.node()["filter"].setInput( setFilter["out"] ) + + setsPlug = setFilter["setExpression"] + + setsPlug.setValue( " ".join( sets ) ) + + GafferUI.Pointer.setCurrent( __originalDragPointer ) + __originalDragPointer = None + + return True + +def addSetDropTarget( nodeGadget ) : + + nodeGadget.dragEnterSignal().connect( __dragEnter, scoped = False ) + nodeGadget.dragLeaveSignal().connect( __dragLeave, scoped = False ) + nodeGadget.dragMoveSignal().connect( __dragMove, scoped = False ) + nodeGadget.dropSignal().connect( __drop, scoped = False ) + +def __nodeGadget( setFilter ) : + + nodeGadget = GafferUI.StandardNodeGadget( setFilter ) + addSetDropTarget( nodeGadget ) + + return nodeGadget + +GafferUI.NodeGadget.registerNodeGadget( GafferScene.SetFilter, __nodeGadget ) diff --git a/python/GafferSceneUI/SetUI.py b/python/GafferSceneUI/SetUI.py index 501ef8926e9..18f2406cbd7 100644 --- a/python/GafferSceneUI/SetUI.py +++ b/python/GafferSceneUI/SetUI.py @@ -329,6 +329,7 @@ def __nodeGadget( node ) : nodeGadget = GafferUI.StandardNodeGadget( node ) GafferSceneUI.PathFilterUI.addObjectDropTarget( nodeGadget ) + GafferSceneUI.SetFilterUI.addSetDropTarget( nodeGadget ) return nodeGadget diff --git a/resources/graphics.py b/resources/graphics.py index c2ba048a6d5..3125d39a2f3 100644 --- a/resources/graphics.py +++ b/resources/graphics.py @@ -51,7 +51,11 @@ "objects", # \todo prefix with 'pointer' "addObjects", # \todo prefix with 'pointer' "removeObjects", # \todo prefix with 'pointer' - "replaceObjects" # \todo prefix with 'pointer' + "replaceObjects", # \todo prefix with 'pointer' + "pointerSets", + "pointerReplaceSets", + "pointerAddSets", + "pointerRemoveSets", ] }, diff --git a/resources/graphics.svg b/resources/graphics.svg index d28532bb367..4e6a1f1f5c3 100644 --- a/resources/graphics.svg +++ b/resources/graphics.svg @@ -804,7 +804,8 @@ y="983" style="font-size:24px;fill:#f4f4f4;fill-opacity:1" />POINTERS - PathFilterUI POINTERS - PathFilterUI, SetFilterUI + + style="opacity:1;fill:none;fill-opacity:1;stroke:none;stroke-width:3.12102342;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.10173225;stroke-opacity:1;paint-order:markers stroke fill" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +