From 3e169ebac5009221ff044eed18e6736fe18a839d Mon Sep 17 00:00:00 2001 From: John Haddon Date: Mon, 21 Sep 2026 10:50:54 +0100 Subject: [PATCH] AlembicScene : Add reading of a synthetic "__cameras" set This is needed to make cameras work cleanly in Gaffer. Also document a preexisting bug that meant other sets have never been loadable in Gaffer, and document why it doesn't seem to matter. --- Changes | 3 + .../src/IECoreAlembic/AlembicScene.cpp | 74 ++++++++++++++++++- .../test/IECoreAlembic/AlembicSceneTest.py | 9 +++ 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 8ef2451314..c661d444d2 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,10 @@ 10.7.x.x (relative to 10.7.1.3) ======== +Fixes +----- +- AlembicScene : Added automatic reading of a `__cameras` set, containing the locations of all cameras. 10.7.1.3 (relative to 10.7.1.2) ======== diff --git a/contrib/IECoreAlembic/src/IECoreAlembic/AlembicScene.cpp b/contrib/IECoreAlembic/src/IECoreAlembic/AlembicScene.cpp index 0f5428487a..ad95721b2e 100644 --- a/contrib/IECoreAlembic/src/IECoreAlembic/AlembicScene.cpp +++ b/contrib/IECoreAlembic/src/IECoreAlembic/AlembicScene.cpp @@ -61,6 +61,8 @@ #include "boost/tokenizer.hpp" +#include "tbb/blocked_range.h" +#include "tbb/parallel_for.h" #include "tbb/spin_mutex.h" #include "fmt/format.h" @@ -1064,6 +1066,21 @@ class AlembicScene::AlembicReader : public AlembicIO if( !m_xform ) { + // Root. + setNames.push_back( "__cameras" ); + // Note : We should not be returning early when + // `includeDescendantSets == true`. But in practice this is + // irrelevant for two reasons : + // + // 1. We've never received an Alembic file containing + // collections - do any DCCs support them? + // 2. We don't implement `writeSet()` at the root + // location, which is the only location where Gaffer tries + // to write sets (this is also the only location Gaffer + // tries to read sets from). Nobody has complained so far. + // + // Since recursing would be fairly expensive and has no current + // use, we don't bother. return setNames; } @@ -1096,13 +1113,26 @@ class AlembicScene::AlembicReader : public AlembicIO // ensure our set names are unique std::sort( setNames.begin(), setNames.end() ); return NameList( setNames.begin(), std::unique( setNames.begin(), setNames.end() ) ); - } IECore::PathMatcher readSet( const Name &name, bool includeDescendantSets, const Canceller *canceller ) const { + IECore::PathMatcher pathMatcher; + if( !m_xform ) + { + if( name == "__cameras" ) + { + tbb::this_task_arena::isolate( + [&] { + tbb::task_group_context taskGroupContext( tbb::task_group_context::isolated ); + pathMatcher = recurseReadCamerasSet( taskGroupContext, canceller ); + } + ); + } + return pathMatcher; + } + SceneInterface::Path prefix; - PathMatcher pathMatcher; recurseReadSet( prefix, name, pathMatcher, includeDescendantSets, canceller ); return pathMatcher; @@ -1266,6 +1296,46 @@ class AlembicScene::AlembicReader : public AlembicIO } } + IECore::PathMatcher recurseReadCamerasSet( tbb::task_group_context &taskGroupContext, const Canceller *canceller ) const + { + Canceller::check( canceller ); + + PathMatcher result; + tbb::spin_mutex m; + + if( m_objectReader && Alembic::AbcGeom::ICamera::matches( m_objectReader->object().getMetaData() ) ) + { + result.addPath( std::vector() ); + } + + NameList children; + childNames( children ); + + tbb::parallel_for( + + tbb::blocked_range( 0, children.size() ), + + [&]( const tbb::blocked_range &r ) + { + for( size_t i = r.begin(); i != r.end(); ++i ) + { + auto &childName = children[i]; + ConstAlembicIOPtr c = child( childName, SceneInterface::ThrowIfMissing ); + PathMatcher childSet = static_cast( c.get() )->recurseReadCamerasSet( taskGroupContext, canceller ); + if( !childSet.isEmpty() ) + { + tbb::spin_mutex::scoped_lock lock( m ); + result.addPaths( childSet, { childName } ); + } + } + }, + taskGroupContext + + ); + + return result; + } + IECore::PathMatcherDataPtr readLocalSet( const Name &name ) const { PathMatcherDataPtr pathMatcher = new PathMatcherData(); diff --git a/contrib/IECoreAlembic/test/IECoreAlembic/AlembicSceneTest.py b/contrib/IECoreAlembic/test/IECoreAlembic/AlembicSceneTest.py index 3cc171db12..e24e25b5fe 100644 --- a/contrib/IECoreAlembic/test/IECoreAlembic/AlembicSceneTest.py +++ b/contrib/IECoreAlembic/test/IECoreAlembic/AlembicSceneTest.py @@ -539,6 +539,15 @@ def testCamera( self ) : c = a.child( "persp" ).readObject( 0 ) self.assertTrue( isinstance( c, IECoreScene.Camera ) ) + def testCamerasSet( self ) : + + root = IECoreScene.SceneInterface.create( os.path.join( os.path.dirname( __file__ ), "data", "animatedCube.abc" ), IECore.IndexedIO.OpenMode.Read ) + self.assertEqual( root.setNames(), [ "__cameras" ] ) + self.assertEqual( root.readSet( "__cameras" ), IECore.PathMatcher( [ "/front", "/persp", "/side", "/top" ] ) ) + + child = root.child( "pCube1" ) + self.assertNotIn( "__cameras", child.setNames() ) + def testLinearCurves( self ) : a = IECoreScene.SceneInterface.create( os.path.join( os.path.dirname( __file__ ), "data", "curves.abc" ), IECore.IndexedIO.OpenMode.Read )