Skip to content

Commit f334a2f

Browse files
committed
Merge pull request #155 from andrewkaufman/customAttributes
Custom Attributes in Houdini
2 parents c2eb50f + 58caa00 commit f334a2f

File tree

5 files changed

+158
-36
lines changed

5 files changed

+158
-36
lines changed

include/IECoreHoudini/HoudiniScene.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,15 @@ class HoudiniScene : public IECore::SceneInterface
117117

118118
typedef boost::function<bool (const OP_Node *)> HasFn;
119119
typedef boost::function<IECore::ConstObjectPtr (const OP_Node *, double &)> ReadFn;
120+
typedef boost::function<IECore::ConstObjectPtr (const OP_Node *, const Name &, double &)> ReadAttrFn;
120121
typedef boost::function<bool (const OP_Node *, const Name &)> HasTagFn;
121122
typedef boost::function<void (const OP_Node *, NameList &, bool)> ReadTagsFn;
123+
typedef boost::function<void (const OP_Node *, NameList &)> ReadNamesFn;
122124

123125
// Register callbacks for custom named attributes.
124-
// The has function will be called during hasAttribute and it stops in the first one that returns true.
125-
// The read method is called if the has method returns true, so it should return a valid Object pointer or raise an Exception.
126-
static void registerCustomAttribute( const Name &attrName, HasFn hasFn, ReadFn readFn );
126+
// The names function will be called during attributeNames and hasAttribute.
127+
// The read method is called if the names method returns the expected attribute, so it should return a valid Object pointer or raise an Exception.
128+
static void registerCustomAttributes( ReadNamesFn namesFn, ReadAttrFn readFn );
127129

128130
// Register callbacks for nodes to define custom tags
129131
// The functions will be called during hasTag and readTags.
@@ -152,10 +154,10 @@ class HoudiniScene : public IECore::SceneInterface
152154
const char *contentPathValue() const;
153155

154156
/// Struct for registering readers for custom Attributes.
155-
struct CustomReader
157+
struct CustomAttributeReader
156158
{
157-
HasFn m_has;
158-
ReadFn m_read;
159+
ReadNamesFn m_names;
160+
ReadAttrFn m_read;
159161
};
160162

161163
/// Struct for registering readers for custom Tags.
@@ -165,7 +167,7 @@ class HoudiniScene : public IECore::SceneInterface
165167
ReadTagsFn m_read;
166168
};
167169

168-
static std::map<Name, CustomReader> &customAttributeReaders();
170+
static std::vector<CustomAttributeReader> &customAttributeReaders();
169171
static std::vector<CustomTagReader> &customTagReaders();
170172

171173
UT_String m_nodePath;

include/IECoreHoudini/OBJ_SceneCacheTransform.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ class OBJ_SceneCacheTransform : public OBJ_SceneCacheNode<OBJ_SubNet>
132132
};
133133
static HoudiniSceneAddOn g_houdiniSceneAddOn;
134134

135-
static bool hasLink( const OP_Node *node );
136-
static IECore::ConstObjectPtr readLink( const OP_Node *node, double time );
135+
static void attributeNames( const OP_Node *node, IECore::SceneInterface::NameList &attrs );
136+
static IECore::ConstObjectPtr readAttribute( const OP_Node *node, const IECore::SceneInterface::Name &name, double time );
137137
static bool hasTag( const OP_Node *node, const IECore::SceneInterface::Name &tag );
138138
static void readTags( const OP_Node *node, IECore::SceneInterface::NameList &tags, bool includeChildren );
139139

src/IECoreHoudini/HoudiniScene.cpp

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -306,10 +306,17 @@ void HoudiniScene::writeTransform( const Data *transform, double time )
306306

