From e54b11ac9de32214471494505ff9b50b20868ce2 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Thu, 31 Aug 2023 16:53:07 +0100 Subject: [PATCH 1/3] SceneAlgo : Add `findAll()` method --- Changes.md | 1 + include/GafferScene/SceneAlgo.h | 10 +++++++ include/GafferScene/SceneAlgo.inl | 25 ++++++++++++++++ python/GafferSceneTest/SceneAlgoTest.py | 34 ++++++++++++++++++++++ src/GafferSceneModule/SceneAlgoBinding.cpp | 17 +++++++++++ 5 files changed, 87 insertions(+) diff --git a/Changes.md b/Changes.md index 6b7665170d1..c9a1b2a0d6c 100644 --- a/Changes.md +++ b/Changes.md @@ -4,6 +4,7 @@ API --- +- SceneAlgo : Added `findAll()` method, for finding all scene locations matching a predicate. - ThreadState : Added `process()` method. - Process : Added const overload for `handleException()` method. The non-const version will be removed in future. diff --git a/include/GafferScene/SceneAlgo.h b/include/GafferScene/SceneAlgo.h index 01d8dac2d8e..a9697dd8362 100644 --- a/include/GafferScene/SceneAlgo.h +++ b/include/GafferScene/SceneAlgo.h @@ -144,6 +144,16 @@ void filteredParallelTraverse( const ScenePlug *scene, const FilterPlug *filterP template void filteredParallelTraverse( const ScenePlug *scene, const IECore::PathMatcher &filter, ThreadableFunctor &f, const ScenePlug::ScenePath &root = ScenePlug::ScenePath() ); +/// Searching +/// ========= + +/// Returns all the locations for which `predicate( scene, path )` returns `true`. +/// +/// > Caution : The search is performed in parallel, so `predicate` must be safe to call +/// concurrently from multiple threads. +template +IECore::PathMatcher findAll( const ScenePlug *scene, Predicate &&predicate, const ScenePlug::ScenePath &root = ScenePlug::ScenePath() ); + /// Globals /// ======= diff --git a/include/GafferScene/SceneAlgo.inl b/include/GafferScene/SceneAlgo.inl index d442fd5982f..723ec13b501 100644 --- a/include/GafferScene/SceneAlgo.inl +++ b/include/GafferScene/SceneAlgo.inl @@ -36,6 +36,7 @@ #include "Gaffer/Context.h" +#include "tbb/enumerable_thread_specific.h" #include "tbb/parallel_for.h" namespace GafferScene @@ -179,6 +180,30 @@ void filteredParallelTraverse( const ScenePlug *scene, const IECore::PathMatcher parallelTraverse( scene, ff, root ); } +template +IECore::PathMatcher findAll( const ScenePlug *scene, Predicate &&predicate, const ScenePlug::ScenePath &root ) +{ + tbb::enumerable_thread_specific threadResults; + + auto f = [&] ( const ScenePlug *scene, const ScenePlug::ScenePath &path ) { + if( predicate( scene, path ) ) + { + threadResults.local().addPath( path ); + } + return true; + }; + + parallelTraverse( scene, f, root ); + + return threadResults.combine( + [] ( const IECore::PathMatcher &a, const IECore::PathMatcher &b ) { + IECore::PathMatcher c = a; + c.addPaths( b ); + return c; + } + ); +} + } // namespace SceneAlgo } // namespace GafferScene diff --git a/python/GafferSceneTest/SceneAlgoTest.py b/python/GafferSceneTest/SceneAlgoTest.py index 50740f67ecd..49ab75a53f5 100644 --- a/python/GafferSceneTest/SceneAlgoTest.py +++ b/python/GafferSceneTest/SceneAlgoTest.py @@ -1829,6 +1829,40 @@ def testValidateName( self ) : with self.assertRaises( RuntimeError ) : GafferScene.SceneAlgo.validateName( badName ) + def testFindAll( self ) : + + plane = GafferScene.Plane() + + planeFilter = GafferScene.PathFilter() + planeFilter["paths"].setValue( IECore.StringVectorData( [ "/plane" ] ) ) + + sphere = GafferScene.Sphere() + + instancer = GafferScene.Instancer() + instancer["in"].setInput( plane["out"] ) + instancer["prototypes"].setInput( sphere["out"] ) + instancer["filter"].setInput( planeFilter["out"] ) + + self.assertEqual( + GafferScene.SceneAlgo.findAll( + instancer["out"], + lambda scene, path : scene["transform"].getValue().translation().x > 0 + ), + IECore.PathMatcher( [ + "/plane/instances/sphere/1", + "/plane/instances/sphere/3", + ] ) + ) + + self.assertEqual( + GafferScene.SceneAlgo.findAll( + instancer["out"], + lambda scene, path : scene["transform"].getValue().translation().x > 0, + root = "/not/a/location" + ), + IECore.PathMatcher() + ) + def tearDown( self ) : GafferSceneTest.SceneTestCase.tearDown( self ) diff --git a/src/GafferSceneModule/SceneAlgoBinding.cpp b/src/GafferSceneModule/SceneAlgoBinding.cpp index 0e01557c5ed..382d075552e 100644 --- a/src/GafferSceneModule/SceneAlgoBinding.cpp +++ b/src/GafferSceneModule/SceneAlgoBinding.cpp @@ -128,6 +128,20 @@ IECore::MurmurHash matchingPathsHashWrapper2( const IECore::PathMatcher &filter, return SceneAlgo::matchingPathsHash( filter, &scene ); } +IECore::PathMatcher findAllWrapper( const ScenePlug &scene, object predicate, const ScenePlug::ScenePath &root ) +{ + IECorePython::ScopedGILRelease gilRelease; + return SceneAlgo::findAll( + &scene, + [&] ( ConstScenePlugPtr scene, const ScenePlug::ScenePath &path ) { + const std::string pathString = ScenePlug::pathToString( path ); + IECorePython::ScopedGILLock gilLock; + return predicate( boost::const_pointer_cast( scene ), pathString ); + }, + root + ); +} + Imath::V2f shutterWrapper( const IECore::CompoundObject &globals, const ScenePlug &scene ) { IECorePython::ScopedGILRelease r; @@ -319,6 +333,9 @@ void bindSceneAlgo() def( "matchingPaths", &matchingPathsWrapper4 ); def( "matchingPathsHash", &matchingPathsHashWrapper1, ( arg( "filter" ), arg( "scene" ), arg( "root" ) = "/" ) ); def( "matchingPathsHash", &matchingPathsHashWrapper2, ( arg( "filter" ), arg( "scene" ) ) ); + + def( "findAll", &findAllWrapper, ( arg( "scene" ), arg( "predicate" ), arg( "root" ) = "/" ) ); + def( "shutter", &shutterWrapper ); def( "setExists", &setExistsWrapper ); def( From aca8ca1457d51517b9aa8b2bc6c1eb6cca0db7b8 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Fri, 1 Sep 2023 16:37:09 +0100 Subject: [PATCH 2/3] SceneAlgo : Add `findAllWithAttribute()` method I did spend a while thinking about the possibility of promoting the existing private `findAttributes()` function from SceneAlgo.cpp instead. But that's a fairly esoteric thing that runs a predicate functor on the full attributes, and for `findAllWithAttribute()` my goal was to provide something simpler which could be used from Python without the overhead of a Python functor. --- Changes.md | 4 +- include/GafferScene/SceneAlgo.h | 4 ++ python/GafferSceneTest/SceneAlgoTest.py | 51 ++++++++++++++++++++++ src/GafferScene/SceneAlgo.cpp | 20 +++++++++ src/GafferSceneModule/SceneAlgoBinding.cpp | 7 +++ 5 files changed, 85 insertions(+), 1 deletion(-) diff --git a/Changes.md b/Changes.md index c9a1b2a0d6c..7d5f32dcddd 100644 --- a/Changes.md +++ b/Changes.md @@ -4,7 +4,9 @@ API --- -- SceneAlgo : Added `findAll()` method, for finding all scene locations matching a predicate. +- SceneAlgo : + - Added `findAll()` method, for finding all scene locations matching a predicate. + - Added `findAllWithAttribute()` method, for finding all scene locations with a particular attribute. - ThreadState : Added `process()` method. - Process : Added const overload for `handleException()` method. The non-const version will be removed in future. diff --git a/include/GafferScene/SceneAlgo.h b/include/GafferScene/SceneAlgo.h index a9697dd8362..b62ecc74ffc 100644 --- a/include/GafferScene/SceneAlgo.h +++ b/include/GafferScene/SceneAlgo.h @@ -154,6 +154,10 @@ void filteredParallelTraverse( const ScenePlug *scene, const IECore::PathMatcher template IECore::PathMatcher findAll( const ScenePlug *scene, Predicate &&predicate, const ScenePlug::ScenePath &root = ScenePlug::ScenePath() ); +/// Returns all the locations which have a local attribute called `name`. If `value` is specified, then only +/// returns locations where the attribute has that value. +GAFFERSCENE_API IECore::PathMatcher findAllWithAttribute( const ScenePlug *scene, IECore::InternedString name, const IECore::Object *value = nullptr, const ScenePlug::ScenePath &root = ScenePlug::ScenePath() ); + /// Globals /// ======= diff --git a/python/GafferSceneTest/SceneAlgoTest.py b/python/GafferSceneTest/SceneAlgoTest.py index 49ab75a53f5..68b938d79ca 100644 --- a/python/GafferSceneTest/SceneAlgoTest.py +++ b/python/GafferSceneTest/SceneAlgoTest.py @@ -1863,6 +1863,57 @@ def testFindAll( self ) : IECore.PathMatcher() ) + def testFindAllWithAttribute( self ) : + + # /group + # /light1 + # /light2 + + light1 = GafferSceneTest.TestLight() + light1["name"].setValue( "light1" ) + + group = GafferScene.Group() + group["in"][0].setInput( light1["out"] ) + + light2 = GafferSceneTest.TestLight() + light2["name"].setValue( "light2" ) + + parent = GafferScene.Parent() + parent["in"].setInput( group["out"] ) + parent["children"][0].setInput( light2["out"] ) + parent["parent"].setValue( "/" ) + + self.assertEqual( + GafferScene.SceneAlgo.findAllWithAttribute( parent["out"], "light:mute" ), + IECore.PathMatcher() + ) + + light1["mute"]["enabled"].setValue( True ) + light1["mute"]["value"].setValue( True ) + + light2["mute"]["enabled"].setValue( True ) + light2["mute"]["value"].setValue( False ) + + self.assertEqual( + GafferScene.SceneAlgo.findAllWithAttribute( parent["out"], "light:mute" ), + IECore.PathMatcher( [ "/group/light1", "/light2" ] ) + ) + + self.assertEqual( + GafferScene.SceneAlgo.findAllWithAttribute( parent["out"], "light:mute", value = IECore.BoolData( True ) ), + IECore.PathMatcher( [ "/group/light1" ] ) + ) + + self.assertEqual( + GafferScene.SceneAlgo.findAllWithAttribute( parent["out"], "light:mute", value = IECore.BoolData( False ) ), + IECore.PathMatcher( [ "/light2" ] ) + ) + + self.assertEqual( + GafferScene.SceneAlgo.findAllWithAttribute( parent["out"], "light:mute", root = "/group" ), + IECore.PathMatcher( [ "/group/light1" ] ) + ) + def tearDown( self ) : GafferSceneTest.SceneTestCase.tearDown( self ) diff --git a/src/GafferScene/SceneAlgo.cpp b/src/GafferScene/SceneAlgo.cpp index d209e733061..9a65e4f47d2 100644 --- a/src/GafferScene/SceneAlgo.cpp +++ b/src/GafferScene/SceneAlgo.cpp @@ -238,6 +238,26 @@ IECore::MurmurHash GafferScene::SceneAlgo::matchingPathsHash( const PathMatcher return IECore::MurmurHash( f.m_h1Accum, f.m_h2Accum ); } +////////////////////////////////////////////////////////////////////////// +// Searching +////////////////////////////////////////////////////////////////////////// + +IECore::PathMatcher GafferScene::SceneAlgo::findAllWithAttribute( const ScenePlug *scene, IECore::InternedString name, const IECore::Object *value, const ScenePlug::ScenePath &root ) +{ + return findAll( + scene, + [&] ( const ScenePlug *scene, const ScenePlug::ScenePath &path ) { + ConstCompoundObjectPtr attributes = scene->attributesPlug()->getValue(); + if( const Object *attribute = attributes->member( name ) ) + { + return !value || attribute->isEqualTo( value ); + } + return false; + }, + root + ); +} + ////////////////////////////////////////////////////////////////////////// // Globals ////////////////////////////////////////////////////////////////////////// diff --git a/src/GafferSceneModule/SceneAlgoBinding.cpp b/src/GafferSceneModule/SceneAlgoBinding.cpp index 382d075552e..6d7c26ff2af 100644 --- a/src/GafferSceneModule/SceneAlgoBinding.cpp +++ b/src/GafferSceneModule/SceneAlgoBinding.cpp @@ -142,6 +142,12 @@ IECore::PathMatcher findAllWrapper( const ScenePlug &scene, object predicate, co ); } +IECore::PathMatcher findAllWithAttributeWrapper( const ScenePlug &scene, InternedString name, const Object *value, const ScenePlug::ScenePath &root ) +{ + IECorePython::ScopedGILRelease gilRelease; + return SceneAlgo::findAllWithAttribute( &scene, name, value, root ); +} + Imath::V2f shutterWrapper( const IECore::CompoundObject &globals, const ScenePlug &scene ) { IECorePython::ScopedGILRelease r; @@ -335,6 +341,7 @@ void bindSceneAlgo() def( "matchingPathsHash", &matchingPathsHashWrapper2, ( arg( "filter" ), arg( "scene" ) ) ); def( "findAll", &findAllWrapper, ( arg( "scene" ), arg( "predicate" ), arg( "root" ) = "/" ) ); + def( "findAllWithAttribute", &findAllWithAttributeWrapper, ( arg( "scene" ), arg( "name" ), arg( "value" ) = object(), arg( "root" ) = "/" ) ); def( "shutter", &shutterWrapper ); def( "setExists", &setExistsWrapper ); From ec138145ad750a817bbd0a4f4fafb8009a34975c Mon Sep 17 00:00:00 2001 From: John Haddon Date: Mon, 11 Sep 2023 13:14:27 +0100 Subject: [PATCH 3/3] DeleteObject : Fix Windows build --- src/GafferScene/DeleteObject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GafferScene/DeleteObject.cpp b/src/GafferScene/DeleteObject.cpp index 5cf27ad2d11..df0dc265aa3 100644 --- a/src/GafferScene/DeleteObject.cpp +++ b/src/GafferScene/DeleteObject.cpp @@ -45,7 +45,7 @@ using namespace IECore; using namespace Gaffer; using namespace GafferScene; -GAFFER_NODE_DEFINE_TYPE( DeleteObject ); +GAFFER_NODE_DEFINE_TYPE( GafferScene::DeleteObject ); size_t DeleteObject::g_firstPlugIndex = 0;