Skip to content

Commit

Permalink
Merge pull request GafferHQ#6064 from johnhaddon/patternMatch
Browse files Browse the repository at this point in the history
Improved support for conditional enabling of nodes
  • Loading branch information
johnhaddon authored Oct 3, 2024
2 parents 2f9c54d + 91f02c8 commit 7777d77
Show file tree
Hide file tree
Showing 15 changed files with 578 additions and 64 deletions.
9 changes: 9 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
1.x.x.x (relative to 1.5.0.0a1)
=======

Features
--------

- PatternMatch : Added a new node for matching strings against wildcard patterns.

Improvements
------------

Expand All @@ -14,6 +19,7 @@ Improvements
- LightEditor, RenderPassEditor : History windows now use a context determined relative to the current focus node.
- NumericWidget : Added the ability to use <kbd>Ctrl</kbd> + scroll wheel to adjust values in the same manner as <kbd>Up</kbd> and <kbd>Down</kbd> (#6009). [^1]
- NodeEditor : Improved performance when showing a node with many colour plugs. Showing the Arnold `standard_surface` shader is now almost 2x faster. [^1]
- GraphEditor : Added colour coding to the strike-throughs drawn for disabled nodes. Black indicates that the node is always disabled, and yellow indicates that its `enabled` plug has an input connection, and therefore might be context-sensitive.
- ListContainer : Adding a child widget with non-default alignment no longer causes the container to take up all available space.

Fixes
Expand All @@ -26,6 +32,8 @@ Fixes
- Viewer : Fixed drawing of custom mesh light texture visualisers (#6002). [^1]
- GraphEditor :
- Fixed lingering error badges (#3820).
- Fixed <kbd>D</kbd> shortcut to respect read-only metadata on `enabled` plugs. Previously only metadata on the node itself was respected.
- Fixed <kbd>D</kbd> shortcut to handle multiple selection with some nodes enabled and some disabled. This will now consistently disabled all nodes if at least one is enabled, rather than toggling each individually.
- RenderPassEditor :
- Fixed history window to update on context changes, for example, when the current frame is changed.
- Fixed invalid `scene:path` context variables created by the history window. [^1]
Expand All @@ -35,6 +43,7 @@ Breaking Changes

- IECoreArnold : Added `messageContext` argument to `NodeAlgo::Converter` and `NodeAlgo::MotionConverter`.
- Instancer : Renamed `encapsulateInstanceGroups` plug to `encapsulate`. Encapsulation now produces a single capsule at the `.../instances` location, instead of capsules at each `.../instances/<prototypeName>` location.
- GraphGadget : Moved <kbd>D</kbd> shortcut handling to GraphEditor.

[^1]: To be omitted from 1.5.0.0 release notes.

Expand Down
80 changes: 80 additions & 0 deletions include/Gaffer/PatternMatch.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2024, Cinesite VFX Ltd. 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.
//
//////////////////////////////////////////////////////////////////////////

#pragma once

#include "Gaffer/ComputeNode.h"
#include "Gaffer/Export.h"
#include "Gaffer/StringPlug.h"
#include "Gaffer/TypeIds.h"

namespace Gaffer
{

class GAFFER_API PatternMatch : public ComputeNode
{

public :

PatternMatch( const std::string &name = defaultName<PatternMatch>() );
~PatternMatch() override;

GAFFER_NODE_DECLARE_TYPE( Gaffer::PatternMatch, PatternMatchTypeId, ComputeNode );

StringPlug *stringPlug();
const StringPlug *stringPlug() const;

StringPlug *patternPlug();
const StringPlug *patternPlug() const;

BoolPlug *enabledPlug() override;
const BoolPlug *enabledPlug() const override;

BoolPlug *matchPlug();
const BoolPlug *matchPlug() const;

void affects( const Plug *input, AffectedPlugsContainer &outputs ) const override;

protected :

void hash( const ValuePlug *output, const Context *context, IECore::MurmurHash &h ) const override;
void compute( ValuePlug *output, const Context *context) const override;

static size_t g_firstPlugIndex;

};

} // namespace Gaffer
1 change: 1 addition & 0 deletions include/Gaffer/TypeIds.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ enum TypeId
OptionalValuePlugTypeId = 110107,
CollectTypeId = 110108,
Box2fVectorDataPlugTypeId = 110109,
PatternMatchTypeId = 110110,

LastTypeId = 110159,

Expand Down
2 changes: 0 additions & 2 deletions include/GafferUI/GraphGadget.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,6 @@ class GAFFERUI_API GraphGadget : public ContainerGadget
void noduleRemoved( Nodule *nodule );
void nodeMetadataChanged( IECore::TypeId nodeTypeId, IECore::InternedString key, Gaffer::Node *node );

bool keyPressed( GadgetPtr gadget, const KeyEvent &event );

bool buttonPress( GadgetPtr gadget, const ButtonEvent &event );
bool buttonRelease( GadgetPtr gadget, const ButtonEvent &event );

Expand Down
5 changes: 3 additions & 2 deletions include/GafferUI/StandardNodeGadget.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ class GAFFERUI_API StandardNodeGadget : public NodeGadget
bool updateUserColor();
void updateMinWidth();
void updatePadding();
void updateStrikeThroughVisibility( const Gaffer::Plug *dirtiedPlug = nullptr );
void updateStrikeThroughState( const Gaffer::Plug *dirtiedPlug = nullptr );
void updateIcon();
bool updateShape();
void updateFocusGadgetVisibility();
Expand All @@ -170,7 +170,8 @@ class GAFFERUI_API StandardNodeGadget : public NodeGadget
void displayError( Gaffer::ConstPlugPtr plug, const std::string &message );

std::optional<bool> m_nodeEnabledInContextTracker;
bool m_strikeThroughVisible;
enum class StrikeThroughState : char { Invisible, Static, Dynamic };
StrikeThroughState m_strikeThroughState;
bool m_labelsVisibleOnHover;
// We accept drags onto the NodeGadget itself and
// use them to create a connection to the
Expand Down
70 changes: 70 additions & 0 deletions python/GafferTest/PatternMatchTest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
##########################################################################
#
# Copyright (c) 2024, Cinesite VFX Ltd. 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 Gaffer
import GafferTest

class PatternMatchTest( GafferTest.TestCase ) :

def test( self ) :

node = Gaffer.PatternMatch()

for string, pattern, expectedResult in [
( "a", "a", True ),
( "a", "b", False ),
( "b", "b", True ),
( "a", "b a", True ),
( "a1", "a[0-9]", True ),
( "ab", "a[0-9]", False ),
( "a", "*", True ),
] :
with self.subTest( string = string, pattern = pattern ) :
node["string"].setValue( string )
node["pattern"].setValue( pattern )
self.assertEqual( node["match"].getValue(), expectedResult )

def testDisabling( self ) :

node = Gaffer.PatternMatch()
self.assertTrue( node["match"].getValue() )
node["enabled"].setValue( False )
self.assertFalse( node["match"].getValue() )

if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions python/GafferTest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ def inCI( platforms = set() ) :
from .ThreadMonitorTest import ThreadMonitorTest
from .CollectTest import CollectTest
from .ProcessTest import ProcessTest
from .PatternMatchTest import PatternMatchTest

from .IECorePreviewTest import *

Expand Down
64 changes: 59 additions & 5 deletions python/GafferUI/GraphEditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,13 +238,13 @@ def plugDirectionsWalk( gadget ) :
@classmethod
def appendEnabledPlugMenuDefinitions( cls, graphEditor, node, menuDefinition ) :

enabledPlug = node.enabledPlug() if isinstance( node, Gaffer.DependencyNode ) else None
enabledPlug = cls.__enabledPlugForEditing( node )
if enabledPlug is not None :
menuDefinition.append( "/EnabledDivider", { "divider" : True } )
menuDefinition.append(
"/Enabled",
{
"command" : functools.partial( cls.__setEnabled, node ),
"command" : functools.partial( cls.__setValue, enabledPlug ),
"checkBox" : enabledPlug.getValue(),
"active" : enabledPlug.settable() and not Gaffer.MetadataAlgo.readOnly( enabledPlug )
}
Expand Down Expand Up @@ -423,6 +423,24 @@ def __keyPress( self, widget, event ) :
elif event.key == "Tab" :
self.__popupNodeMenu()
return True
elif event.key == "D" and not event.modifiers :
enabledPlugs = set()
for node in self.scriptNode().selection() :
if not isinstance( node, Gaffer.DependencyNode ) :
continue
if self.graphGadget().nodeGadget( node ) is None :
continue
enabledPlug = self.__enabledPlugForEditing( node )
if enabledPlug is None or not enabledPlug.settable() or Gaffer.MetadataAlgo.readOnly( enabledPlug ) :
continue
enabledPlugs.add( enabledPlug )

enabled = any( enabledPlug.getValue() for enabledPlug in enabledPlugs )
with Gaffer.UndoScope( self.scriptNode() ) :
for enabledPlug in enabledPlugs :
enabledPlug.setValue( not enabled )

return True

return False

Expand Down Expand Up @@ -723,10 +741,10 @@ def __setNodeOutputConnectionsVisible( cls, graphGadget, node, value ) :
graphGadget.setNodeOutputConnectionsMinimised( node, not value )

@classmethod
def __setEnabled( cls, node, value ) :
def __setValue( cls, plug, value ) :

with Gaffer.UndoScope( node.ancestor( Gaffer.ScriptNode ) ) :
node.enabledPlug().setValue( value )
with Gaffer.UndoScope( plug.ancestor( Gaffer.ScriptNode ) ) :
plug.setValue( value )

@staticmethod
def __childrenViewable( node ) :
Expand All @@ -744,4 +762,40 @@ def __select( node ) :
node.scriptNode().selection().clear()
node.scriptNode().selection().add( node )

@staticmethod
def __enabledPlugForEditing( node ) :

if not isinstance( node, Gaffer.DependencyNode ) :
return None

enabledPlug = node.enabledPlug()
if enabledPlug is None :
return None

if enabledPlug.getInput() is None :
return enabledPlug

# Plug has an input, but maybe we can edit that instead.

source = enabledPlug.source()
if not Gaffer.PlugAlgo.dependsOnCompute( source ) :
return source

# Plug depends on a compute, but maybe we can enable/disable
# that node instead? This only works if the node outputs False
# when disabled - things like PatternMatch.

if source.defaultValue() != False :
return enabledPlug

sourceNode = source.node()
if not isinstance( sourceNode, Gaffer.DependencyNode ) :
return enabledPlug

sourceEnabledPlug = sourceNode.enabledPlug()
if sourceEnabledPlug is not None and sourceNode.correspondingInput( source ) is None :
return sourceEnabledPlug

return enabledPlug

GafferUI.Editor.registerType( "GraphEditor", GraphEditor )
Loading

0 comments on commit 7777d77

Please sign in to comment.