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

Adding a dynamic attribute "sceneInterface:linkHash" computed by LinkedScene #19

Closed
wants to merge 2 commits into from
Closed
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
11 changes: 11 additions & 0 deletions include/IECore/LinkedScene.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#define IECORE_LINKEDSCENE_H

#include "IECore/SampledSceneInterface.h"
#include "IECore/MurmurHash.h"

namespace IECore
{
Expand All @@ -56,6 +57,9 @@ IE_CORE_FORWARDDECLARE( LinkedScene );
/// (1) calls to the function writeLink() or
/// (2) calls to the function writeAttribute( LinkedScene::linkSceneAttribute, LinkedScene::linkAttributeData(), ... ).
/// Note that the link can be animated, allowing for time remapped animations.
/// When reading, this object will return a dynamically computed attribute "SceneInterface:linkHash", which will be
/// a MurmurHash (as a StringData) that uniquely identify a given linked file (with any time remapping on parent links) and
/// could be used by readers for automatic instantiation. This attribute is never returned by attributeNames() function.
class LinkedScene : public SampledSceneInterface
{
public :
Expand All @@ -65,6 +69,9 @@ class LinkedScene : public SampledSceneInterface
/// Equals to "SceneInterface:link" and it's the name given to the link attribute that is recognized
// by this class when expanding linked scenes.
static const Name &linkAttribute;
/// Equals to "SceneInterface:linkHash" and it's the name given to the attribute that uniquely identify
/// the target linked scene with any time remapping (recursivelly applied on all levels).
static const Name &linkHashAttribute;

/// When the open mode is Read it expands the links and only the const methods may be used and the
/// when the open mode is Write, only the non-const methods may be used and
Expand Down Expand Up @@ -154,6 +161,10 @@ class LinkedScene : public SampledSceneInterface
double remappedLinkTime( double time ) const;
double remappedLinkTimeAtSample( size_t sampleIndex ) const;

// creates the hash that represents the link at the current location.
Object *computeLinkHashAttribute() const;
void computeLinkHash( MurmurHash &h ) const;

SceneInterfacePtr m_mainScene;
ConstSceneInterfacePtr m_linkedScene;
unsigned int m_rootLinkDepth;
Expand Down
84 changes: 84 additions & 0 deletions src/IECore/LinkedScene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ static IndexedIO::Description<FileIndexedIO> extensionDescription( ".lscc" );
static SceneInterface::FileFormatDescription<LinkedScene> registrar(".lscc", IndexedIO::Read | IndexedIO::Write);

const SceneInterface::Name &LinkedScene::linkAttribute = InternedString( "sceneInterface:link" );
const SceneInterface::Name &LinkedScene::linkHashAttribute = InternedString( "sceneInterface:linkHash" );
const InternedString LinkedScene::g_fileNameLinkAttribute("fileName");
const InternedString LinkedScene::g_rootLinkAttribute("root");
const InternedString LinkedScene::g_timeAttribute("time");
Expand Down Expand Up @@ -437,6 +438,19 @@ bool LinkedScene::hasAttribute( const Name &name ) const
return false;
}

if ( name == linkHashAttribute )
{
if ( m_linkedScene )
{
if ( m_atLink )
{
return true;
}
return m_linkedScene->hasAttribute(name);
}
return false;
}

if ( m_linkedScene && !m_atLink )
{
return m_linkedScene->hasAttribute(name);
Expand Down Expand Up @@ -469,6 +483,11 @@ void LinkedScene::attributeNames( NameList &attrs ) const

size_t LinkedScene::numAttributeSamples( const Name &name ) const
{
if ( name == linkHashAttribute )
{
return 1;
}

if (!m_sampled)
{
return 0;
Expand All @@ -492,6 +511,11 @@ size_t LinkedScene::numAttributeSamples( const Name &name ) const

double LinkedScene::attributeSampleTime( const Name &name, size_t sampleIndex ) const
{
if ( name == linkHashAttribute )
{
return 0;
}

if (!m_sampled)
{
throw Exception( "attributeSampleTime not supported: LinkedScene is pointing to a non-sampled scene!" );
Expand All @@ -515,6 +539,13 @@ double LinkedScene::attributeSampleTime( const Name &name, size_t sampleIndex )

double LinkedScene::attributeSampleInterval( const Name &name, double time, size_t &floorIndex, size_t &ceilIndex ) const
{
if ( name == linkHashAttribute )
{
floorIndex = 0;
ceilIndex = 0;
return 0;
}

if (!m_sampled)
{
throw Exception( "attributeSampleInterval not supported: LinkedScene is pointing to a non-sampled scene!" );
Expand All @@ -536,8 +567,56 @@ double LinkedScene::attributeSampleInterval( const Name &name, double time, size
}
}

void LinkedScene::computeLinkHash( MurmurHash &h ) const
{
if ( m_linkedScene )
{
if ( m_timeRemapped )
{
/// \todo consider baking the time remap hash as an attribute at each link location
/// \todo consider checking for identity remapping and ignoring the hash in that case.
size_t linkAttributeSamples = numAttributeSamples( linkAttribute );
const SampledSceneInterface *mainScene = static_cast<const SampledSceneInterface*>(m_mainScene.get());
for ( size_t s = 0; s < linkAttributeSamples; s++ )
{
h.append( mainScene->attributeSampleTime( linkAttribute, s ) );
h.append( remappedLinkTimeAtSample( s ) );
}
}

const LinkedScene *castedLinkedScene = runTimeCast< const LinkedScene >( m_linkedScene );
if ( castedLinkedScene )
{
castedLinkedScene->computeLinkHash( h );
}
else
{
linkAttributeData( m_linkedScene )->hash( h );
}
}
else
{
linkAttributeData( m_mainScene )->hash( h );
}
}

Object *LinkedScene::computeLinkHashAttribute() const
{
MurmurHash hash;
computeLinkHash(hash);

StringData *d = new StringData();
d->writable() = hash.toString();
return d;
}

ObjectPtr LinkedScene::readAttributeAtSample( const Name &name, size_t sampleIndex ) const
{
if ( name == linkHashAttribute )
{
return computeLinkHashAttribute();
}

if (!m_sampled)
{
throw Exception( "readAttributeAtSample not supported: LinkedScene is pointing to a non-sampled scene!" );
Expand All @@ -561,6 +640,11 @@ ObjectPtr LinkedScene::readAttributeAtSample( const Name &name, size_t sampleInd

ObjectPtr LinkedScene::readAttribute( const Name &name, double time ) const
{
if ( name == linkHashAttribute )
{
return computeLinkHashAttribute();
}

if ( m_linkedScene && !m_atLink )
{
if ( m_timeRemapped )
Expand Down
1 change: 1 addition & 0 deletions src/IECorePython/LinkedSceneBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ void bindLinkedScene()
.def( "linkAttributeData", linkAttributeData )
.def( "linkAttributeData", retimedLinkAttributeData ).staticmethod( "linkAttributeData" )
.def_readonly("linkAttribute", &LinkedScene::linkAttribute )
.def_readonly("linkHashAttribute", &LinkedScene::linkHashAttribute )
;
}

Expand Down
144 changes: 144 additions & 0 deletions test/IECore/LinkedSceneTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,150 @@ def testTimeRemapping( self ):
self.assertEqual( A2.readTransformAtSample(1), IECore.M44dData( IECore.M44d.createTranslated( IECore.V3d( 1.5, 0, 0 ) ) ) )
self.assertEqual( A2.readTransformAtSample(2), IECore.M44dData( IECore.M44d.createTranslated( IECore.V3d( 2, 0, 0 ) ) ) )

def testLinkHash( self ):

m = IECore.SceneCache( "test/IECore/data/sccFiles/animatedSpheres.scc", IECore.IndexedIO.OpenMode.Read )

l = IECore.LinkedScene( "/tmp/test.lscc", IECore.IndexedIO.OpenMode.Write )
# save animated spheres with no time offset, but less samples
i0 = l.createChild("instance0")
i0.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( m, 1.0 ), 1.0 )
i0.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( m, 2.0 ), 2.0 )
# save animated spheres with same speed and no offset, same samples (time remapping is identity)
i1 = l.createChild("instance1")
i1.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( m, 0.0 ), 0.0 )
i1.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( m, 1.0 ), 1.0 )
i1.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( m, 2.0 ), 2.0 )
i1.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( m, 3.0 ), 3.0 )
# save animated spheres with same speed and with offset, same samples (time remapping is identity)
i2 = l.createChild("instance2")
i2.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( m, 0.0 ), 1.0 )
i2.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( m, 1.0 ), 2.0 )
i2.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( m, 2.0 ), 3.0 )
i2.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( m, 3.0 ), 4.0 )
# save non-animated link
i3 = l.createChild("instance3")
i3.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( m ), 0.0 )
# create a transform and have the same links as instance0 and instance3
B = l.createChild("B")
i0b = B.createChild("instance0")
i0b.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( m, 1.0 ), 1.0 )
i0b.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( m, 2.0 ), 2.0 )
i3b = B.createChild("instance3")
i3b.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( m ), 0.0 )
C = l.createChild("C")

