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

Annotation Improvements #6256

Merged
merged 7 commits into from
Feb 10, 2025
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
7 changes: 7 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ Features
- May be disabled entirely with `GafferScene.SceneAlgo.deregisterRenderAdaptor( "USDPointInstancerAdaptor" )`.
- Viewer : Added "Expand USD Instancers" item to the Expansion menu. Defaults to on for all renderers except OpenGL.
- PromotePointInstances : Added a new node for selectively converting a subset of a USD PointInstancer to expanded "hero" geometry.
- Annotations :
- Added copy and paste of annotations. The right-click menu of an annotation allows you to copy the annotation. Pressing <kbd>Control</kbd> + <kbd>V</kbd> in the Node Editor will paste the annotation to the selected nodes.
- Double clicking on an annotation now pops up the annotation editor dialogue.

Improvements
------------
Expand Down Expand Up @@ -43,6 +46,10 @@ API

- EditScopeAlgo : Added `renameRenderPass()` and `renameRenderPassNonEditableReason()` functions.
- SceneAlgo : Added `parallelGatherLocations()` function.
- GraphGadget : Added `annotationsGadget()` function.
- MetadataAlgo : Added `annotations()` variant accepting `Gaffer::Metadata::RegistrationTypes`. The default is `All` to match existing behavior and the previous `annotations()` variant is deprecated.
- AnnotationsGadget : Added `annotationAt()` function.
- AnnotationUI : Added `contextMenuSignal()` allowing customisations to the context menu for annotations.

1.5.4.1 (relative to 1.5.4.0)
=======
Expand Down
3 changes: 3 additions & 0 deletions include/Gaffer/MetadataAlgo.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#pragma once

#include "Gaffer/Export.h"
#include "Gaffer/Metadata.h"
#include "Gaffer/Node.h"

#include "IECore/SimpleTypedData.h"
Expand Down Expand Up @@ -180,7 +181,9 @@ struct GAFFER_API Annotation
GAFFER_API void addAnnotation( Node *node, const std::string &name, const Annotation &annotation, bool persistent = true );
GAFFER_API Annotation getAnnotation( const Node *node, const std::string &name, bool inheritTemplate = false );
GAFFER_API void removeAnnotation( Node *node, const std::string &name );
[[deprecated( "Use alternative form with `RegistrationTypes` instead")]]
GAFFER_API void annotations( const Node *node, std::vector<std::string> &names );
GAFFER_API std::vector<std::string> annotations( const Node *node, Metadata::RegistrationTypes types = Metadata::RegistrationTypes::All );

/// Pass `user = false` for annotations not intended for creation directly by the user.
GAFFER_API void addAnnotationTemplate( const std::string &name, const Annotation &annotation, bool user = true );
Expand Down
16 changes: 16 additions & 0 deletions include/GafferUI/AnnotationsGadget.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@

#include "GafferUI/Gadget.h"

#include "IECoreGL/Selector.h"

#include "IECore/StringAlgo.h"

#include <unordered_map>
Expand Down Expand Up @@ -82,6 +84,11 @@ class GAFFERUI_API AnnotationsGadget : public Gadget

bool acceptsParent( const GraphComponent *potentialParent ) const override;

// Identifies an annotation by the node and it's name.
using AnnotationIdentifier = std::pair<const Gaffer::Node *, std::string>;
// Returns the node and annotation name under the specified line.
std::optional<AnnotationIdentifier> annotationAt( const IECore::LineSegment3f &lineInGadgetSpace ) const;

protected :

// Protected constructor and friend status so only GraphGadget can
Expand Down Expand Up @@ -148,6 +155,15 @@ class GAFFERUI_API AnnotationsGadget : public Gadget
// When we are hidden, we want to cancel all background tasks.
void visibilityChanged();

// Map associating an `IECoreGL::Selector::IDRender` entry with a `AnnotationIndex`.
using AnnotationBufferMap = std::unordered_map<unsigned int, AnnotationIdentifier>;