307307
bool HoudiniScene::hasAttribute( const Name &name ) const
308308
{
309-
std::map<Name, CustomReader>::const_iterator it = customAttributeReaders().find( name );
310-
if ( it != customAttributeReaders().end() )
309+
OP_Node *node = retrieveNode();
310+
311+
const std::vector<CustomAttributeReader> &attributeReaders = customAttributeReaders();
312+
for ( std::vector<CustomAttributeReader>::const_iterator it = attributeReaders.begin(); it != attributeReaders.end(); ++it )
311313
{
312-
return it->second.m_has( retrieveNode() );
314+
NameList names;
315+
it->m_names( node, names );
316+
if ( std::find( names.begin(), names.end(), name ) != names.end() )
317+
{
318+
return true;
319+
}
313320
}
314321

315322
return false;
@@ -318,21 +325,29 @@ bool HoudiniScene::hasAttribute( const Name &name ) const
318325
void HoudiniScene::attributeNames( NameList &attrs ) const
319326
{
320327
attrs.clear();
321-
for ( std::map<Name, CustomReader>::const_iterator it = customAttributeReaders().begin(); it != customAttributeReaders().end(); ++it )
328+
329+
OP_Node *node = retrieveNode();
330+
331+
const std::vector<CustomAttributeReader> &attributeReaders = customAttributeReaders();
332+
for ( std::vector<CustomAttributeReader>::const_iterator it = attributeReaders.begin(); it != attributeReaders.end(); ++it )
322333
{
323-
if ( it->second.m_has( retrieveNode() ) )
324-
{
325-
attrs.push_back( it->first );
326-
}
334+
NameList names;
335+
it->m_names( node, names );
336+
attrs.insert( attrs.end(), names.begin(), names.end() );
327337
}
328338
}
329339

330340
ConstObjectPtr HoudiniScene::readAttribute( const Name &name, double time ) const
331341
{
332-
std::map<Name, CustomReader>::const_iterator it = customAttributeReaders().find( name );
333-
if ( it != customAttributeReaders().end() )
342+
OP_Node *node = retrieveNode();
343+
344+
const std::vector<CustomAttributeReader> &attributeReaders = customAttributeReaders();
345+
for ( std::vector<CustomAttributeReader>::const_iterator it = attributeReaders.begin(); it != attributeReaders.end(); ++it )
334346
{
335-
return it->second.m_read( retrieveNode(), time );
347+
if ( IECore::ConstObjectPtr object = it->m_read( node, name, time ) )
348+
{
349+
return object;
350+
}
336351
}
337352

338353
return 0;
@@ -999,17 +1014,17 @@ const char *HoudiniScene::contentPathValue() const
9991014
return name.c_str();
10001015
}
10011016

1002-
void HoudiniScene::registerCustomAttribute( const Name &attrName, HasFn hasFn, ReadFn readFn )
1017+
void HoudiniScene::registerCustomAttributes( ReadNamesFn namesFn, ReadAttrFn readFn )
10031018
{
1004-
CustomReader r;
1005-
r.m_has = hasFn;
1019+
CustomAttributeReader r;
1020+
r.m_names = namesFn;
10061021
r.m_read = readFn;
1007-
customAttributeReaders()[attrName] = r;
1022+
customAttributeReaders().push_back( r );
10081023
}
10091024

1010-
std::map<SceneInterface::Name, HoudiniScene::CustomReader> &HoudiniScene::customAttributeReaders()
1025+
std::vector<HoudiniScene::CustomAttributeReader> &HoudiniScene::customAttributeReaders()
10111026
{
1012-
static std::map<SceneInterface::Name, HoudiniScene::CustomReader> readers;
1027+
static std::vector<HoudiniScene::CustomAttributeReader> readers;
10131028
return readers;
10141029
}
10151030

src/IECoreHoudini/OBJ_SceneCacheTransform.cpp

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -422,28 +422,36 @@ OBJ_SceneCacheTransform::HoudiniSceneAddOn OBJ_SceneCacheTransform::g_houdiniSce
422422

423423
OBJ_SceneCacheTransform::HoudiniSceneAddOn::HoudiniSceneAddOn()
424424
{
425-
HoudiniScene::registerCustomAttribute( LinkedScene::linkAttribute, OBJ_SceneCacheTransform::hasLink, OBJ_SceneCacheTransform::readLink );
425+
HoudiniScene::registerCustomAttributes( OBJ_SceneCacheTransform::attributeNames, OBJ_SceneCacheTransform::readAttribute );
426426
HoudiniScene::registerCustomTags( OBJ_SceneCacheTransform::hasTag, OBJ_SceneCacheTransform::readTags );
427427
}
428428

429-
bool OBJ_SceneCacheTransform::hasLink( const OP_Node *node )
429+
void OBJ_SceneCacheTransform::attributeNames( const OP_Node *node, SceneInterface::NameList &attrs )
430430
{
431431
// make sure its a SceneCacheNode
432432
if ( !node->hasParm( pFile.getToken() ) || !node->hasParm( pRoot.getToken() ) )
433433
{
434-
return false;
434+
return;
435+
}
436+
437+
const SceneCacheNode<OP_Node> *sceneNode = reinterpret_cast< const SceneCacheNode<OP_Node>* >( node );
438+
/// \todo: do we need to ensure the file exists first?
439+
ConstSceneInterfacePtr scene = OBJ_SceneCacheTransform::scene( sceneNode->getFile(), sceneNode->getPath() );
440+
if ( !scene )
441+
{
442+
return;
435443
}
436444

445+
scene->attributeNames( attrs );
446+
437447
const char *expanded = pExpanded.getToken();
438448
if ( node->hasParm( expanded ) && !node->evalInt( expanded, 0, 0 ) )
439449
{
440-
return true;
450+
attrs.push_back( LinkedScene::linkAttribute );
441451
}
442-
443-
return false;
444452
}
445453

446-
IECore::ConstObjectPtr OBJ_SceneCacheTransform::readLink( const OP_Node *node, double time )
454+
IECore::ConstObjectPtr OBJ_SceneCacheTransform::readAttribute( const OP_Node *node, const SceneInterface::Name &name, double time )
447455
{
448456
// make sure its a SceneCacheNode
449457
if ( !node->hasParm( pFile.getToken() ) || !node->hasParm( pRoot.getToken() ) )
@@ -459,7 +467,18 @@ IECore::ConstObjectPtr OBJ_SceneCacheTransform::readLink( const OP_Node *node, d
459467
return 0;
460468
}
461469

462-
return LinkedScene::linkAttributeData( scene, time );
470+
if ( name == LinkedScene::linkAttribute )
471+
{
472+
const char *expanded = pExpanded.getToken();
473+
if ( node->hasParm( expanded ) && !node->evalInt( expanded, 0, 0 ) )
474+
{
475+
return LinkedScene::linkAttributeData( scene, time );
476+
}
477+
478+
return 0;
479+
}
480+
481+
return scene->readAttribute( name, time );
463482
}
464483

465484
bool OBJ_SceneCacheTransform::hasTag( const OP_Node *node, const SceneInterface::Name &tag )

test/IECoreHoudini/SceneCacheTest.py

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,78 @@ def testLiveTags( self ) :
12251225
self.assertTrue( scene.hasTag( tag ) )
12261226
self.assertFalse( scene.hasTag( "notATag" ) )
12271227

1228+
def writeAttributeSCC( self ) :
1229+
1230+
scene = self.writeSCC()
1231+
sc1 = scene.child( str( 1 ) )
1232+
sc2 = sc1.child( str( 2 ) )
1233+
sc3 = sc2.child( str( 3 ) )
1234+
sc1.writeAttribute( "label", IECore.StringData( "a" ), 0 )
1235+
sc1.writeAttribute( "color", IECore.Color3dData( IECore.Color3d( 0.5 ) ), 0 )
1236+
sc2.writeAttribute( "label", IECore.StringData( "b" ), 0 )
1237+
sc2.writeAttribute( "material", IECore.StringData( "rubber" ), 0 )
1238+
sc3.writeAttribute( "label", IECore.StringData( "c" ), 0 )
1239+
sc3.writeAttribute( "animColor", IECore.Color3dData( IECore.Color3d( 0 ) ), 0 )
1240+
sc3.writeAttribute( "animColor", IECore.Color3dData( IECore.Color3d( 0.5 ) ), 0.5 )
1241+
sc3.writeAttribute( "animColor", IECore.Color3dData( IECore.Color3d( 1 ) ), 1 )
1242+
1243+
return scene
1244+
1245+
def testLiveAttributes( self ) :
1246+
1247+
self.writeAttributeSCC()
1248+
1249+
xform = self.xform()
1250+
xform.parm( "hierarchy" ).set( IECoreHoudini.SceneCacheNode.Hierarchy.SubNetworks )
1251+
xform.parm( "depth" ).set( IECoreHoudini.SceneCacheNode.Depth.AllDescendants )
1252+
1253+
# its a link before it is expanded
1254+
scene = IECoreHoudini.HoudiniScene( xform.path() )
1255+
self.assertEqual( scene.attributeNames(), [ IECore.LinkedScene.linkAttribute ] )
1256+
self.assertTrue( scene.hasAttribute( IECore.LinkedScene.linkAttribute ) )
1257+
self.assertEqual(
1258+
scene.readAttribute( IECore.LinkedScene.linkAttribute, 0 ),
1259+
IECore.CompoundData( {
1260+
"time" : IECore.DoubleData( 0 ),
1261+
"fileName" : IECore.StringData( "test/test.scc" ),
1262+
"root" : IECore.InternedStringVectorData( [] )
1263+
} )
1264+
)
1265+
1266+
# the link disapears once expanded
1267+
xform.parm( "expand" ).pressButton()
1268+
self.assertEqual( scene.attributeNames(), [] )
1269+
self.assertFalse( scene.hasAttribute( IECore.LinkedScene.linkAttribute ) )
1270+
self.assertEqual( scene.readAttribute( IECore.LinkedScene.linkAttribute, 0 ), None )
1271+
1272+
# nodes expose their attributes
1273+
a = scene.child( "1" )
1274+
self.assertEqual( sorted(a.attributeNames()), [ "color", "label", "sceneInterface:animatedObjectPrimVars" ] )
1275+
for attr in a.attributeNames() :
1276+
self.assertTrue( a.hasAttribute( attr ) )
1277+
self.assertFalse( a.hasAttribute( "material" ) )
1278+
self.assertEqual( a.readAttribute( "label", 0 ), IECore.StringData( "a" ) )
1279+
self.assertEqual( a.readAttribute( "color", 0 ), IECore.Color3dData( IECore.Color3d( 0.5 ) ) )
1280+
1281+
b = a.child( "2" )
1282+
self.assertEqual( sorted(b.attributeNames()), [ "label", "material", "sceneInterface:animatedObjectPrimVars" ] )
1283+
for attr in b.attributeNames() :
1284+
self.assertTrue( b.hasAttribute( attr ) )
1285+
self.assertFalse( b.hasAttribute( "color" ) )
1286+
self.assertEqual( b.readAttribute( "label", 0 ), IECore.StringData( "b" ) )
1287+
self.assertEqual( b.readAttribute( "material", 0 ), IECore.StringData( "rubber" ) )
1288+
1289+
c = b.child( "3" )
1290+
self.assertEqual( sorted(c.attributeNames()), [ "animColor", "label", "sceneInterface:animatedObjectPrimVars" ] )
1291+
for attr in c.attributeNames() :
1292+
self.assertTrue( c.hasAttribute( attr ) )
1293+
self.assertFalse( c.hasAttribute( "color" ) )
1294+
self.assertFalse( c.hasAttribute( "material" ) )
1295+
self.assertEqual( c.readAttribute( "label", 0 ), IECore.StringData( "c" ) )
1296+
self.assertEqual( c.readAttribute( "animColor", 0 ), IECore.Color3dData( IECore.Color3d( 0 ) ) )
1297+
self.assertEqual( c.readAttribute( "animColor", 0.5 ), IECore.Color3dData( IECore.Color3d( 0.5 ) ) )
1298+
self.assertEqual( c.readAttribute( "animColor", 1 ), IECore.Color3dData( IECore.Color3d( 1 ) ) )
1299+
12281300
def testReloadButton( self ) :
12291301

12301302
def testNode( node ) :
@@ -1410,6 +1482,19 @@ def compareScene( self, a, b, time = 0, bakedObjects = [], parentTransform = Non
14101482
self.assertTrue( ab.min.equalWithAbsError( bb.min, 1e-6 ) )
14111483
self.assertTrue( ab.max.equalWithAbsError( bb.max, 1e-6 ) )
14121484

1485+
aAttrs = a.attributeNames()
1486+
bAttrs = b.attributeNames()
1487+
# need to remove the animatedObjectPrimVars attribute since it doesn't exist in some circumstances
1488+
if "sceneInterface:animatedObjectPrimVars" in aAttrs :
1489+
aAttrs.remove( "sceneInterface:animatedObjectPrimVars" )
1490+
if "sceneInterface:animatedObjectPrimVars" in bAttrs :
1491+
bAttrs.remove( "sceneInterface:animatedObjectPrimVars" )
1492+
self.assertEqual( aAttrs, bAttrs )
1493+
for attr in aAttrs :
1494+
self.assertTrue( a.hasAttribute( attr ) )
1495+
self.assertTrue( b.hasAttribute( attr ) )
1496+
self.assertEqual( a.readAttribute( attr, time ), b.readAttribute( attr, time ) )
1497+
14131498
self.assertEqual( a.hasObject(), b.hasObject() )
14141499
if a.hasObject() :
14151500
ma = a.readObject( time )
@@ -1654,7 +1739,7 @@ def testRopLinked( self ) :
16541739

16551740
def testRopForceObjects( self ) :
16561741

1657-
s = self.writeSCC()
1742+
s = self.writeAttributeSCC()
16581743
d = s.child( "1" ).createChild( "4" )
16591744
e = d.createChild( "5" )
16601745
box = IECore.MeshPrimitive.createBox(IECore.Box3f(IECore.V3f(0),IECore.V3f(1)))
@@ -1693,14 +1778,15 @@ def testLinks( bakedObjects = None ) :
16931778
self.assertTrue( b.hasAttribute( IECore.LinkedScene.rootLinkAttribute ) )
16941779
self.assertEqual( b.readAttribute( IECore.LinkedScene.fileNameLinkAttribute, 0 ), IECore.StringData( TestSceneCache.__testFile ) )
16951780
self.assertEqual( b.readAttribute( IECore.LinkedScene.rootLinkAttribute, 0 ), IECore.InternedStringVectorData( [ "1", "2" ] ) )
1696-
self.assertEqual( b.readAttribute( IECore.LinkedScene.timeLinkAttribute, 0 ), IECore.DoubleData( 1.0 / hou.fps() ) )
1781+
self.assertEqual( b.readAttribute( IECore.LinkedScene.timeLinkAttribute, 0 ), IECore.DoubleData( 0 ) )
16971782
d = a.child( "4" )
16981783
self.assertFalse( d.hasAttribute( IECore.LinkedScene.linkAttribute ) )
16991784
self.assertFalse( d.hasAttribute( IECore.LinkedScene.fileNameLinkAttribute ) )
17001785
self.assertFalse( d.hasAttribute( IECore.LinkedScene.rootLinkAttribute ) )
17011786
self.assertFalse( d.hasAttribute( IECore.LinkedScene.timeLinkAttribute ) )
17021787

17031788
# force b and below as links even though they are expanded
1789+
hou.setTime( -1.0 / hou.fps() )
17041790
xform = self.xform()
17051791
xform.parm( "expand" ).pressButton()
17061792
rop = self.rop( xform )

0 commit comments

Comments
 (0)