del i0, i1, i2, i3, i0b, i3b, B, C, l

# open first linked scene for reading...
l = IECore.LinkedScene( "/tmp/test.lscc", IECore.IndexedIO.OpenMode.Read )
i0 = l.child("instance0")
i1 = l.child("instance1")
i2 = l.child("instance2")
i3 = l.child("instance3")
B = l.child("B")
i0b = B.child("instance0")
i3b = B.child("instance3")
C = l.child("C")

# create a second level of linked scene
l2 = IECore.LinkedScene( "/tmp/test2.lscc", IECore.IndexedIO.OpenMode.Write )
# save animated spheres with no time offset, but less samples
j0 = l2.createChild("link0")
j0.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( l ), 0.0 )
j1 = l2.createChild("link1")
j1.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( l, 1 ), 0.0 )
j2 = l2.createChild("link2")
j2.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( i0 ), 0.0 )
j3 = l2.createChild("link3")
j3.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( i0, 1 ), 0.0 )
j4 = l2.createChild("link4")
j4.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( i1 ), 0.0 )
j5 = l2.createChild("link5")
j5.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( i1, 1 ), 0.0 )
j6 = l2.createChild("link6")
j6.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( i2 ), 0.0 )
j7 = l2.createChild("link7")
j7.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( i2, 1 ), 0.0 )
j8 = l2.createChild("link8")
j8.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( i3 ), 0.0 )
j9 = l2.createChild("link9")
j9.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( i3, 1 ), 0.0 )
j10 = l2.createChild("link10")
j10.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( i0b ), 0.0 )
j11 = l2.createChild("link11")
j11.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( i0b, 1 ), 0.0 )
j12 = l2.createChild("link12")
j12.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( i3b ), 0.0 )
j13 = l2.createChild("link13")
j13.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( i3b, 1 ), 0.0 )
j14 = l2.createChild("link14")
j14.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( B ), 0.0 )
j15 = l2.createChild("link15")
j15.writeAttribute( IECore.LinkedScene.linkAttribute, IECore.LinkedScene.linkAttributeData( C ), 0.0 )

