void ToHoudiniGeometryConverter::transferAttribValues(
	const Primitive *primitive, GU_Detail *geo,
	const GA_Range &points, const GA_Range &prims,
	PrimitiveVariable::Interpolation vertexInterpolation,
	PrimitiveVariable::Interpolation primitiveInterpolation,
	PrimitiveVariable::Interpolation pointInterpolation,
	PrimitiveVariable::Interpolation detailInterpolation
) const
{
	GA_OffsetList offsets;
	if ( prims.isValid() )
	{
		const GA_PrimitiveList &primitives = geo->getPrimitiveList();
		for ( GA_Iterator it=prims.begin(); !it.atEnd(); ++it )
		{
			const GA_Primitive *prim = primitives.get( it.getOffset() );
			size_t numPrimVerts = prim->getVertexCount();
			for ( size_t v=0; v < numPrimVerts; v++ )
			{
				if ( prim->getTypeId() == GEO_PRIMPOLY )
				{
					offsets.append( prim->getVertexOffset( numPrimVerts - 1 - v ) );
				}
				else
				{
					offsets.append( prim->getVertexOffset( v ) );
				}
			}
		}
	}

	GA_Range vertRange( geo->getVertexMap(), offsets );

	UT_String filter( attributeFilterParameter()->getTypedValue() );

	bool convertStandardAttributes = m_convertStandardAttributesParameter->getTypedValue();

	// process all primvars with UV interpretation
	for ( const auto &it : primitive->variables)
	{

		if ( !UT_String( it.first ).multiMatch( filter ) )
		{
			continue;
		}

		if (const V2fVectorData *uvData = runTimeCast<const V2fVectorData> ( it.second.data.get() ) )
		{
			if ( uvData->getInterpretation() != GeometricData::UV )
			{
				continue;
			}

			PrimitiveVariable::IndexedView<Imath::V2f> uvIndexedView ( it.second );

			// Houdini prefers a V3f uvw rather than V2f uv,
			// though they advise setting the 3rd component to 0.
			std::vector<Imath::V3f> uvw;
			uvw.reserve( uvIndexedView.size() );
			for ( size_t i=0; i < uvIndexedView.size(); ++i )
			{
				uvw.emplace_back( uvIndexedView[i][0], uvIndexedView[i][1], 0 );
			}

			GA_Range range = vertRange;
			if ( it.second.interpolation == pointInterpolation )
			{
				range = points;
			}

			V3fVectorData::Ptr uvwData = new V3fVectorData( uvw );
			uvwData->setInterpretation( GeometricData::UV );

			ToHoudiniAttribConverterPtr converter = ToHoudiniAttribConverter::create( uvwData.get() );
			converter->convert( it.first, geo, range );
			filter += " ^" + it.first;
		}
	}


 	UT_StringMMPattern attribFilter;
	attribFilter.compile( filter );

	// add the primitive variables to the various GEO_AttribDicts based on interpolation type
	for ( PrimitiveVariableMap::const_iterator it=primitive->variables.begin() ; it != primitive->variables.end(); it++ )
	{
		if( !primitive->isPrimitiveVariableValid( it->second ) )
		{
			IECore::msg( IECore::MessageHandler::Warning, "ToHoudiniGeometryConverter", "PrimitiveVariable " + it->first + " is invalid. Ignoring." );
			continue;
		}

		UT_String varName( it->first );
		if ( !varName.multiMatch( attribFilter ) )
		{
			continue;
		}

		PrimitiveVariable primVar = processPrimitiveVariable( primitive, it->second );

		DataPtr data = nullptr;
		ToHoudiniAttribConverterPtr converter = nullptr;

		if( primVar.indices && primVar.data->typeId() == StringVectorDataTypeId )
		{
			// we want to process the indexed strings rather than the expanded strings
			converter = ToHoudiniAttribConverter::create( primVar.data.get() );
			if( ToHoudiniStringVectorAttribConverter *stringVectorConverter = IECore::runTimeCast<ToHoudiniStringVectorAttribConverter>( converter.get() ) )
			{
				stringVectorConverter->indicesParameter()->setValidatedValue( primVar.indices.get() );
			}
		}
		else
		{
			// all other primitive variables must be expanded
			data = primVar.expandedData();
			converter = ToHoudiniAttribConverter::create( data.get() );
		}

		if ( !converter )
		{
			continue;
		}

		const std::string name = ( convertStandardAttributes ) ? processPrimitiveVariableName( it->first ) : it->first;

		if ( primVar.interpolation == detailInterpolation )
 		{
			// add detail attribs
			try
			{
				converter->convert( name, geo );
			}
			catch ( std::exception &e )
			{
				throw IECore::Exception( "PrimitiveVariable \"" + it->first + "\" could not be converted as a Detail Attrib: " + e.what() );
			}
	 	}
		else if ( primVar.interpolation == pointInterpolation )
		{

#if UT_MAJOR_VERSION_INT < 15

			// add point attribs
			if ( name == "P" )
			{
				// special case for P
				transferP( runTimeCast<const V3fVectorData>( primVar.data.get() ), geo, points );
			}
			else

#endif

			{
 				try
				{
					GA_RWAttributeRef attrRef = converter->convert( name, geo, points );

					// mark rest as non-transforming so it doesn't get manipulated once inside Houdini
					if ( name == "rest" || name == "Pref" )
					{

#if UT_MAJOR_VERSION_INT >= 15

						attrRef.setTypeInfo( GA_TYPE_VOID );

#else

						attrRef.getAttribute()->setNonTransforming( true );

#endif

					}
				}
				catch ( std::exception &e )
				{
					throw IECore::Exception( "PrimitiveVariable \"" + it->first + "\" could not be converted as a Point Attrib: " + e.what() );
				}
			}
		}
		else if ( primVar.interpolation == primitiveInterpolation )
		{
			// add primitive attribs
			try
			{
				converter->convert( name, geo, prims );
			}
			catch ( std::exception &e )
			{
				throw IECore::Exception( "PrimitiveVariable \"" + it->first + "\" could not be converted as a Primitive Attrib: " + e.what() );
			}
		}
		else if ( primVar.interpolation == vertexInterpolation )
		{
			// add vertex attribs
			try
			{
				converter->convert( name, geo, vertRange );
			}
			catch ( std::exception &e )
			{
				throw IECore::Exception( "PrimitiveVariable \"" + it->first + "\" could not be converted as a Vertex Attrib: " + e.what() );
			}
		}
	}

	// backwards compatibility with older data
	const StringData *nameData = primitive->blindData()->member<StringData>( "name" );
	if ( nameData && prims.isValid() )
	{
		ToHoudiniStringVectorAttribConverter::convertString( "name", nameData->readable(), geo, prims );
	}
}
void ToHoudiniGeometryConverter::transferAttribValues(
	const Primitive *primitive, GU_Detail *geo,
	const GA_Range &points, const GA_Range &prims,
	PrimitiveVariable::Interpolation vertexInterpolation,
	PrimitiveVariable::Interpolation primitiveInterpolation,
	PrimitiveVariable::Interpolation pointInterpolation,
	PrimitiveVariable::Interpolation detailInterpolation
) const
{
	GA_OffsetList offsets;
	if ( prims.isValid() )
	{
		const GA_PrimitiveList &primitives = geo->getPrimitiveList();
		for ( GA_Iterator it=prims.begin(); !it.atEnd(); ++it )
		{
			const GA_Primitive *prim = primitives.get( it.getOffset() );
			size_t numPrimVerts = prim->getVertexCount();
			for ( size_t v=0; v < numPrimVerts; v++ )
			{
				if ( prim->getTypeId() == GEO_PRIMPOLY )
				{
					offsets.append( prim->getVertexOffset( numPrimVerts - 1 - v ) );
				}
				else
				{
					offsets.append( prim->getVertexOffset( v ) );
				}
			}
		}
	}

	GA_Range vertRange( geo->getVertexMap(), offsets );
	
	UT_String filter( attributeFilterParameter()->getTypedValue() );
	
	// match all the string variables to each associated indices variable
	/// \todo: replace all this logic with IECore::IndexedData once it exists...
	PrimitiveVariableMap stringsToIndices;
	for ( PrimitiveVariableMap::const_iterator it=primitive->variables.begin() ; it != primitive->variables.end(); it++ )
	{
		if ( !primitive->isPrimitiveVariableValid( it->second ) )
		{
			IECore::msg( IECore::MessageHandler::Warning, "ToHoudiniGeometryConverter", "PrimitiveVariable " + it->first + " is invalid. Ignoring." );
			filter += UT_String( " ^" + it->first );
			continue;
		}

		ToHoudiniAttribConverterPtr converter = ToHoudiniAttribConverter::create( it->second.data.get() );
		if ( !converter )
		{
			continue;
		}
		
		if ( it->second.data->isInstanceOf( StringVectorDataTypeId ) )
		{
			std::string indicesVariableName = it->first + "Indices";
			PrimitiveVariableMap::const_iterator indices = primitive->variables.find( indicesVariableName );
			if ( indices != primitive->variables.end() && indices->second.data->isInstanceOf( IntVectorDataTypeId ) && primitive->isPrimitiveVariableValid( indices->second ) )
			{
				stringsToIndices[it->first] = indices->second;
				filter += UT_String( " ^" + indicesVariableName );
			}
		}
	}
	
	bool convertStandardAttributes = m_convertStandardAttributesParameter->getTypedValue();
	if ( convertStandardAttributes && UT_String( "s" ).multiMatch( filter ) && UT_String( "t" ).multiMatch( filter ) )
	{
		// convert s and t to uv
		PrimitiveVariableMap::const_iterator sPrimVar = primitive->variables.find( "s" );
		PrimitiveVariableMap::const_iterator tPrimVar = primitive->variables.find( "t" );
		if ( sPrimVar != primitive->variables.end() && tPrimVar != primitive->variables.end() )
		{
			if ( sPrimVar->second.interpolation == tPrimVar->second.interpolation )
			{
				const FloatVectorData *sData = runTimeCast<const FloatVectorData>( sPrimVar->second.data.get() );
				const FloatVectorData *tData = runTimeCast<const FloatVectorData>( tPrimVar->second.data.get() );
				if ( sData && tData )
				{
					const std::vector<float> &s = sData->readable();
					const std::vector<float> &t = tData->readable();
					
					std::vector<Imath::V3f> uvw;
					uvw.reserve( s.size() );
					for ( size_t i=0; i < s.size(); ++i )
					{
						uvw.push_back( Imath::V3f( s[i], 1 - t[i], 0 ) );
					}
					
					GA_Range range = vertRange;
					if ( sPrimVar->second.interpolation == pointInterpolation )
					{
						range = points;
					}
					
					ToHoudiniAttribConverterPtr converter = ToHoudiniAttribConverter::create( new V3fVectorData( uvw ) );
					converter->convert( "uv", geo, range );
					filter += " ^s ^t";
				}
			}
		}
	}
	
 	UT_StringMMPattern attribFilter;
	attribFilter.compile( filter );
	
	// add the primitive variables to the various GEO_AttribDicts based on interpolation type
	for ( PrimitiveVariableMap::const_iterator it=primitive->variables.begin() ; it != primitive->variables.end(); it++ )
	{
		UT_String varName( it->first );
		if ( !varName.multiMatch( attribFilter ) )
		{
			continue;
		}
		
		PrimitiveVariable primVar = processPrimitiveVariable( primitive, it->second );
		ToHoudiniAttribConverterPtr converter = ToHoudiniAttribConverter::create( primVar.data.get() );
		if ( !converter )
		{
			continue;
		}
		
		PrimitiveVariable::Interpolation interpolation = primVar.interpolation;
		
		if ( converter->isInstanceOf( (IECore::TypeId)ToHoudiniStringVectorAttribConverterTypeId ) )
		{
			PrimitiveVariableMap::const_iterator indices = stringsToIndices.find( it->first );
			if ( indices != stringsToIndices.end() )
			{
				ToHoudiniStringVectorAttribConverter *stringVectorConverter = IECore::runTimeCast<ToHoudiniStringVectorAttribConverter>( converter.get() );
				PrimitiveVariable indicesPrimVar = processPrimitiveVariable( primitive, indices->second );
				stringVectorConverter->indicesParameter()->setValidatedValue( indicesPrimVar.data );
				interpolation = indices->second.interpolation;
			}
		}
		
		const std::string name = ( convertStandardAttributes ) ? processPrimitiveVariableName( it->first ) : it->first;
		
		if ( interpolation == detailInterpolation )
 		{
			// add detail attribs
			try
			{
				converter->convert( name, geo );
			}
			catch ( std::exception &e )
			{
				throw IECore::Exception( "PrimitiveVariable \"" + it->first + "\" could not be converted as a Detail Attrib: " + e.what() );
			}
	 	}
		else if ( interpolation == pointInterpolation )
		{
			// add point attribs
			if ( name == "P" )
			{
				// special case for P
				transferP( runTimeCast<const V3fVectorData>( primVar.data.get() ), geo, points );
			}
			else
			{
 				try
				{
					GA_RWAttributeRef attrRef = converter->convert( name, geo, points );
					
					// mark rest as non-transforming so it doesn't get manipulated once inside Houdini
					if ( name == "rest" || name == "Pref" )
					{
						attrRef.getAttribute()->setNonTransforming( true );
					}
				}
				catch ( std::exception &e )
				{
					throw IECore::Exception( "PrimitiveVariable \"" + it->first + "\" could not be converted as a Point Attrib: " + e.what() );
				}
			}
		}
		else if ( interpolation == primitiveInterpolation )
		{
			// add primitive attribs
			try
			{
				converter->convert( name, geo, prims );
			}
			catch ( std::exception &e )
			{
				throw IECore::Exception( "PrimitiveVariable \"" + it->first + "\" could not be converted as a Primitive Attrib: " + e.what() );
			}
		}
		else if ( interpolation == vertexInterpolation )
		{
			// add vertex attribs
			try
			{
				converter->convert( name, geo, vertRange );
			}
			catch ( std::exception &e )
			{
				throw IECore::Exception( "PrimitiveVariable \"" + it->first + "\" could not be converted as a Vertex Attrib: " + e.what() );
			}
		}
	}
	
	// backwards compatibility with older data
	const StringData *nameData = primitive->blindData()->member<StringData>( "name" );
	if ( nameData && prims.isValid() )
	{
		ToHoudiniStringVectorAttribConverter::convertString( "name", nameData->readable(), geo, prims );
	}
}
OP_ERROR SOP_InterpolatedCacheReader::cookMySop( OP_Context &context )
{
	flags().setTimeDep( true );
	
	if ( lockInputs( context ) >= UT_ERROR_ABORT )
	{
		return error();
	}
	
	gdp->stashAll();
	
	float time = context.getTime();
	float frame = context.getFloatFrame();
	
	UT_String paramVal;
	
	evalString( paramVal, "cacheSequence", 0, time );
	std::string cacheFileName = paramVal.toStdString();
	
	evalString( paramVal, "objectFixes", 0, time );
	std::string objectPrefix = paramVal.toStdString();
	evalString( paramVal, "objectFixes", 1, time );
	std::string objectSuffix = paramVal.toStdString();
	
	evalString( paramVal, "attributeFixes", 0, time );
	std::string attributePrefix = paramVal.toStdString();
	evalString( paramVal, "attributeFixes", 1, time );
	std::string attributeSuffix = paramVal.toStdString();
	
	evalString( paramVal, "transformAttribute", 0, time );
	std::string transformAttribute = paramVal.toStdString();
	
	int samplesPerFrame = evalInt( "samplesPerFrame", 0, time );
	InterpolatedCache::Interpolation interpolation = (InterpolatedCache::Interpolation)evalInt( "interpolation", 0, time );
	GroupingMode groupingMode = (GroupingMode)evalInt( "groupingMode", 0, time );
	
	// create the InterpolatedCache
	if ( cacheFileName.compare( m_cacheFileName ) != 0 || samplesPerFrame != m_samplesPerFrame || interpolation != m_interpolation )
	{
		try
		{
			float fps = OPgetDirector()->getChannelManager()->getSamplesPerSec();
			OversamplesCalculator calc( fps, samplesPerFrame );
			m_cache = new InterpolatedCache( cacheFileName, interpolation, calc );
		}
		catch ( IECore::InvalidArgumentException e )
		{
			addWarning( SOP_ATTRIBUTE_INVALID, e.what() );
			unlockInputs();
			return error();
		}
		
		m_cacheFileName = cacheFileName;
		m_samplesPerFrame = samplesPerFrame;
		m_interpolation = interpolation;
	}
	
	if ( !m_cache )
	{
		addWarning( SOP_MESSAGE, "SOP_InterpolatedCacheReader: Cache Sequence not found" );
		unlockInputs();
		return error();
	}
	
	std::vector<InterpolatedCache::ObjectHandle> objects;
	std::vector<InterpolatedCache::AttributeHandle> attrs;
	
	try
	{
		m_cache->objects( frame, objects );
	}
	catch ( IECore::Exception e )
	{
		addWarning( SOP_ATTRIBUTE_INVALID, e.what() );
		unlockInputs();
		return error();
	}
	
	duplicatePointSource( 0, context );
	
	GA_ElementGroupTable *groups = 0;
	if ( groupingMode == PointGroup )
	{
		groups = &gdp->pointGroups();
	}
	else if ( groupingMode == PrimitiveGroup )
	{
		groups = &gdp->primitiveGroups();
	}
	
	for ( GA_GroupTable::iterator<GA_ElementGroup> it=groups->beginTraverse(); !it.atEnd(); ++it )
	{
		GA_ElementGroup *group = it.group();
		if ( group->getInternal() || group->isEmpty() )
		{
			continue;
		}
		
		// match GA_ElementGroup name to InterpolatedCache::ObjectHandle
		std::string searchName = objectPrefix + group->getName().toStdString() + objectSuffix;
		std::vector<InterpolatedCache::ObjectHandle>::iterator oIt = find( objects.begin(), objects.end(), searchName );
		if ( oIt == objects.end() )
		{
			continue;
		}
		
		CompoundObjectPtr attributes = 0;
		
		try
		{
			m_cache->attributes( frame, *oIt, attrs );
			attributes = m_cache->read( frame, *oIt );
		}
		catch ( IECore::Exception e )
		{
			addError( SOP_ATTRIBUTE_INVALID, e.what() );
			unlockInputs();
			return error();
		}
		
		const CompoundObject::ObjectMap &attributeMap = attributes->members();
		
		GA_Range pointRange;
		GA_Range primRange;
		GA_Range vertexRange;
		
		if ( groupingMode == PointGroup )
		{
			pointRange = gdp->getPointRange( (GA_PointGroup*)it.group() );
		}
		else if ( groupingMode == PrimitiveGroup )
		{
			primRange = gdp->getPrimitiveRange( (GA_PrimitiveGroup*)it.group() );
			const GA_PrimitiveList &primitives = gdp->getPrimitiveList();
			
			GA_OffsetList pointOffsets;
			GA_OffsetList vertOffsets;
			for ( GA_Iterator it=primRange.begin(); !it.atEnd(); ++it )
			{
				const GA_Primitive *prim = primitives.get( it.getOffset() );
				GA_Range primPointRange = prim->getPointRange();
				for ( GA_Iterator pIt=primPointRange.begin(); !pIt.atEnd(); ++pIt )
				{
					pointOffsets.append( pIt.getOffset() );
				}
				
				size_t numPrimVerts = prim->getVertexCount();
				for ( size_t v=0; v < numPrimVerts; v++ )
				{
					if ( prim->getTypeId() == GEO_PRIMPOLY )
					{
						vertOffsets.append( prim->getVertexOffset( numPrimVerts - 1 - v ) );
					}
					else
					{
						vertOffsets.append( prim->getVertexOffset( v ) );
					}
				}
			}
			
			pointOffsets.sortAndRemoveDuplicates();
			pointRange = GA_Range( gdp->getPointMap(), pointOffsets );
			vertexRange = GA_Range( gdp->getVertexMap(), vertOffsets );
		}
		
		// transfer the InterpolatedCache::Attributes onto the GA_ElementGroup
		for ( CompoundObject::ObjectMap::const_iterator aIt=attributeMap.begin(); aIt != attributeMap.end(); aIt++ )
		{
			Data *data = IECore::runTimeCast<Data>( aIt->second );
			if ( !data )
			{
				continue;
			}
			
			ToHoudiniAttribConverterPtr converter = ToHoudiniAttribConverter::create( data );
			if ( !converter )
 			{
 				continue;
 			}
			
			// strip the prefix/suffix from the GA_Attribute name
			std::string attrName = aIt->first.value();
			size_t prefixLength = attributePrefix.length();
			if ( prefixLength && ( search( attrName.begin(), attrName.begin()+prefixLength, attributePrefix.begin(), attributePrefix.end() ) == attrName.begin() ) )
			{
				attrName.erase( attrName.begin(), attrName.begin() + prefixLength );
			}
			
			size_t suffixLength = attributeSuffix.length();
			if ( suffixLength && ( search( attrName.end() - suffixLength, attrName.end(), attributeSuffix.begin(), attributeSuffix.end() ) == ( attrName.end() - suffixLength ) ) )
			{
				attrName.erase( attrName.end() - suffixLength, attrName.end() );
			}
			
			if ( attrName == "P" )
			{
				const V3fVectorData *positions = IECore::runTimeCast<const V3fVectorData>( data );
				if ( !positions )
				{
					continue;
				}
				
				size_t index = 0;
				size_t entries = pointRange.getEntries();
				const std::vector<Imath::V3f> &pos = positions->readable();
				
				// Attempting to account for the vertex difference between an IECore::CurvesPrimitive and Houdini curves.
				// As Houdini implicitly triples the endpoints of a curve, a cache generated from a single CurvesPrimitive
				// will have exactly four extra vertices. In this case, we adjust the cache by ignoring the first two and
				// last two V3fs. In all other cases, we report a warning and don't apply the cache to these points.
				if ( pos.size() - 4 == entries )
				{
					index = 2;
				}
				else if ( pos.size() != entries )
				{
					addWarning( SOP_ATTRIBUTE_INVALID, ( boost::format( "Geometry/Cache mismatch: %s contains %d points, while cache expects %d values for P." ) % group->getName().toStdString() % entries % pos.size() ).str().c_str() );
					continue;
				}
				
				/// \todo: try multi-threading this with a GA_SplittableRange
				for ( GA_Iterator it=pointRange.begin(); !it.atEnd(); ++it, ++index )
				{
					gdp->setPos3( it.getOffset(), IECore::convert<UT_Vector3>( pos[index] ) );
				}

			}
			else if ( groupingMode == PrimitiveGroup )
			{
				GA_Range currentRange;
				unsigned size = despatchTypedData<TypedDataSize, TypeTraits::IsVectorTypedData, DespatchTypedDataIgnoreError>( data );
				
				// check for existing attributes
				if ( gdp->findPrimitiveAttribute( attrName.c_str() ).isValid() && size == primRange.getEntries() )
				{
					currentRange = primRange;
				}
				else if ( gdp->findPointAttribute( attrName.c_str() ).isValid() && size == pointRange.getEntries() )
				{
					currentRange = pointRange;
				}
				else if ( gdp->findVertexAttribute( attrName.c_str() ).isValid() && size == vertexRange.getEntries() )
				{
					currentRange = vertexRange;
				}
				// fall back to Cortex standard inferred order
				else if ( size == primRange.getEntries() )
				{
					currentRange = primRange;
				}
				else if ( size == pointRange.getEntries() )
				{
					currentRange = pointRange;
				}
				else if ( size == vertexRange.getEntries() )
				{
					currentRange = vertexRange;
				}
				else
				{
					addWarning( SOP_ATTRIBUTE_INVALID, ( boost::format( "Geometry/Cache mismatch: %s: cache expects %d values for %s." ) % group->getName().toStdString() % size % attrName ).str().c_str() );
					continue;
				}
				
				converter->convert( attrName, gdp, currentRange );
			}
			else
			{
				converter->convert( attrName, gdp, pointRange );
			}
		}
		
		// if transformAttribute is specified, use to to transform the points
		if ( transformAttribute != "" )
		{
			const TransformationMatrixdData *transform = attributes->member<TransformationMatrixdData>( transformAttribute );
			if ( transform )
			{
				UT_Matrix4 matrix( IECore::convert<UT_Matrix4T<double> >( transform->readable().transform() ) );
				gdp->transformGroup( matrix, *group );
			}
			else
			{
				const TransformationMatrixfData *transform = attributes->member<TransformationMatrixfData>( transformAttribute );
				if ( transform )
				{
					UT_Matrix4 matrix = IECore::convert<UT_Matrix4>( transform->readable().transform() );
					gdp->transformGroup( matrix, *group );
				}
			}
		}
	}
	
	unlockInputs();
	return error();
}