// If given an `AnnotationBufferMap` and `Selector`, draws all annotations
// with a unique `IDRender` index per annotation and fills `selectionIds`.
// If they are not given, no modification to the selection buffer IDs are
// made (all annotations have the ID for this widget).
void renderAnnotations( const Style *style, AnnotationBufferMap *selectionIds = nullptr ) const;

struct StandardAnnotation : public Gaffer::MetadataAlgo::Annotation
{
StandardAnnotation( const Gaffer::MetadataAlgo::Annotation &a, IECore::InternedString name ) : Annotation( a ), name( name ) {}
Expand Down
5 changes: 5 additions & 0 deletions include/GafferUI/GraphGadget.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

#pragma once

#include "GafferUI/AnnotationsGadget.h"
#include "GafferUI/ContainerGadget.h"
#include "GafferUI/ContextTracker.h"

Expand Down Expand Up @@ -133,6 +134,10 @@ class GAFFERUI_API GraphGadget : public ContainerGadget
AuxiliaryConnectionsGadget *auxiliaryConnectionsGadget();
const AuxiliaryConnectionsGadget *auxiliaryConnectionsGadget() const;

/// Returns the Gadget responsible for drawing annotations.
AnnotationsGadget *annotationsGadget();
const AnnotationsGadget *annotationsGadget() const;

/// Finds all the upstream NodeGadgets connected to the specified node
/// and appends them to the specified vector. Returns the new size of the vector.
/// \note Here "upstream" nodes are defined as nodes at the end of input
Expand Down
12 changes: 12 additions & 0 deletions python/GafferTest/MetadataAlgoTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,18 @@ def testAnnotations( self ) :
Gaffer.MetadataAlgo.Annotation( "abc", imath.Color3f( 0, 1, 0 ) )
)

def testAnnotationRegistrationTypes( self ) :

n = Gaffer.Node()
Gaffer.MetadataAlgo.addAnnotation( n, "persistent", Gaffer.MetadataAlgo.Annotation( "Always here", imath.Color3f( 1, 0, 0 ) ) )
Gaffer.MetadataAlgo.addAnnotation( n, "nonPersistent", Gaffer.MetadataAlgo.Annotation( "Ephemeral", imath.Color3f( 0, 1, 0 ) ), False )

Types = Gaffer.Metadata.RegistrationTypes
self.assertEqual( Gaffer.MetadataAlgo.annotations( n, Types.InstancePersistent ), ["persistent"] )
self.assertEqual( Gaffer.MetadataAlgo.annotations( n, Types.InstanceNonPersistent ), ["nonPersistent"] )
for t in [ Types.None_, Types.TypeId, Types.TypeIdDescendant ] :
self.assertEqual( Gaffer.MetadataAlgo.annotations( n, t ), [] )

def testAnnotationWithoutColor( self ) :

n = Gaffer.Node()
Expand Down
120 changes: 120 additions & 0 deletions python/GafferUI/AnnotationsUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import functools
import re
import imath
import weakref

import IECore

Expand Down Expand Up @@ -72,6 +73,125 @@ def __annotate( node, name, menu ) :
dialogue = __AnnotationsDialogue( node, name )
dialogue.wait( parentWindow = menu.ancestor( GafferUI.Window ) )

# A signal emitted when a popup menu for an annotation is about to be shown.
# This provides an opportunity to customize the menu from external code.
# The signature for slots is ( menuDefinition, annotation, persistent ) where
# `annotation` is a tuple of `( node, name )` and `persistent` indicates whether
# or not the annotation will be serialised with the script. Slots should modify
# `menuDefinition` in place.

__contextMenuSignal = Gaffer.Signals.Signal3()

def contextMenuSignal() :
return __contextMenuSignal

def __annotationIsPersistent( annotation ) :

node, name = annotation

persistentAnnotations = Gaffer.MetadataAlgo.annotations( node, Gaffer.Metadata.RegistrationTypes.InstancePersistent )
return name in persistentAnnotations

def __buttonPress( editorWeakRef, annotationsGadget, event ) :

if event.buttons & event.Buttons.Right :
annotation = annotationsGadget.annotationAt( event.line )
if annotation is None :
return False

