bool ToMayaSkinClusterConverter::doConversion( IECore::ConstObjectPtr from, MObject &to, IECore::ConstCompoundObjectPtr operands ) const
{
	MStatus s;

	IECore::ConstSmoothSkinningDataPtr skinningData = IECore::runTimeCast<const IECore::SmoothSkinningData>( from );
	assert( skinningData );
	
	const std::vector<std::string> &influenceNames = skinningData->influenceNames()->readable();
	const std::vector<Imath::M44f> &influencePoseData  = skinningData->influencePose()->readable();
	const std::vector<int> &pointIndexOffsets  = skinningData->pointIndexOffsets()->readable();
	const std::vector<int> &pointInfluenceCounts = skinningData->pointInfluenceCounts()->readable();
	const std::vector<int> &pointInfluenceIndices = skinningData->pointInfluenceIndices()->readable();
	const std::vector<float> &pointInfluenceWeights = skinningData->pointInfluenceWeights()->readable();
	
	MFnDependencyNode fnSkinClusterNode( to, &s );
	MFnSkinCluster fnSkinCluster( to, &s );
	if ( s != MS::kSuccess )
	{
		/// \todo: optional parameter to allow custom node types and checks for the necessary attributes
		/// \todo: create a new skinCluster if we want a kSkinClusterFilter and this isn't one
		throw IECore::Exception( ( boost::format( "ToMayaSkinClusterConverter: \"%s\" is not a valid skinCluster" ) % fnSkinClusterNode.name() ).str() );
	}

	const unsigned origNumInfluences = influenceNames.size();
	unsigned numInfluences = origNumInfluences;
	std::vector<bool> ignoreInfluence( origNumInfluences, false );
	std::vector<int> indexMap( origNumInfluences, -1 );
	const bool ignoreMissingInfluences = m_ignoreMissingInfluencesParameter->getTypedValue();
	const bool ignoreBindPose = m_ignoreBindPoseParameter->getTypedValue();
	
	// gather the influence objects
	MObject mObj;
	MDagPath path;
	MSelectionList influenceList;
	MDagPathArray influencePaths;
	for ( unsigned i=0, index=0; i < origNumInfluences; i++ )
	{
		MString influenceName( influenceNames[i].c_str() );
		s = influenceList.add( influenceName );
		if ( !s )
		{
			if ( ignoreMissingInfluences )
			{
				ignoreInfluence[i] = true;
				MGlobal::displayWarning( MString( "ToMayaSkinClusterConverter: \"" + influenceName + "\" is not a valid influence" ) );
				continue;
			}
			
			throw IECore::Exception( ( boost::format( "ToMayaSkinClusterConverter: \"%s\" is not a valid influence" ) % influenceName ).str() );
		}
		
		influenceList.getDependNode( index, mObj );
		MFnIkJoint fnInfluence( mObj, &s );
		if ( !s )
		{
			if ( ignoreMissingInfluences )
			{
				ignoreInfluence[i] = true;
				influenceList.remove( index );
				MGlobal::displayWarning( MString( "ToMayaSkinClusterConverter: \"" + influenceName + "\" is not a valid influence" ) );
				continue;
			}
			
			throw IECore::Exception( ( boost::format( "ToMayaSkinClusterConverter: \"%s\" is not a valid influence" ) % influenceName ).str() );
		}
		
		fnInfluence.getPath( path );
		influencePaths.append( path );
		indexMap[i] = index;
		index++;
	}
	
	MPlugArray connectedPlugs;
	
	bool existingBindPose = true;
	MPlug bindPlug = fnSkinClusterNode.findPlug( "bindPose", true, &s );
	if ( !bindPlug.connectedTo( connectedPlugs, true, false ) )
	{
		existingBindPose = false;
		if ( !ignoreBindPose )
		{
			throw IECore::Exception( ( boost::format( "ToMayaSkinClusterConverter: \"%s\" does not have a valid bindPose" ) % fnSkinClusterNode.name() ).str() );
		}
	}
	
	MPlug bindPoseMatrixArrayPlug;
	MPlug bindPoseMemberArrayPlug;
	if ( existingBindPose )
	{
		MFnDependencyNode fnBindPose( connectedPlugs[0].node() );
		if ( fnBindPose.typeName() != "dagPose" )
		{
			throw IECore::Exception( ( boost::format( "ToMayaSkinClusterConverter: \"%s\" is not a valid bindPose" ) % fnBindPose.name() ).str() );
		}
		
		bindPoseMatrixArrayPlug = fnBindPose.findPlug( "worldMatrix", true, &s );
		bindPoseMemberArrayPlug = fnBindPose.findPlug( "members", true, &s );
	}
	
	/// \todo: optional parameter to reset the skinCluster's geomMatrix plug
	
	// break existing influence connections to the skinCluster
	MDGModifier dgModifier;
	MMatrixArray ignoredPreMatrices;
	MPlug matrixArrayPlug = fnSkinClusterNode.findPlug( "matrix", true, &s );
	MPlug bindPreMatrixArrayPlug = fnSkinClusterNode.findPlug( "bindPreMatrix", true, &s );
	for ( unsigned i=0; i < matrixArrayPlug.numConnectedElements(); i++ )
	{
		MPlug matrixPlug = matrixArrayPlug.connectionByPhysicalIndex( i, &s );
		matrixPlug.connectedTo( connectedPlugs, true, false );
		if ( !connectedPlugs.length() )
		{
			continue;
		}
		
		MFnIkJoint fnInfluence( connectedPlugs[0].node() );
		fnInfluence.getPath( path );
		if ( ignoreMissingInfluences && !influenceList.hasItem( path ) )
		{
			MPlug preMatrixPlug = bindPreMatrixArrayPlug.elementByLogicalIndex( i );
			preMatrixPlug.getValue( mObj );
			MFnMatrixData matFn( mObj );
			ignoredPreMatrices.append( matFn.matrix() );
			ignoreInfluence.push_back( false );
			indexMap.push_back( influenceList.length() );
			influenceList.add( connectedPlugs[0].node() );
			numInfluences++;
		}
		dgModifier.disconnect( connectedPlugs[0], matrixPlug );
	}
	MPlug lockArrayPlug = fnSkinClusterNode.findPlug( "lockWeights", true, &s );
	for ( unsigned i=0; i < lockArrayPlug.numConnectedElements(); i++ )
	{
		MPlug lockPlug = lockArrayPlug.connectionByPhysicalIndex( i, &s );
		lockPlug.connectedTo( connectedPlugs, true, false );
		if ( connectedPlugs.length() )
		{
			dgModifier.disconnect( connectedPlugs[0], lockPlug );
		}
	}
	MPlug paintPlug = fnSkinClusterNode.findPlug( "paintTrans", true, &s );
	paintPlug.connectedTo( connectedPlugs, true, false );
	if ( connectedPlugs.length() )
	{
		dgModifier.disconnect( connectedPlugs[0], paintPlug );
	}
	
	// break existing influence connections to the bind pose
	if ( existingBindPose )
	{
		for ( unsigned i=0; i < bindPoseMatrixArrayPlug.numConnectedElements(); i++ )
		{
			MPlug matrixPlug = bindPoseMatrixArrayPlug.connectionByPhysicalIndex( i, &s );
			matrixPlug.connectedTo( connectedPlugs, true, false );
			if ( connectedPlugs.length() )
			{
				dgModifier.disconnect( connectedPlugs[0], matrixPlug );
			}
		}
		for ( unsigned i=0; i < bindPoseMemberArrayPlug.numConnectedElements(); i++ )
		{
			MPlug memberPlug = bindPoseMemberArrayPlug.connectionByPhysicalIndex( i, &s );
			memberPlug.connectedTo( connectedPlugs, true, false );
			if ( connectedPlugs.length() )
			{
				dgModifier.disconnect( connectedPlugs[0], memberPlug );
			}
		}
	}
	
	if ( !dgModifier.doIt() )
	{
		dgModifier.undoIt();
		throw IECore::Exception( "ToMayaSkinClusterConverter: Unable to break the influence connections" );
	}
	
	// make connections from influences to skinCluster and bindPose
	for ( unsigned i=0; i < numInfluences; i++ )
	{
		if ( ignoreInfluence[i] )
		{
			continue;
		}
		
		int index = indexMap[i];
		s = influenceList.getDependNode( index, mObj );
		MFnIkJoint fnInfluence( mObj, &s );
		MPlug influenceMatrixPlug = fnInfluence.findPlug( "worldMatrix", true, &s ).elementByLogicalIndex( 0, &s );
		MPlug influenceMessagePlug = fnInfluence.findPlug( "message", true, &s );
		MPlug influenceBindPosePlug = fnInfluence.findPlug( "bindPose", true, &s );
		MPlug influenceLockPlug = fnInfluence.findPlug( "lockInfluenceWeights", true, &s );
		if ( !s )
		{
			// add the lockInfluenceWeights attribute if it doesn't exist
			MFnNumericAttribute nAttr;
			MObject attribute = nAttr.create( "lockInfluenceWeights", "liw", MFnNumericData::kBoolean, false );
			fnInfluence.addAttribute( attribute );
			influenceLockPlug = fnInfluence.findPlug( "lockInfluenceWeights", true, &s );
		}
		
		// connect influence to the skinCluster
		MPlug matrixPlug = matrixArrayPlug.elementByLogicalIndex( index );
		MPlug lockPlug = lockArrayPlug.elementByLogicalIndex( index );
		dgModifier.connect( influenceMatrixPlug, matrixPlug );
		dgModifier.connect( influenceLockPlug, lockPlug );
		
		// connect influence to the bindPose
		if ( !ignoreBindPose )
		{
			MPlug bindPoseMatrixPlug = bindPoseMatrixArrayPlug.elementByLogicalIndex( index );
			MPlug memberPlug = bindPoseMemberArrayPlug.elementByLogicalIndex( index );
			dgModifier.connect( influenceMessagePlug, bindPoseMatrixPlug );
			dgModifier.connect( influenceBindPosePlug, memberPlug );
		}
	}
	unsigned firstIndex = find( ignoreInfluence.begin(), ignoreInfluence.end(), false ) - ignoreInfluence.begin();
	influenceList.getDependNode( firstIndex, mObj );
	MFnDependencyNode fnInfluence( mObj );
	MPlug influenceMessagePlug = fnInfluence.findPlug( "message", true, &s );
	dgModifier.connect( influenceMessagePlug, paintPlug );
	if ( !dgModifier.doIt() )
	{
		dgModifier.undoIt();
		throw IECore::Exception( "ToMayaSkinClusterConverter: Unable to create the influence connections" );
	}
	
	// use influencePoseData as bindPreMatrix
	for ( unsigned i=0; i < numInfluences; i++ )
	{
		if ( ignoreInfluence[i] )
		{
			continue;
		}
		
		MMatrix preMatrix = ( i < origNumInfluences ) ? IECore::convert<MMatrix>( influencePoseData[i] ) : ignoredPreMatrices[i-origNumInfluences];
		MPlug preMatrixPlug = bindPreMatrixArrayPlug.elementByLogicalIndex( indexMap[i], &s );
		s = preMatrixPlug.getValue( mObj );
		if ( s )
		{
			MFnMatrixData matFn( mObj );
			matFn.set( preMatrix );
			mObj = matFn.object();
		}
		else
		{
			MFnMatrixData matFn;
			mObj = matFn.create( preMatrix );
		}
		
		preMatrixPlug.setValue( mObj );
	}
	
	// remove unneeded bindPreMatrix children
	unsigned existingElements = bindPreMatrixArrayPlug.numElements();
	for ( unsigned i=influenceList.length(); i < existingElements; i++ )
	{
		MPlug preMatrixPlug = bindPreMatrixArrayPlug.elementByLogicalIndex( i, &s );
		/// \todo: surely there is a way to accomplish this in c++...
		MGlobal::executeCommand( ( boost::format( "removeMultiInstance %s" ) % preMatrixPlug.name() ).str().c_str() );
	}
	
	// get the geometry
	MObjectArray outputGeoObjs;
	if ( !fnSkinCluster.getOutputGeometry( outputGeoObjs ) )
	{
		throw IECore::Exception( ( boost::format( "ToMayaSkinClusterConverter: skinCluster \"%s\" does not have any output geometry!" ) % fnSkinCluster.name() ).str() );
	}
	MFnDagNode dagFn( outputGeoObjs[0] );
	MDagPath geoPath;
	dagFn.getPath( geoPath );
	
	// loop through all the points of the geometry and set the weights
	MItGeometry geoIt( outputGeoObjs[0] );

	size_t pointCount = geoIt.exactCount();
	if( pointCount != pointIndexOffsets.size() )
	{
		throw IECore::Exception( ( boost::format( "ToMayaSkinClusterConverter: topology of skinCluster \"%s\"'s output geometry has changed!" ) % fnSkinCluster.name() ).str() );
	}

	MPlug weightListArrayPlug = fnSkinClusterNode.findPlug( "weightList", true, &s );
	for ( unsigned pIndex=0; !geoIt.isDone(); geoIt.next(), pIndex++ )
	{
		MPlug pointWeightsPlug = weightListArrayPlug.elementByLogicalIndex( pIndex, &s ).child( 0 );
		
		// remove existing influence weight plugs
		MIntArray existingInfluenceIndices;
		pointWeightsPlug.getExistingArrayAttributeIndices( existingInfluenceIndices );
		for( unsigned i=0; i < existingInfluenceIndices.length(); i++ )
		{
			MPlug influenceWeightPlug = pointWeightsPlug.elementByLogicalIndex( existingInfluenceIndices[i], &s );
			MGlobal::executeCommand( ( boost::format( "removeMultiInstance -break 1 %s" ) % influenceWeightPlug.name() ).str().c_str() );
		}
		
		// add new influence weight plugs
		int firstIndex = pointIndexOffsets[pIndex];
		for( int i=0; i < pointInfluenceCounts[pIndex]; i++ )
		{
			int influenceIndex = pointInfluenceIndices[ firstIndex + i ];
			if ( ignoreInfluence[ influenceIndex ] )
			{
				continue;
			}
			
			int skinClusterInfluenceIndex = fnSkinCluster.indexForInfluenceObject( influencePaths[ indexMap[ influenceIndex ] ] );
			MPlug influenceWeightPlug = pointWeightsPlug.elementByLogicalIndex( skinClusterInfluenceIndex, &s );
			influenceWeightPlug.setValue( pointInfluenceWeights[ firstIndex + i ] );
		}
	}
	
	return true;
}
void CoronaRenderer::defineMaterial(Corona::IInstance* instance, std::shared_ptr<MayaObject> mobj)
{
	std::shared_ptr<mtco_MayaObject> obj = std::static_pointer_cast<mtco_MayaObject>(mobj);
	
	MFnDependencyNode globalsNode(objectFromName("coronaGlobals"));

	if (getBoolAttr("useGlobalMaterialOverride", globalsNode, false))
	{
		MObject surfaceShader = getOtherSideNode(MString("globalMaterialOverride"), globalsNode.object());
		// get shading group for reuse
		MFnDependencyNode surfaceShaderNode(surfaceShader);
		MPlug outColorPlug = surfaceShaderNode.findPlug("outColor");
		MObject shadingGroupObject;
		if (outColorPlug.isConnected())
		{
			MPlugArray outArray;
			outColorPlug.connectedTo(outArray, false, true);
			if (outArray.length() > 0)
			{
				shadingGroupObject = outArray[0].node();
			}
		}
		if (shadingGroupObject != MObject::kNullObj)
			if (assingExistingMat(shadingGroupObject, obj))
				return;

		Corona::SharedPtr<Corona::IMaterial> base = defineCoronaMaterial(surfaceShader, obj);
		
		Corona::IMaterialSet ms = Corona::IMaterialSet(base);
		setRenderStats(ms, obj);
		obj->instance->addMaterial(ms);
		return;
	}
	getObjectShadingGroups(obj->dagPath, obj->perFaceAssignments, obj->shadingGroups, false);

	if( obj->shadingGroups.length() > 0)
	{
		for (uint sgId = 0; sgId < obj->shadingGroups.length(); sgId++)
		{
			MObject shadingGroup = obj->shadingGroups[sgId];
			Logging::debug(MString("---------- Check shading group: ") + getObjectName(shadingGroup) + " for existence on object named " +  obj->fullName);
			if (assingExistingMat(shadingGroup, obj))
				return;

			MObject surfaceShader = getConnectedInNode(shadingGroup, "surfaceShader");

			// check obj set overrides
			MObject connectedSet = getConnectedObjSet(obj->dagPath);
			if (connectedSet != MObject::kNullObj)
			{
				MFnDependencyNode setFn(connectedSet);
				Logging::debug(MString("Found connected object set:") + setFn.name());
				MPlug shaderOverride = setFn.findPlug("mtco_mtlOverride");
				if (!shaderOverride.isNull())
				{

					MObject connectedObject = getConnectedInNode(shaderOverride);
					if (connectedObject != MObject::kNullObj)
						surfaceShader = connectedObject;
				}
			}

			// raytype shader is a special case. Here a material set gets different materials, so I have to call defineCoronaMaterial several times
			MFnDependencyNode shaderMat(surfaceShader);
			Corona::SharedPtr<Corona::IMaterial> base = nullptr;
			Corona::SharedPtr<Corona::IMaterial> reflect = nullptr;
			Corona::SharedPtr<Corona::IMaterial> refract = nullptr;
			Corona::SharedPtr<Corona::IMaterial> direct = nullptr;

			if (shaderMat.typeName() == "CoronaRaytype")
			{
				MPlug basePlug = shaderMat.findPlug("base");
				MPlug reflectPlug = shaderMat.findPlug("reflect");
				MPlug refractPlug = shaderMat.findPlug("refract");
				MPlug directPlug = shaderMat.findPlug("direct");
				if (basePlug.isConnected())
				{
					MObject inNode = getConnectedInNode(basePlug);
					base = defineCoronaMaterial(inNode, nullptr);
				}
				if (reflectPlug.isConnected())
				{
					MObject inNode = getConnectedInNode(reflectPlug);
					reflect = defineCoronaMaterial(inNode, nullptr);
				}
				if (refractPlug.isConnected())
				{
					MObject inNode = getConnectedInNode(refractPlug);
					refract = defineCoronaMaterial(inNode, nullptr);
				}
				if (directPlug.isConnected())
				{
					MObject inNode = getConnectedInNode(directPlug);
					direct = defineCoronaMaterial(inNode, nullptr);
				}
			}
			else{
				base = defineCoronaMaterial(surfaceShader, obj);
			}

			Corona::IMaterialSet ms = Corona::IMaterialSet(base);
			ms.overrides.direct = direct;
			ms.overrides.reflect = reflect;
			ms.overrides.refract = refract;
			setRenderStats(ms, obj);
			obj->instance->addMaterial(ms);
		}
	}
	else{
		Corona::SharedPtr<Corona::IMaterial> mat = defineCoronaMaterial(MObject::kNullObj, nullptr);
		Corona::IMaterialSet ms = Corona::IMaterialSet(mat);
		setRenderStats(ms, obj);
		obj->instance->addMaterial(ms);
	}
}
Example #3
0
/**
 *	Build the animation from Maya MotionPath
 */