del j0,j1,j2,j3,j4,j5,j6,j7,j8,j9,j10,j11,j12,j13,j14,j15,l2

# open second linked scene for reading..
l2 = IECore.LinkedScene( "/tmp/test2.lscc", IECore.IndexedIO.OpenMode.Read )
j0 = l2.child("link0")
j1 = l2.child("link1")
j2 = l2.child("link2")
j3 = l2.child("link3")
j4 = l2.child("link4")
j5 = l2.child("link5")
j6 = l2.child("link6")
j7 = l2.child("link7")
j8 = l2.child("link8")
j9 = l2.child("link9")
j10 = l2.child("link10")
j11 = l2.child("link11")
j12 = l2.child("link12")
j13 = l2.child("link13")
j14 = l2.child("link14")
j15 = l2.child("link15")

self.assertTrue( j0.hasAttribute( IECore.LinkedScene.linkHashAttribute ) )
self.assertTrue( j13.hasAttribute( IECore.LinkedScene.linkHashAttribute ) )
self.assertTrue( j14.hasAttribute( IECore.LinkedScene.linkHashAttribute ) )
self.assertTrue( j15.hasAttribute( IECore.LinkedScene.linkHashAttribute ) )
self.assertFalse( l.hasAttribute( IECore.LinkedScene.linkHashAttribute ) )
self.assertFalse( l2.hasAttribute( IECore.LinkedScene.linkHashAttribute ) )
self.assertFalse( B.hasAttribute( IECore.LinkedScene.linkHashAttribute ) )
self.assertFalse( C.hasAttribute( IECore.LinkedScene.linkHashAttribute ) )

hashes = map( lambda s: s.readAttribute( IECore.LinkedScene.linkHashAttribute, 0 ).value, [ i0, i1, i2, i3, i0b, i3b, j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15 ] )

matchingIndices = [
[ 0, 4, 8, 16 ], # instance0 indices
[ 1, 10 ], # instance1 indices
[ 2, 12 ], # instance2 indices
[ 3, 5, 14, 18 ], # instance3 indices
[ 9, 17 ], # time remapped instance0
[ 15, 19 ], # time remapped instance3
]

for indices in enumerate( matchingIndices ) :
h = hashes[indices[0]]
matchingHashes = map( lambda (i,h): h, filter( lambda (i,h): i in indices, enumerate(hashes) ) )
mismatchingHashes = set(hashes).difference( matchingHashes )
self.assertTrue( h in matchingHashes )
self.assertFalse( h in mismatchingHashes )

# test all the other hashes
otherIndices = list()
for i in xrange(0,len(hashes)):
for mid in xrange(0,len(matchingIndices)):
if i in matchingIndices[mid] :
break
else :
otherIndices.append(i)

for i in otherIndices :
h = hashes[i]
mismatchingHashes = list(hashes)
del mismatchingHashes[i]
self.assertFalse( h in mismatchingHashes )

def testReading( self ):

Expand Down