diff --git a/Changes.md b/Changes.md index 11cd49c2d32..7428702e313 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(