osg::ref_ptr<osg::AnimationPath> Transform::motionPath2AnimationPath(MObject &obj)
{
	osg::ref_ptr<osg::AnimationPath> anim = new osg::AnimationPath();

	// STEP 1. Get the animation curve from this MotionPath
	MFnMotionPath motion;

	MFnDependencyNode dn(obj);
	MPlugArray conns;
	dn.getConnections(conns);
	for(int i=0; i<conns.length(); i++){
		MPlug conn = conns[i];
		MPlugArray connectedTo;
		// Get the connections having this node as destination
		conn.connectedTo(connectedTo, true, false);
		for(int j=0; j<connectedTo.length(); j++){
			MPlug origin = connectedTo[j];
			MObject origin_node = origin.node();
			if(origin_node.hasFn(MFn::kMotionPath)){
				motion.setObject(origin_node);
				break;
			}
		}
	}

	MFnAnimCurve anim_curve;
	dn.setObject(motion.object());
	dn.getConnections(conns);
	for(int i=0; i<conns.length(); i++){
		MPlug conn = conns[i];
		MPlugArray connectedTo;
		// Get the connections having this node as destination
		conn.connectedTo(connectedTo, true, false);
		for(int j=0; j<connectedTo.length(); j++){
			MPlug origin = connectedTo[j];
			MObject origin_node = origin.node();
			if(origin_node.hasFn(MFn::kAnimCurve)){
				anim_curve.setObject(origin_node);
				break;
			}
		}
	}


	// STEP 2 ...


	// STEP 3. Benefits!

	for(int i=0; i<anim_curve.numKeys(); i++){

		MTime t = anim_curve.time(i);
		double time = t.as(MTime::kSeconds);

		MAnimControl::setCurrentTime(MTime(time,MTime::kSeconds));

		anim->insert(time, osg::AnimationPath::ControlPoint(
			getCPPosition(obj),
			getCPRotation(obj),
			getCPScale(obj)
			));
	}

	return anim;
}
void ParameterisedHolder<B>::nonNetworkedConnections( const MPlug &plug, MPlugArray &connectionsFromPlug, MPlugArray &connectionsToPlug ) const
{
    MPlugArray from;
    MPlugArray to;

    // the MPlug.connectedTo() method is documented as always returning networked plugs.
    plug.connectedTo( from, false, true );
    plug.connectedTo( to, true, false );

    connectionsFromPlug.clear();
    connectionsFromPlug.setLength( from.length() );
    connectionsToPlug.clear();
    connectionsToPlug.setLength( to.length() );

    for( unsigned i=0; i<from.length(); i++ )
    {
        // the MPlug( node, attribute ) constructor is documented as always returning non-networked plugs.
        connectionsFromPlug.set( MPlug( from[i].node(), from[i].attribute() ), i );
    }

    for( unsigned i=0; i<to.length(); i++ )
    {
        connectionsToPlug.set( MPlug( to[i].node(), to[i].attribute() ), i );
    }
}
Example #5
0
/**
 *	Build the animation from Maya AnimCurves
 */
