ConstObjectPtr HoudiniScene::readObject( double time ) const
{
	OBJ_Node *objNode = retrieveNode( true )->castToOBJNode();
	if ( !objNode )
	{
		return 0;
	}
	
	if ( objNode->getObjectType() == OBJ_GEOMETRY )
	{
		OP_Context context( time );
		GU_DetailHandle handle = objNode->getRenderGeometryHandle( context, false );
		
		if ( !m_splitter || ( handle != m_splitter->handle() ) )
		{
			m_splitter = new DetailSplitter( handle );
		}
		
		GU_DetailHandle newHandle = m_splitter->split( contentPathValue() );
		FromHoudiniGeometryConverterPtr converter = FromHoudiniGeometryConverter::create( ( newHandle.isNull() ) ? handle : newHandle );
		if ( !converter )
		{
			return 0;
		}
		
		return converter->convert();
	}
	
	/// \todo: need to account for cameras and lights
	
	return 0;
}
OP_Node *HoudiniScene::locateContent( OP_Node *node ) const
{
	OBJ_Node *objNode = node->castToOBJNode();
	if ( node->isManager() || ( objNode && objNode->getObjectType() == OBJ_SUBNET ) )
	{
		for ( int i=0; i < node->getNchildren(); ++i )
		{
			OP_Node *child = node->getChild( i );
			if ( child->getName().equal( contentName.c_str() ) )
			{
				return child;
			}
		}
	}
	else if ( objNode && objNode->getObjectType() == OBJ_GEOMETRY )
	{
		return objNode;
	}
	
	return 0;
}
bool HoudiniScene::hasObject() const
{
	OP_Node *node = retrieveNode( true );
	if ( node->isManager() )
	{
		return false;
	}
	
	OBJ_Node *objNode = node->castToOBJNode();
	if ( !objNode )
	{
		return false;
	}
	
	OBJ_OBJECT_TYPE type = objNode->getObjectType();
	if ( type == OBJ_GEOMETRY  )
	{
		OP_Context context( getDefaultTime() );
		const GU_Detail *geo = objNode->getRenderGeometry( context, false );
		// multiple named shapes define children that contain each object
		/// \todo: similar attribute logic is repeated in several places. unify in a single function if possible
		GA_ROAttributeRef nameAttrRef = geo->findStringTuple( GA_ATTRIB_PRIMITIVE, "name" );
		if ( !nameAttrRef.isValid() )
		{
			return true;
		}
		
		const GA_Attribute *nameAttr = nameAttrRef.getAttribute();
		const GA_AIFSharedStringTuple *tuple = nameAttr->getAIFSharedStringTuple();
		GA_Size numShapes = tuple->getTableEntries( nameAttr );
		if ( !numShapes )
		{
			return true;
		}
		
		for ( GA_Size i=0; i < numShapes; ++i )
		{
			const char *currentName = tuple->getTableString( nameAttr, tuple->validateTableHandle( nameAttr, i ) );
			const char *match = matchPath( currentName );
			if ( match && *match == *emptyString )
			{
				// exact match
				return true;
			}
		}
		
		return false;
	}
	
	/// \todo: need to account for OBJ_CAMERA and OBJ_LIGHT
	
	return false;
}
OP_Node *HoudiniScene::retrieveChild( const Name &name, Path &contentPath, MissingBehaviour missingBehaviour ) const
{
	OP_Node *node = retrieveNode( false, missingBehaviour );
	OP_Node *contentBaseNode = retrieveNode( true, missingBehaviour );
	if ( !node || !contentBaseNode )
	{
		return 0;
	}
	
	OBJ_Node *objNode = node->castToOBJNode();
	OBJ_Node *contentNode = contentBaseNode->castToOBJNode();
	
	// check subnet children
	if ( node->isManager() || ( objNode && objNode->getObjectType() == OBJ_SUBNET ) )
	{
		for ( int i=0; i < node->getNchildren(); ++i )
		{
			OP_Node *child = node->getChild( i );
			// the contentNode is actually an extension of ourself
			if ( child == contentNode )
			{
				continue;
			}
			
			if ( child->getName().equal( name.c_str() ) && !hasInput( child ) )
			{
				return child;
			}
		}
	}
	
	if ( contentNode )
	{
		// check connected outputs
		for ( unsigned i=0; i < contentNode->nOutputs(); ++i )
		{
			OP_Node *child = contentNode->getOutput( i );
			if ( child->getName().equal( name.c_str() ) )
			{
				return child;
			}
		}
		
		// check child shapes within the geo
		if ( contentNode->getObjectType() == OBJ_GEOMETRY )
		{
			OP_Context context( getDefaultTime() );
			const GU_Detail *geo = contentNode->getRenderGeometry( context, false );
			GA_ROAttributeRef nameAttrRef = geo->findStringTuple( GA_ATTRIB_PRIMITIVE, "name" );
			if ( nameAttrRef.isValid() )
			{
				const GA_Attribute *nameAttr = nameAttrRef.getAttribute();
				const GA_AIFSharedStringTuple *tuple = nameAttr->getAIFSharedStringTuple();
				GA_Size numShapes = tuple->getTableEntries( nameAttr );
				for ( GA_Size i=0; i < numShapes; ++i )
				{
					const char *currentName = tuple->getTableString( nameAttr, tuple->validateTableHandle( nameAttr, i ) );
					const char *match = matchPath( currentName );
					if ( match && *match != *emptyString )
					{
						std::pair<const char *, size_t> childMarker = nextWord( match );
						std::string child( childMarker.first, childMarker.second );
						if ( name == child )
						{
							size_t contentSize = ( m_contentIndex ) ? m_path.size() - m_contentIndex : 0;
							if ( contentSize )
							{
								contentPath.resize( contentSize );
								std::copy( m_path.begin() + m_contentIndex, m_path.end(), contentPath.begin() );
							}
							
							contentPath.push_back( name );
							
							return contentNode;
						}
					}
				}
			}
		}
	}
	
	if ( missingBehaviour == SceneInterface::ThrowIfMissing )
	{
		Path p;
		path( p );
		std::string pStr;
		pathToString( p, pStr );
		throw Exception( "IECoreHoudini::HoudiniScene::retrieveChild: Path \"" + pStr + "\" has no child named " + name.string() + "." );
	}
	
	return 0;
}
void HoudiniScene::childNames( NameList &childNames ) const
{
	OP_Node *node = retrieveNode();
	OBJ_Node *objNode = node->castToOBJNode();
	OBJ_Node *contentNode = retrieveNode( true )->castToOBJNode();
	
	// add subnet children
	if ( node->isManager() || ( objNode && objNode->getObjectType() == OBJ_SUBNET ) )
	{
		for ( int i=0; i < node->getNchildren(); ++i )
		{
			OP_Node *child = node->getChild( i );
			
			// ignore children that have incoming connections, as those are actually grandchildren
			// also ignore the contentNode, which is actually an extension of ourself
			if ( child != contentNode && !hasInput( child ) )
			{
				childNames.push_back( Name( child->getName() ) );
			}
		}
	}
	
	if ( !contentNode )
	{
		return;
	}
	
	// add connected outputs
	for ( unsigned i=0; i < contentNode->nOutputs(); ++i )
	{
		childNames.push_back( Name( contentNode->getOutput( i )->getName() ) );
	}
	
	// add child shapes within the geometry
	if ( contentNode->getObjectType() == OBJ_GEOMETRY )
	{
		OP_Context context( getDefaultTime() );
		const GU_Detail *geo = contentNode->getRenderGeometry( context, false );
		GA_ROAttributeRef nameAttrRef = geo->findStringTuple( GA_ATTRIB_PRIMITIVE, "name" );
		if ( !nameAttrRef.isValid() )
		{
			return;
		}
		
		const GA_Attribute *nameAttr = nameAttrRef.getAttribute();
		const GA_AIFSharedStringTuple *tuple = nameAttr->getAIFSharedStringTuple();
		GA_Size numShapes = tuple->getTableEntries( nameAttr );
		for ( GA_Size i=0; i < numShapes; ++i )
		{
			const char *currentName = tuple->getTableString( nameAttr, tuple->validateTableHandle( nameAttr, i ) );
			const char *match = matchPath( currentName );
			if ( match && *match != *emptyString )
			{
				std::pair<const char *, size_t> childMarker = nextWord( match );
				std::string child( childMarker.first, childMarker.second );
				if ( std::find( childNames.begin(), childNames.end(), child ) == childNames.end() )
				{
					childNames.push_back( child );
				}
			}
		}
	}
}
void HoudiniScene::readTags( NameList &tags, bool includeChildren ) const
{
	tags.clear();
	
	const OP_Node *node = retrieveNode();
	if ( !node )
	{
		return;
	}
	
	// add user supplied tags if we're not inside a SOP
	if ( !m_contentIndex && node->hasParm( pTags.getToken() ) )
	{
		UT_String parmTagStr;
		node->evalString( parmTagStr, pTags.getToken(), 0, 0 );
		if ( !parmTagStr.equal( UT_String::getEmptyString() ) )
		{
			UT_WorkArgs tokens;
			parmTagStr.tokenize( tokens, " " );
			for ( int i = 0; i < tokens.getArgc(); ++i )
			{
				tags.push_back( tokens[i] );
			}
		}
	}
	
	// add tags from the registered tag readers
	std::vector<CustomTagReader> &tagReaders = customTagReaders();
	for ( std::vector<CustomTagReader>::const_iterator it = tagReaders.begin(); it != tagReaders.end(); ++it )
	{
		NameList values;
		it->m_read( node, values, includeChildren );
		tags.insert( tags.end(), values.begin(), values.end() );
	}
	
	// add tags based on primitive groups
	OBJ_Node *contentNode = retrieveNode( true )->castToOBJNode();
	if ( contentNode && contentNode->getObjectType() == OBJ_GEOMETRY && m_splitter )
	{
		GU_DetailHandle newHandle = m_splitter->split( contentPathValue() );
		if ( !newHandle.isNull() )
		{
			GU_DetailHandleAutoReadLock readHandle( newHandle );
			if ( const GU_Detail *geo = readHandle.getGdp() )
			{
				GA_Range prims = geo->getPrimitiveRange();
				for ( GA_GroupTable::iterator<GA_ElementGroup> it=geo->primitiveGroups().beginTraverse(); !it.atEnd(); ++it )
				{
					GA_PrimitiveGroup *group = static_cast<GA_PrimitiveGroup*>( it.group() );
					if ( group->getInternal() || group->isEmpty() )
					{
						continue;
					}
					
					const UT_String &groupName = group->getName();
					if ( groupName.startsWith( tagGroupPrefix ) && group->containsAny( prims ) )
					{
						UT_String tag;
						groupName.substr( tag, tagGroupPrefix.length() );
						tag.substitute( "_", ":" );
						tags.push_back( tag.buffer() );
					}
				}
			}
		}
	}
}
bool HoudiniScene::hasTag( const Name &name, bool includeChildren ) const
{
	const OP_Node *node = retrieveNode();
	if ( !node )
	{
		return false;
	}
	
	// check for user supplied tags if we're not inside a SOP
	if ( !m_contentIndex && node->hasParm( pTags.getToken() ) )
	{
		UT_String parmTags;
		node->evalString( parmTags, pTags.getToken(), 0, 0 );
		if ( UT_String( name.c_str() ).multiMatch( parmTags ) )
		{
			return true;
		}
	}
	
	// check with the registered tag readers
	std::vector<CustomTagReader> &tagReaders = customTagReaders();
	for ( std::vector<CustomTagReader>::const_iterator it = tagReaders.begin(); it != tagReaders.end(); ++it )
	{
		if ( it->m_has( node, name ) )
		{
			return true;
		}
	}
	
	// check tags based on primitive groups
	OBJ_Node *contentNode = retrieveNode( true )->castToOBJNode();
	if ( contentNode && contentNode->getObjectType() == OBJ_GEOMETRY && m_splitter )
	{
		GU_DetailHandle newHandle = m_splitter->split( contentPathValue() );
		if ( !newHandle.isNull() )
		{
			GU_DetailHandleAutoReadLock readHandle( newHandle );
			if ( const GU_Detail *geo = readHandle.getGdp() )
			{
				GA_Range prims = geo->getPrimitiveRange();
				for ( GA_GroupTable::iterator<GA_ElementGroup> it=geo->primitiveGroups().beginTraverse(); !it.atEnd(); ++it )
				{
					GA_PrimitiveGroup *group = static_cast<GA_PrimitiveGroup*>( it.group() );
					if ( group->getInternal() || group->isEmpty() )
					{
						continue;
					}
					
					const UT_String &groupName = group->getName();
					if ( groupName.startsWith( tagGroupPrefix ) && group->containsAny( prims ) )
					{
						UT_String tag;
						groupName.substr( tag, tagGroupPrefix.length() );
						tag.substitute( "_", ":" );
						if ( tag.equal( name.c_str() ) )
						{
							return true;
						}
					}
				}
			}
		}
	}
	
	return false;
}