menuDefinition = IECore.MenuDefinition()
contextMenuSignal()( menuDefinition, annotation, __annotationIsPersistent( annotation ) )

global __popupMenu
__popupMenu = GafferUI.Menu( menuDefinition )
__popupMenu.popup( editorWeakRef() )

return True

return True # Needed for `__buttonDoubleClick()` to fire

def __buttonDoubleClick( editorWeakRef, annotationsGadget, event ) :

if event.buttons == event.Buttons.Left :
annotation = annotationsGadget.annotationAt( event.line )
if annotation is None or not __annotationIsPersistent( annotation ) :
return False

node, name = annotation

__annotate( node, name, editorWeakRef() )

return True

return False

def __clipboardIsAnnotation( clipboard ) :

return (
isinstance( clipboard, IECore.CompoundData ) and
[ "color", "name", "text" ] == sorted( clipboard.keys() ) and
isinstance( clipboard["color"], IECore.Color3fData ) and
isinstance( clipboard["name"], IECore.StringData ) and
isinstance( clipboard["text"], IECore.StringData )
)

def __keyPress( editor, event ) :

if event.key == "V" and event.modifiers == event.modifiers.Control :
scriptNode = editor.scriptNode()
clipboard = scriptNode.ancestor( Gaffer.ApplicationRoot ).getClipboardContents()

if __clipboardIsAnnotation( clipboard ) :
with Gaffer.UndoScope( scriptNode ) :
editorSelection = [ i for i in scriptNode.selection() if editor.graphGadget().nodeGadget( i ) is not None ]
for n in editorSelection :
Gaffer.MetadataAlgo.addAnnotation(
n,
clipboard["name"].value,
Gaffer.MetadataAlgo.Annotation( clipboard["text"].value, clipboard["color"].value )
)
return True

return False

def __copyAnnotation( node, name ) :

annotation = Gaffer.MetadataAlgo.getAnnotation( node, name, True )

data = IECore.CompoundData(
{
"color" : IECore.Color3fData( annotation.color() ),
"name" : IECore.StringData( name ),
"text" : IECore.StringData( annotation.text() ),
}
)

node.scriptNode().ancestor( Gaffer.ApplicationRoot ).setClipboardContents( data )

def __contextMenu( menuDefinition, annotation, persistent ) :

node, name = annotation
menuDefinition.append(
"/Copy",
{
"command" : functools.partial( __copyAnnotation, node, name ),
"active" : persistent
},
)

def __graphEditorCreated( editor ) :
editor.graphGadget().annotationsGadget().buttonPressSignal().connect(
functools.partial( __buttonPress, weakref.ref( editor ) )
)
editor.graphGadget().annotationsGadget().buttonDoubleClickSignal().connect(
functools.partial( __buttonDoubleClick, weakref.ref( editor ) )
)
editor.keyPressSignal().connect( __keyPress )

GafferUI.GraphEditor.instanceCreatedSignal().connect( __graphEditorCreated )

contextMenuSignal().connect( __contextMenu )

class _AnnotationsHighlighter( GafferUI.CodeWidget.Highlighter ) :

__substitutionRe = re.compile( r"(\{[^}]+\})" )
Expand Down
4 changes: 2 additions & 2 deletions python/GafferUI/GraphEditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ def __preRender( self, viewportGadget ) :
def __annotationsMenu( self ) :

graphGadget = self.graphGadget()
annotationsGadget = graphGadget["__annotations"]
annotationsGadget = graphGadget.annotationsGadget()

annotations = Gaffer.MetadataAlgo.annotationTemplates() + [ "user", annotationsGadget.untemplatedAnnotations ]
visiblePattern = annotationsGadget.getVisibleAnnotations()
Expand Down Expand Up @@ -734,7 +734,7 @@ def appendMenuItem( annotation, label = None ) :

def __setVisibleAnnotations( self, unused, annotations ) :

annotationsGadget = self.graphGadget()["__annotations"]
annotationsGadget = self.graphGadget().annotationsGadget()
pattern = " ".join( a.replace( " ", r"\ " ) for a in annotations )
annotationsGadget.setVisibleAnnotations( pattern )

Expand Down
Loading