osg::ref_ptr<osg::AnimationPath> Transform::animCurve2AnimationPath(MObject &obj)
{
	osg::ref_ptr<osg::AnimationPath> anim = new osg::AnimationPath();

	// STEP 1. Get the animation curves
	MFnAnimCurve tx, ty, tz, rx, ry, rz, sx, sy, sz;

	MFnDependencyNode dn(obj);
	MPlugArray conns;
	dn.getConnections(conns);
	for(int i=0; i<conns.length(); i++){
		MPlug conn = conns[i];
		MPlugArray connectedTo;
		// Get the connections having this node as destination
		conn.connectedTo(connectedTo, true, false);
		for(int j=0; j<connectedTo.length(); j++){
			MPlug origin = connectedTo[j];
			MObject origin_node = origin.node();
			if(origin_node.hasFn(MFn::kAnimCurve)){
				if(conn.name() == dn.name() + ".translateX" ){
					tx.setObject(origin_node);
				}
				else if(conn.name() == dn.name() + ".translateY" ){
					ty.setObject(origin_node);
				}
				else if(conn.name() == dn.name() + ".translateZ" ){
					tz.setObject(origin_node);
				}
				else if(conn.name() == dn.name() + ".rotateX" ){
					rx.setObject(origin_node);
				}
				else if(conn.name() == dn.name() + ".rotateY" ){
					ry.setObject(origin_node);
				}
				else if(conn.name() == dn.name() + ".rotateZ" ){
					rz.setObject(origin_node);
				}
				else if(conn.name() == dn.name() + ".scaleX" ){
					sx.setObject(origin_node);
				}
				else if(conn.name() == dn.name() + ".scaleY" ){
					sy.setObject(origin_node);
				}
				else if(conn.name() == dn.name() + ".scaleZ" ){
					sz.setObject(origin_node);
				}
				else {
					std::cout << "Animation curve connected to parameter " << conn.name().asChar() << " (not supported)" << std::endl;
				}
			}
		}
	}

	// STEP 2. Build the AnimationPath from animation curves

	// Search the first key in time from all the AnimCurves

	bool t_present=false;
	double mint=0;

#define VALID_CURVE(f)	( f.object() != MObject::kNullObj )

#define GETMIN(f)	if(VALID_CURVE(f)) { \
									double t = f.time(0).as(MTime::kSeconds); \
									if( !t_present || t < mint ) \
										mint = t; \
									t_present = true; \
								}

	GETMIN(tx);	GETMIN(ty);	GETMIN(tz);	GETMIN(rx);	GETMIN(ry);	GETMIN(rz);	GETMIN(sx);	GETMIN(sy);	GETMIN(sz);

	// Set the right time in Maya timeline so all properties are updated
	MAnimControl::setCurrentTime(MTime(mint,MTime::kSeconds));

	// Create ControlPoint for initial time
	anim->insert(mint, osg::AnimationPath::ControlPoint(
					getCPPosition(obj),
					getCPRotation(obj),
					getCPScale(obj)
					));

	double t_prev = mint;

	// Locate the time of the next key (in any curve)

#define GET_NEXT(f)	if(VALID_CURVE(f)) { \
									for(int c=0; c<f.numKeys(); c++){ \
										double t = f.time(c).as(MTime::kSeconds); \
										if(t > t_prev && !t_present){ \
											t_now = t; \
											t_present = true; \
											break; \
										} \
										else if(t > t_prev && t < t_now) { \
											t_now = t; \
											break; \
										} \
									} \
								}

	// Get next keys cronologically
	double t_now = t_prev;
	t_present=false;
	GET_NEXT(tx); GET_NEXT(ty); GET_NEXT(tz); 
	GET_NEXT(rx); GET_NEXT(ry); GET_NEXT(rz); 
	GET_NEXT(sx); GET_NEXT(sy); GET_NEXT(sz);
	while(t_now != t_prev){

		// Set the right time in Maya timeline so all properties are updated
		MAnimControl::setCurrentTime(MTime(t_now,MTime::kSeconds));

		anim->insert(t_now, osg::AnimationPath::ControlPoint(
			getCPPosition(obj),
			getCPRotation(obj),
			getCPScale(obj)
			));

		t_prev = t_now;
		t_present=false;
		GET_NEXT(tx); GET_NEXT(ty); GET_NEXT(tz); 
		GET_NEXT(rx); GET_NEXT(ry); GET_NEXT(rz); 
		GET_NEXT(sx); GET_NEXT(sy); GET_NEXT(sz);
	}

	return anim;
}