0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

maya get all assets (deadline's script)

Posted at
/*
Copyright 2017 Thinkbox Software
All Rights Reserved

This file has the following public functions. Examples of how to use them are below:
get_network_assets
get_filename_attributes_from_scene
get_source_file_paths_from_attr
get_files_to_copy
write_asset_introspection_file_transfer_pairs
gather_source_file_copies
change_to_relative_directory
write_repathing_mel_script_file
repath_assets
find_asset_paths
do_local_asset_caching

Use #1 - Local Asset Caching:
This code is used in production for our "Local Asset Caching". In this case, everything is run on the slave-side, it copies the files, then repaths them.
You can use it directly on the slave-side by calling do_local_asset_caching.
In this case, this file must be executed on the slave-side.

User #2 - Local hot folder synching and remote asset repathing:
This file also contains all the functions needed to do scene introspection and hot folder copying.
This is an example of what we'd do on the submission-side if you wanted to copy all the files into a hot-folder, followed
by creating a script that should run on the slave-side-end which will repath them to the correct place.
It was written for Rodeo FX, but was never put into production.
NOTE: In this case, this MEL script must execute on the SUBMITTER side.
So, if we want to use this code for both 'local asset caching' and this hot-folder thing, we have to have this AssetTools.mel file both on the slave and the submitter.

//Example submitter function:
proc function_to_run_on_submission_for_scene_introspection_and_hot_folder_syncing() {
	
	//This function creates a file called "asset_repathing_script.mel" which must be run on the slave-side to do the repathing.
	
	string $attrNames[0];
	string $cacheFileObjectNames[0];
	string $yetiAttrNames[0];
	string $sourceFiles[0];
	string $destFiles[0];
	string $gatheredSourceFiles[0];

	get_filename_attributes_from_scene( $attrNames, $cacheFileObjectNames, $yetiAttrNames );

	// Only use if you want to filter out files that are already local
	string $networkPrefixes[] = { "//", "/", "X:", "x:", "Y:", "y:", "Z:", "z:" };
	get_network_assets( $attrNames, "basic", $networkPrefixes );
	get_network_assets( $cacheFileObjectNames, "cacheFile", $networkPrefixes );
	get_network_assets( $yetiAttrNames, "yeti", $networkPrefixes );

	get_files_to_copy( "C:/temp", $attrNames, $cacheFileObjectNames, $yetiAttrNames, $sourceFiles, $destFiles );
	gather_source_file_copies("C:/temp", $sourceFiles, $gatheredSourceFiles );
	change_to_relative_directory( $gatheredSourceFiles, "C:/temp");

	write_asset_introspection_file_transfer_pairs( "C:/temp/maya_asset_pairs.txt", $gatheredSourceFiles, $destFiles );
	repath_assets( "C:/temp", $attrNames, $cacheFileObjectNames, $yetiAttrNames  );
	write_repathing_mel_script_file( "C:/temp/asset_repathing_script.mel", "C:/temp", $attrNames, $cacheFileObjectNames, $yetiAttrNames );
}

*/

///////////////////////////////INTERNAL FUNCTIONS////////////////////////////////////

proc int is_windows_path( string $v ) {
	//Starts with "//" (windows network)
	//Starts with "[driveletter]:/" (windows filesystem)
	//Add to this as needed.
	$v = fromNativePath( $v );
	if( size($v) < 4 ) //min size of four
		return false;
	$matchVal = `match ".*\n.*" $v`; //no newlines. important to add because of embedded mel in string attributes.
	if( $matchVal != "" )
		return false;
	if( startsWith( $v, "//" ) && !startsWith( $v, "///" ) ) //double slash starting. good.
		return true;
	$matchVal = `match "^[a-zA-Z]:/.*" $v`; //windows drive letter style. good. absolute paths only.
	if( $matchVal != "" )
		return true;
	return false;
}

proc int is_posix_path( string $v ) {
	//Starts with "/" (linux filesystem)
	//Add to this as needed.
	$v = fromNativePath( $v );
	if( size($v) < 2 ) //min size of two
		return false;
	$matchVal = `match ".*\n.*" $v`; //no newlines. important to add because of embedded mel in string attributes.
	if( $matchVal != "" )
		return false;
	if( startsWith( $v, "/" ) && !startsWith( $v, "//" ) ) //must start with single slash. only absolute paths supported (can't have a double slash. that's windows)
		return true;
	return false;
}

proc string to_native_path( string $v ) {
	//This is used to swap slashes on windows.
	//There is already a MEL script function called toNativePath, but swaps to the filename types of the current OS. We want to do it based on the actual style of path instead.
	//NOTE: MEL's "fromNativePath" ALWAYS converts everything to forward slashes, but "toNativePath" does it based on the current OS.
	if( is_windows_path( $v ) )
		while( $v != ( $v = `substitute "/" $v "\\"` ) ); //swap to backslashes
	return $v;
}

proc int looks_like_filepath( string $attrVal ) {
	//check all the cases for absolute filenames:
	if( is_windows_path( $attrVal ) )
		return true;
	if( is_posix_path( $attrVal ) )
		return true;
	return false;
}

proc string get_asset_path( string $attrName ) {
	//Get a filename out of a string attribute
	//Normally this is just the value of the attribute.
	//HOWEVER there are special exceptions that we may need to handle here.
	//An example might be filenames that use "####" in place of the current frame number or something. An example is Krakatoa objects. However, this isn't handled yet.

	//Normal case, just use the string attribute itself. Most things will end up here.
	$attrVal = `getAttr $attrName`; //should return string.
	
	//Just exclude the empty string case right away
	if( $attrVal == "" )
		return "";

	//This swaps backslashes to forward slashes
	$attrVal = fromNativePath( $attrVal );

	if( looks_like_filepath( $attrVal ) )
		return $attrVal;

	return "";
}

proc int is_attribute_a_string( string $nodeAndAttrName ) {
	//for some reason, some attributes produce a "The value for the attribute could not be retrieved" when trying to query them. If that's the case, then just skip it. I don't think this'll be an issue.
	if( catchQuiet( `getAttr -type $nodeAndAttrName` ) ) {
		return false;
	}

	$attrType = `getAttr -type $nodeAndAttrName`;
	if( $attrType == "string" ) {
		return true;
	}
	return false;
}

proc int is_multi_attribute_a_string( string $nodeName, string $attrNameComponents[] ) {
	//Test whether this multi-attribute holds strings
	//Without this test prior to recursion, we'll be recursively iterating through a lot of useless non-string attributes. For example, mesh vertices.
	//For example, recurse to the end of "tweak1.plist[0].controlPoints[0].xValue" and determine if "xValue" is a string attribute.
	
	$multiAttrName = $nodeName;
	for( $i=0; $i<size($attrNameComponents)-1; $i++ ) {
		$multiAttrName += "." + $attrNameComponents[$i];
		int $indexArray[] = `getAttr -multiIndices $multiAttrName`;
		if( size($indexArray) == 0 )
			return false; //this array of multi-attributes is empty. we don't need to continue.
		$multiAttrName += "[" + $indexArray[0] + "]"; //add the first index in this multi-attribute
	}
	$multiAttrName += "." + $attrNameComponents[ size($attrNameComponents)-1 ]; //add the actual non-multi-portion of the attribute to the end

	return is_attribute_a_string( $multiAttrName );
}

proc int is_attribute_storable( string $attrName )
{
	string $attrComponents[] = stringToStringArray( $attrName, "." );
	int $attrArraySize = `size($attrComponents)`;
	string $attrComponentAttr = $attrComponents[ $attrArraySize - 1 ];
	stringArrayRemoveAtIndex( $attrArraySize - 1, $attrComponents );
	string $attrComponentBase = stringArrayToString( $attrComponents, "." );
	
	return `attributeQuery -st -node $attrComponentBase $attrComponentAttr`;
}

proc get_multi_attrs_recursive( string $outAttrs[], string $attrNameBase, string $attrNameComponents[], int $level ) {
	//Examples:
	//file1.explicitUvTiles[0].explicitUvTileName
	//file1.explicitUvTiles[1].explicitUvTileName
	//tweak1.plist[0].controlPoints[0].xValue

	$attrName = $attrNameBase + "." + $attrNameComponents[$level];

	if( $level == size($attrNameComponents) - 1 ) {
		$outAttrs[ size($outAttrs) ] = $attrName;
		return;
	}
	
	int $indexArray[] = `getAttr -multiIndices $attrName`;
	for( $i=0; $i<size($indexArray); $i++ ) {
		$attrNameWithIndex = $attrName + "[" + $indexArray[$i] + "]";
		get_multi_attrs_recursive( $outAttrs, $attrNameWithIndex, $attrNameComponents, ($level+1) );
	}
}

proc string[] get_all_string_attributes_from_node( string $nodeName ) {

	string $outAttrs[];

	$attrNameList = `listAttr -usedAsFilename $nodeName`;

	for( $i=0; $i<size($attrNameList); $i++ ) {

		$attrName = $attrNameList[$i];
		
		//See if this is a "multi-attribute" by splitting on "."
		//Example: $attrName = "explicitUvTiles.explicitUvTileName"
		//Example: $attrName = plist.controlPoints.xValue... This needs to be accessed by tweak1.plist[0].controlPoints[0].xValue
		$attrNameComponents = stringToStringArray( $attrName, "." );

		if( size($attrNameComponents) >= 2 ) {

			//multi-attribute case. must use recursion to get the attributes.
			if( is_multi_attribute_a_string( $nodeName, $attrNameComponents ) ) {
				get_multi_attrs_recursive( $outAttrs, $nodeName, $attrNameComponents, 0 ); //will append to $outAttrs
			}

		} else {

			//Normal (non-multi) attribute case
			//Example: "file1.fileTextureName"
			string $fullNodeAndAttrName = $nodeName + "." + $attrName;

			if( is_attribute_a_string( $fullNodeAndAttrName ) ) {
				$outAttrs[ size($outAttrs) ] = $fullNodeAndAttrName; //append to $outAttrs
			}

		}
	}

	return $outAttrs;
}

proc string[] get_all_string_attributes_from_scene() {

	//The values in the outgoing array look like "nodeName.attrName" or "nodeName.multiAttrName[index].attrName", etc.

	string $allStringAttrs[0];

	$allSceneNodes = `ls`;
	for( $i=0; $i<size($allSceneNodes); $i++ ) {
		$nodeName = $allSceneNodes[$i];

		//Certain nodes and handled with special cases, and should be ignored here.
		//For example, "cacheFile" type objects use both a "Base Directory" and a "Base Name" attributes to construct a full filename.
		//So we handle those nodes separately later on.
		$nodeType = `objectType $nodeName`;
		if( $nodeType == "cacheFile" )
			continue;

		string $stringAttrs[] = `get_all_string_attributes_from_node $nodeName`;

		//append these to the outgoing array
		appendStringArray( $allStringAttrs, $stringAttrs, size($stringAttrs) );
	}

	return $allStringAttrs;
}

proc int check_if_file_exists( string $filename, string $nameOfSourceAttr ) {
	if( $filename == "" )
		return false;
	if( !`filetest -f $filename` ) {
		if( `filetest -d $filename` )
			print( "WARNING: Skipping potential introspection file. The following attribute appears to be a directory, and not a filename: " + $nameOfSourceAttr + " = '" + $filename + "'\n" );
		else
			print( "WARNING: Skipping potential introspection file. The following attribute looks like a filename, however the file does not exist: " + $nameOfSourceAttr + " = '" + $filename + "'\n" );
		return false;
	}
	return true;
}

proc get_all_filename_attributes_from_scene( string $outAttrNames[] ) {

	//This gives us all string attributes in the scene.
	//Both returned arrays are the same length. Together they make pairs.
	//The values in here look like "nodeName.attrName" or "nodeName.multiAttrName[index].attrName", etc.
	//Each one is queryable as-is and will return a string.
	string $stringAttrArray[] = `get_all_string_attributes_from_scene`;

	for( $i=0; $i<size($stringAttrArray); $i++ ) {
		$stringAttr = $stringAttrArray[$i];

		//Get the value out of this attribute.
		$filenameValue = get_asset_path( $stringAttr );

		//Test to see if this file actually exists.
		//The above code could have caught directories, etc. or just strings that look like filenames.
		// udim files won't be considered files since the token isn't evaluated
		if( `gmatch $filenameValue "*<udim>*"` || `gmatch $filenameValue "*<UDIM>*"` || check_if_file_exists( $filenameValue, $stringAttr ) ) {
			$outAttrNames[ size($outAttrNames) ] = $stringAttr; //append
		}
	}
}

proc get_all_filename_attributes_from_cacheFile_objects( string $outAttrNames[] ) {
	//Nodes of type cacheFiles are intentionally skipped over in the attribute introspection code.
	//That is because they present a special case where there are two files (.mcx, .xml) that are derived from a base directory and base name.
	//I'm not 100% sure if it's always a .mcx file that it needs, but in our current case, it is. There always appears to be a .xml file too.

	string $allCacheNodeNames[] = `ls -type "cacheFile"`;
	for( $i=0; $i<size($allCacheNodeNames); $i++ ) {
		$outAttrNames[ size( $outAttrNames ) ] = $allCacheNodeNames[$i];
	}
}

proc get_all_filename_attributes_from_yeti_objects( string $outYetiAttrNames[] ) {
	//http://peregrinelabs-deploy.s3.amazonaws.com/Documentation/Yeti/1.3.16/scriptingref.html

	//Currently we're supporting Yeti's texture node's "file_name" attribute, and Yeti's reference node's "reference_file" attribute.
	//Maybe in the future this could be expanded to be more general if needed.

	string $yetiShapesArray[] = `ls -type "pgYetiMaya"`;
	string $yetiShapesArrayA[] = `ls -type "pgYetiMayaFeather"`; //I really think only pgYetiMaya nodes can have a Yeti graph. But I don't know. Maybe these other two types can too. I have them in here just for safe measure.
	string $yetiShapesArrayB[] = `ls -type "pgYetiGroom"`;
	appendStringArray( $yetiShapesArray, $yetiShapesArrayA, size($yetiShapesArrayA) );
	appendStringArray( $yetiShapesArray, $yetiShapesArrayB, size($yetiShapesArrayB) );

	for( $i=0; $i<size($yetiShapesArray); $i++ ) {
		$yetiShape = $yetiShapesArray[$i];

		string $yetiTextureNodeList[0];
		if( catch( $yetiTextureNodeList = `pgYetiGraph -listNodes -type "texture" $yetiShape` ) ) {
			print( "WARNING: There are Yeti nodes in the scene, but the Yeti plugin does not appear to be loaded. Texture files within the Yeti nodes will not be repathed.\n" );
			return;
		}
		for( $j=0; $j<size($yetiTextureNodeList); $j++ ) {
			$yetiTextureNode = $yetiTextureNodeList[$j];
			$attrVal = `pgYetiGraph -node $yetiTextureNode -param "file_name" -getParamValue $yetiShape`;
			$attrName = $yetiShape + "." + $yetiTextureNode + ".file_name"; //Make it a period separted attribute name. This is just for convenience. Later on in the code we will split this out into 3 parts again.
			if( check_if_file_exists( $attrVal, $attrName ) ) {
				$outYetiAttrNames[ size($outYetiAttrNames) ] = $attrName;
			}
		}

		$yetiReferenceNodeList = `pgYetiGraph -listNodes -type "reference" $yetiShape`;
		for( $j=0; $j<size($yetiReferenceNodeList); $j++ ) {
			$yetiReferenceNode = $yetiReferenceNodeList[$j];
			$attrVal = `pgYetiGraph -node $yetiReferenceNode -param "reference_file" -getParamValue $yetiShape`;
			$attrName = $yetiShape + "." + $yetiReferenceNode + ".reference_file"; //Make it a period separted attribute name. This is just for convenience. Later on in the code we will split this out into 3 parts again.
			if( check_if_file_exists( $attrVal, $attrName ) ) {
				$outYetiAttrNames[ size($outYetiAttrNames) ] = $attrName;
			}
		}
	}
}

proc string asset_repathing_translation( string $sourcePath, string $repathRoot ) {
	//Here is the rules this function uses. Dest paths can be Windows or Posix. The input path always satisfies one of these three:
	// 1) C:/win/path/myfile.ext > /repath_root/C/win/path/myfile.ext
	// 2) //netdrive/win/path/myfile.ext > /repath_root/netdrive/win/path/myfile.ext
	// 3) /posix/path/myfile.ext > /repath_root/posix/path/myfile.ext

	//Potential trouble:
	//Some characters that are valid in Windows may not be valid in Posix paths. I'm just assuming here that all the characters are allowable in both systems.
	//Maybe need to do some additional mapping here to make it work.
	
	if( !looks_like_filepath( $repathRoot ) ) {
		print( "ERROR: Could not repath to here because it is not a valid absolute path: '" + $repathRoot + "'\n" );
		return "";
	}

	$repathRoot = fromNativePath( $repathRoot );
	if( substring( $repathRoot, size($repathRoot), size($repathRoot) ) == "/" ) //strip trailing slash
		$repathRoot = substring( $repathRoot, 1, size($repathRoot)-1 );

	$outPath = "";
	if( is_windows_path( $sourcePath ) ) {

		$winPath = "";
		if( startsWith( $sourcePath, "//" ) ) {
			$winPath = substring( $sourcePath, 3, size($sourcePath) ); //strip double-slash
		} else {
			//remove the ":", so it looks like "/repath_root/C/previous_path/filename.ext"
			$winPath = toupper( substring( $sourcePath, 1, 1 ) ) + substring( $sourcePath, 3, size($sourcePath) );
		}
		$outPath = $repathRoot + "/" + $winPath;

	} else { //is_posix_path() will return true here, because was checked earlier to be one or the other.
		$outPath = $repathRoot + $sourcePath;
	}
	return $outPath;
}

proc int is_local_file( string $path, string $networkPrefixes[] ) {
	int $isLocal = true;

	for( $i=0; $i<size($networkPrefixes); $i++ ) {
		if( startsWith( $path, $networkPrefixes[$i] ) ) {
			$isLocal = false;
			break;
		}
	}

	return $isLocal;
}

proc string[] get_cacheFile_paths( string $nodeName ) {
	string $cachePathAttr = $nodeName + ".cachePath";
	string $baseNameAttr = $nodeName + ".cacheName";
	string $baseName = `getAttr $baseNameAttr`;
	string $cachePath = `getAttr $cachePathAttr`;
	$cachePath = fromNativePath( $cachePath );

	//combine path and base name. make sure there's a "/" separator
	string $lastChar = `substring $cachePath (size($cachePath)) (size($cachePath))`;
	if( $lastChar != "/" )
		$cachePath = $cachePath + "/";
	$fullPathWithoutExt = $cachePath + $baseName;

	$mcxFilename = $fullPathWithoutExt + ".mcx";
	$xmlFilename = $fullPathWithoutExt + ".xml";

	return {$mcxFilename, $xmlFilename};
}

proc string get_cacheFile_directory( string $nodeName ) {
	string $cachePathAttr = $nodeName + ".cachePath";
	string $cachePath = `getAttr $cachePathAttr`;
	$cachePath = fromNativePath( $cachePath );

	//combine path and base name. make sure there's a "/" separator
	string $lastChar = `substring $cachePath (size($cachePath)) (size($cachePath))`;
	if( $lastChar != "/" )
		$cachePath = $cachePath + "/";

	return $cachePath;
}

proc string get_yeti_path( string $nodeName ) {
	string $yetiValues[];

	$yetiValues = stringToStringArray( $nodeName, "." ); // 3 period separated values
	return `pgYetiGraph -node $yetiValues[1] -param $yetiValues[2] -getParamValue $yetiValues[0]`;
}

proc translate_all_asset_files( string $repathRoot, string $attrNames[], string $cacheFileObjectNames[], string $yetiAttrNames[], string $outAttrFiles[], string $outCacheFiles[], string $outYetiFiles[] ) {
	clear($outAttrFiles);
	clear($outCacheFiles);
	clear($outYetiFiles);

	// translate attribute paths 
	for( $i=0; $i<size($attrNames); $i++ ) {
		$outAttrFiles[$i] = asset_repathing_translation( get_asset_path( $attrNames[$i] ), $repathRoot );
	}

	// translate cacheFile paths
	for( $i=0; $i<size($cacheFileObjectNames); $i++ ) {
		$outCacheFiles[$i] = asset_repathing_translation( get_cacheFile_directory( $cacheFileObjectNames[$i] ), $repathRoot );
	}

	// translate yeti paths
	for( $i=0; $i<size($yetiAttrNames); $i++ ) {
		$outYetiFiles[$i] = asset_repathing_translation( get_yeti_path( $yetiAttrNames[$i] ), $repathRoot );
	}
}

proc int manifest_file_dirty( string $manifestPath, string $sourceFile, string $destFile )
{
	// given an existing manifest path and two existing asset paths (cache and network), compare the timestamps of the manifest and the files.
	// the first line of the manifest file will be the cacheFile's timestamp, the second is the networkFile's timestamp
	python( "import os" );
	string $sourceModifiedtime = python( "os.path.getmtime('" + $sourceFile + "')" );
	string $destModifiedtime = "";
	if( catch( $destModifiedtime = python( "os.path.getmtime('" + $destFile + "')" ) ) )
	{
		print( "Failed to get modified time from file: " + $destFile );
		return true;
	}

	$fileId = `fopen $manifestPath "r"`;

	$sourceManifestTime = `fgetline $fileId`;
	$destManifestTime = `fgetline $fileId`;
	fclose $fileId;

	$sourceManifestTime = substituteAllString($sourceManifestTime, "\n", "");
	$destManifestTime = substituteAllString($destManifestTime, "\n", "");

	return !( ($sourceModifiedtime == $sourceManifestTime) && ($destModifiedtime == $destManifestTime ) );
}

// Walk over manifest files in a directory and delete the old assets
proc inspect_manifests( string $manifestFiles[], int $deleteTimer )
{
	python( "import time" );
	int $manifestSize_CONST = 9; // ".manifest" has 9 characters
	int $deleteTimerInSec = $deleteTimer * 24 * 60 * 60;
	for( $manifest in $manifestFiles )
	{
		$fileId = `fopen $manifest "r"`;
		string $filedata = `fread $fileId $filedata`;
		string $fileLines[];
		tokenize $filedata "\n" $fileLines;

		int $manifestTime =  int( $fileLines[size($fileLines)-1] );
		fclose $fileId;

		int $currTime = python( "int(time.time())" );
		//print( "elapsed=" + ( $currTime - $manifestTime ) + " required=" + $deleteTimerInSec + "\n" );
		if( $currTime - $manifestTime > $deleteTimerInSec )
		{
			string $assetName = `substring $manifest 1 (size($manifest)-$manifestSize_CONST)`;
			print( "Deleting asset file " + $assetName + " because it is more than " + $deleteTimer + " days old\n" );
			sysFile -delete $assetName;
			sysFile -delete $manifest;
		}
	}
}

proc delete_assets( string $cacheDir, int $deleteTimer ) {

	string $manifests[] = `getFileList -folder $cacheDir -fs "*.manifest"`;
	for( $i = 0; $i < size($manifests); $i++ )
	{
		$manifests[$i] = $cacheDir + "/" + $manifests[$i];
	}
	string $folders[] = `getFileList -folder $cacheDir`;
	for( $folder in $folders )
	{
		string $currFolder = $cacheDir + "/" + $folder;
		delete_assets( $currFolder, $deleteTimer );
	}
	inspect_manifests( $manifests, $deleteTimer );
}

////////////////////// EXTERNAL FUNCTIONS TO BE USED FOR HOT-FOLDER SYNCHING AND REPATH, AND LOCAL ASSET CACHING //////////////////

// Using $networkPrefixes we filter out all assets that don't have a network prefix so that we only mess with network files
global proc get_network_assets( string $outAttrNames[], string $type, string $networkPrefixes[] ) {
	string $filenameValue;

	for( $i=size($outAttrNames)-1; $i>=0; $i-- ) {
		if( $type == "cacheFile" ) {
			string $filenames[] = get_cacheFile_paths( $outAttrNames[$i] );
			// Only need to check 1 path, as they have the same folder
			$filenameValue = $filenames[0];
		} else if( $type == "yeti" ) {
			$filenameValue = get_yeti_path( $outAttrNames[$i] );
		} else { // basic
			$filenameValue = get_asset_path( $outAttrNames[$i] );
		}

		if( is_local_file( $filenameValue, $networkPrefixes ) ) {
			stringArrayRemoveAtIndex( $i, $outAttrNames );
		}
	}
}

global proc get_filename_attributes_from_scene( string $outAttrNames[], string $outCacheFileObjectNames[], string $outYetiAttrNames[] ) {
	clear( $outAttrNames );
	clear( $outCacheFileObjectNames );
	clear( $outYetiAttrNames );

	//First get name/value pairs from string attributes in the scene
	//This will be the majority of all assets. Special case attributes to follow.
	get_all_filename_attributes_from_scene( $outAttrNames );

	//Special case alert: Nodes of type "cacheFile" need to be special caseed because they combine two attributes to make a path.
	get_all_filename_attributes_from_cacheFile_objects( $outCacheFileObjectNames );

	//Special case the Yeti nodes because you have to get attributes differently.
	if( `pluginInfo -query -loaded "pgYetiMaya"` ){
		get_all_filename_attributes_from_yeti_objects( $outYetiAttrNames );
	}
}

global proc get_source_file_paths_from_attr( string $outAttrNames[], string $outCacheFileObjectNames[], string $outYetiAttrNames[], string $outSourceFiles[] ){
	python( "import maya.app.general.fileTexturePathResolver" );
	
	// Build list of files
	for( $i=0; $i<size($outAttrNames); $i++ ) {
		$sourceFile = get_asset_path( $outAttrNames[$i] );
		
		// check for tokens in file pattern
		string $computedFileAttr = `substitute "fileTextureName" $outAttrNames[$i] "computedFileTextureNamePattern"`;
		if (`objExists $computedFileAttr`) {
			string $seq_pattern = `getAttr($computedFileAttr)`;
			string $filePaths[] = python( "maya.app.general.fileTexturePathResolver.findAllFilesForPattern( '" + $seq_pattern + "', None )" );
			for( $j=0; $j<size($filePaths); $j++) {
				$outSourceFiles[ size( $outSourceFiles ) ] = $filePaths[$j];
			}
		} else {
			$outSourceFiles[ size( $outSourceFiles ) ] = $sourceFile;
		}
	}

	for( $i=0; $i<size($outCacheFileObjectNames); $i++ ) {
		appendStringArray( $outSourceFiles, get_cacheFile_paths( $outCacheFileObjectNames[$i] ), 2 );
	}
	for( $i=0; $i<size($outYetiAttrNames); $i++ ) {
		$outSourceFiles[ size( $outSourceFiles ) ] = get_yeti_path( $outYetiAttrNames[$i] );
	}
	$outSourceFiles = stringArrayRemoveDuplicates( $outSourceFiles );
}

global proc get_files_to_copy( string $repathRoot, string $outAttrNames[], string $outCacheFileObjectNames[], string $outYetiAttrNames[], string $outSourceFiles[], string $outDestFiles[] ){
	get_source_file_paths_from_attr( $outAttrNames, $outCacheFileObjectNames, $outYetiAttrNames, $outSourceFiles );

	//Translate the paths, and put into our outgoing-variable $outDestFiles
	for( $i=0; $i<size($outSourceFiles); $i++ ) {
		$outDestFiles[$i] = asset_repathing_translation( $outSourceFiles[$i], $repathRoot );
	}
}

global proc get_redshift_cache_files( string $sourceFiles[], string $outRedshiftCacheFiles[] ) {
	
	for( $sourceFile in $sourceFiles ){
		string $sourceDirectory = dirname( $sourceFile );
		string $oldExtension = fileExtension( $sourceFile );
		string $filename = basename( $sourceFile, $oldExtension );
		string $cacheFiles[] = `getFileList -folder $sourceDirectory -filespec ($filename+"rs*bin")`;
		for( $cacheFile in $cacheFiles )
		{
			$outRedshiftCacheFiles[ size( $outRedshiftCacheFiles ) ] = ( $sourceDirectory +"/"+ $cacheFile );
		}
	}
}

global proc write_asset_introspection_file_transfer_pairs( string $outputSourceDestPairsFilename, string $sourceFiles[], string $destFiles[] ) {

	print( "ASSETS TRANSFERED: " + size( $sourceFiles ) + "\n" );
	if( size( $sourceFiles ) > 0 ) {
		$outputSourceDestPairsFilename = fromNativePath( $outputSourceDestPairsFilename ); //Sanitize
		$fileId = `fopen $outputSourceDestPairsFilename "w"`;

		for( $i=0; $i<size($sourceFiles); $i++ ) {
			//swap to windows-style backslashes for the output of all this. currently they're all forward slashes.
			$source = $sourceFiles[$i];
			$dest = $destFiles[$i];
			$source = to_native_path( $source ); //swap to backslashes if necessary
			$dest = to_native_path( $dest );

			$fileLineStr = ( $source + ">" + $dest + "\n" );
			fprint $fileId $fileLineStr;
			print( $fileLineStr ); //For debug
		}

		fclose $fileId;
	}
}

global proc gather_source_file_copies( string $gatherDirectoryRoot, string $inOrigSourceFiles[], string $outNewSourceFiles[] ) {

	//NOTE: On error (couldn't copy for some reason), an error message is printed, and the original path to the file is returned.
	//We might need to change this behaviour to handle errors better/differently.

	//Sanitize inputs
	clear( $outNewSourceFiles );
	$gatherDirectoryRoot = fromNativePath( $gatherDirectoryRoot );
	if( substring( $gatherDirectoryRoot, size($gatherDirectoryRoot), size($gatherDirectoryRoot) ) != "/" ) //add trailing slash
		$gatherDirectoryRoot += "/";

	for( $i=0; $i<size($inOrigSourceFiles); $i++ ) {

		$sourceFile = fromNativePath( $inOrigSourceFiles[$i] );
		$destFile = "";

		$destFile = asset_repathing_translation( $sourceFile, $gatherDirectoryRoot );
		string $destDirectory = python( "import os; os.path.dirname('" + $destFile + "')" ); //get directory part
		
		$dirSuccess = true;
		if( !`filetest -d $destDirectory` ) {
			$pythonScriptMakeDir = "import os; os.makedirs('" + $destDirectory + "')"; //recursive dir creation
			if( catch( python( $pythonScriptMakeDir ) ) ) {
				print( "ERROR: Could not create the gathering destination directory: '" + $destFile + "'\n" );
				$destFile = $sourceFile;
				$dirSuccess = false;
			}
		}
		
		if( $dirSuccess ) { // manifest is of the form src \n dest ( network \n cache )
			$doCopy = false;
			$manifestPath = $destFile + ".manifest";
			// if manifest non existant or manifest dirty
			// something is wrong with checking the status of the file
			if( !(`file -q -exists $manifestPath`) || manifest_file_dirty( $manifestPath, $sourceFile, $destFile) ) {
				print("Manifest file outdated... Updating asset cache.\n");
				$doCopy = true;
			}
			else { // manifest file exists and is clean
				print("Manifest file is up-to-date... Rewriting last accessed time.\n");
				// write back manifest data with updated access time
				python( "import time" );

				// grab the first two lines to write them back to the file
				$fileId = `fopen $manifestPath "r"`;

				$sourceManifestTime = `fgetline $fileId`;
				$destManifestTime = `fgetline $fileId`;
				fclose $fileId;

				$fileId = `fopen $manifestPath "w"`;
				string $currTime = python( "int(time.time())" );
				fprint $fileId $sourceManifestTime;
				fprint $fileId $destManifestTime;
				fprint $fileId $currTime;
				fclose $fileId;
			}
		
			if( $doCopy ) {
				$copySuccess = `sysFile -copy $destFile $sourceFile`;
				if( !$copySuccess ) {
					print( "ERROR: Could not copy file from '" + $sourceFile + "' to '" + $destFile + "'\n" );
					$destFile = $sourceFile;
				} else {
					print( "File copied from '" + $sourceFile + "' to '" + $destFile + "'\n" );
					// (over)write manifest file. sourcetime \n desttime
					$fileId = `fopen $manifestPath "w"`;
					python( "import os, time" );

					string $newSourceManifest = python("os.path.getmtime('" + $sourceFile + "')");
					string $newDestManifest = python("os.path.getmtime('" + $destFile + "')");
					string $currTime = python( "int(time.time())" );

					fprint $fileId ($newSourceManifest+"\n");
					fprint $fileId ($newDestManifest+"\n");
					fprint $fileId $currTime;
					// set line 3 to current time
					fclose $fileId;
				}
			}
		}
		$outNewSourceFiles[$i] = to_native_path( $destFile );
	}
}

global proc change_to_relative_directory( string $fileArray[], string $rootDir ) {
	//Convenience function. Turns absolute paths into relative paths based on the root directory.

	$rootDir = fromNativePath( $rootDir );
	if( substring( $rootDir, size($rootDir), size($rootDir) ) != "/" ) //add trailing slash
		$rootDir += "/";

	for( $i=0; $i<size($fileArray); $i++ ) {
		$file = fromNativePath( $fileArray[$i] );
		if( startsWith( $file, $rootDir ) ) {
			$newFile = substring( $file, size($rootDir)+1, size($file) );
			if( is_windows_path( $file ) ) {
				while( $newFile != ( $newFile = `substitute "/" $newFile "\\"` ) ); //swap to backslashes
			}
			$fileArray[$i] = $newFile;
		}
	}
}

global proc write_repathing_mel_script_file( string $outputMelScriptRepathingFilename, string $repathRoot, string $attrNames[], string $cacheFileObjectNames[], string $yetiAttrNames[] ) {
	string $translatedAttrFiles[];
	string $translatedCacheFiles[];
	string $translatedYetiFiles[];

	translate_all_asset_files( $repathRoot, $attrNames, $cacheFileObjectNames, $yetiAttrNames, $translatedAttrFiles, $translatedCacheFiles, $translatedYetiFiles );

	$fileId = `fopen $outputMelScriptRepathingFilename "w"`;

	// repath attribute paths
	for( $i=0; $i<size($attrNames); $i++ ) {
		if( is_attribute_storable( $attrNames[$i] ) ) {

			if( `gmatch $translatedAttrFiles[$i] "*<udim>*"` || `gmatch $translatedAttrFiles[$i] "*<UDIM>*"`|| `filetest -f $translatedAttrFiles[$i]` ) {
				$fileLineStr = "catch( `setAttr -type \"string\" \"" + $attrNames[$i] + "\" \"" + $translatedAttrFiles[$i] + "\"` );\n";
				fprint $fileId $fileLineStr;
			}
		}
	}

	// repath cacheFile directories
	for( $i=0; $i<size($cacheFileObjectNames); $i++ ) {
		string $cachPathAttr = $cacheFileObjectNames[$i] + ".cachePath";
		if(`attributeQuery -st -node $cacheFileObjectNames[$i] "cachePath"` ) {
			if( `filetest -f $translatedCacheFiles[$i]` ) {
				$fileLineStr = "catch( `setAttr -type \"string\" \"" + $cachPathAttr + "\" \"" + $translatedCacheFiles[$i] + "\"` );\n";
				fprint $fileId $fileLineStr;
			}
		}
	}

	// repath yeti
	for( $i=0; $i<size($yetiAttrNames); $i++ ) {
		string $attrComponents[];
		$attrComponents = stringToStringArray( $yetiAttrNames[$i], "." ); //Always size 3 because we made these in the above code.
		$attrValue = $translatedYetiFiles[$i];
		$shape = $attrComponents[0];
		$node = $attrComponents[1];
		$attr = $attrComponents[2];

		if( `filetest -f $attrValue` ) {
			$fileLineStr = "catch( `pgYetiGraph -node \"" + $node + "\" -param \"" + $attr + "\" -setParamValueString \"" + $attrValue + "\" \"" + $shape + "\"` );\n";
			fprint $fileId $fileLineStr;
		}
	}

	fclose $fileId;
}

global proc repath_assets( string $repathRoot, string $attrNames[], string $cacheFileObjectNames[], string $yetiAttrNames[] ) {
	string $translatedAttrFiles[];
	string $translatedCacheFiles[];
	string $translatedYetiFiles[];

	translate_all_asset_files( $repathRoot, $attrNames, $cacheFileObjectNames, $yetiAttrNames, $translatedAttrFiles, $translatedCacheFiles, $translatedYetiFiles );

	// repath attribute paths
	for( $i=0; $i<size($attrNames); $i++ ) {
		if( is_attribute_storable( $attrNames[$i] ) ){
			if( `gmatch $translatedAttrFiles[$i] "*<udim>*"` || `gmatch $translatedAttrFiles[$i] "*<UDIM>*"`|| `filetest -f $translatedAttrFiles[$i]` ) {
				catch( `setAttr -type "string" $attrNames[$i] $translatedAttrFiles[$i]` );
			}
		}
	}

	// repath cacheFile directories
	for( $i=0; $i<size($cacheFileObjectNames); $i++ ) {
		string $cachPathAttr = $cacheFileObjectNames[$i] + ".cachePath";
		if(`attributeQuery -st -node $cacheFileObjectNames[$i] "cachePath"` ) {
			if( `filetest -f $translatedCacheFiles[$i]` ) {
				catch( `setAttr -type "string" $cachPathAttr $translatedCacheFiles[$i]` );
			}
		}
	}

	// repath yeti
	for( $i=0; $i<size($yetiAttrNames); $i++ ) {
		string $attrComponents[];
		$attrComponents = stringToStringArray( $yetiAttrNames[$i], "." ); //Always size 3 because we made these in the above code.
		$attrValue = $translatedYetiFiles[$i];
		$shape = $attrComponents[0];
		$node = $attrComponents[1];
		$attr = $attrComponents[2];

		if( `filetest -f $attrValue` ) {
			catch( `pgYetiGraph -node $node -param $attr -setParamValueString $attrValue $shape` );
		}
	}
}

global proc delete_old_assets( string $cacheDir, int $deleteTimer )
{
		string $cachePaths[];
		tokenize $cacheDir ">" $cachePaths;
		delete_assets( $cachePaths[0], $deleteTimer );
}

string $dl_ignorable_asset_attributes[] = {"rmanGlobals.imageOutputDir","rmanGlobals.ribOutputDir"};
proc int is_ignorable_attribute( string $attr )
{
	global string $dl_ignorable_asset_attributes[];
	return `stringArrayContains $attr $dl_ignorable_asset_attributes`;
}

global proc string[] find_asset_paths()
{
	filePathEditor -refresh;
	string $file_paths[];
	string $dirs[] = `filePathEditor -q -listDirectories ""`;
	for( $dir in $dirs )
	{
		string $files[] = `filePathEditor -q -withAttribute -listFiles $dir`;
		int $i;
		for( $i = 0; $i<size($files); $i+=2)
		{
			if( !`is_ignorable_attribute $files[$i+1]` )
			{
				string $file_path = $dir + "/" + $files[$i];
				print("Found Asset: " + $files[$i+1] + " - " + $file_path + "\n" );
				$file_paths[ size($file_paths) ] = $dir + "/" + $files[$i]; //append
			}
		}
	}

	return $file_paths;
}

global proc do_local_asset_caching( string $localAssetCacheDirectory, string $semiColonSeparatedNetworkPrefixes, string $slaveLACDaysToDelete )
{
	string $attrNames[0];
	string $cacheFileObjectNames[0];
	string $yetiAttrNames[0];
	string $sourceFiles[0];
	string $redshiftCacheFiles[0];
	string $destFiles[0];
	string $gatheredSourceFiles[0];
	string $gatheredRedshiftSourceFiles[0];

	//identify all attributes that contain filenames
	get_filename_attributes_from_scene( $attrNames, $cacheFileObjectNames, $yetiAttrNames );

	//of the identified files above, filter out the ones that aren't in a "network" location.
	string $networkPrefixes[] = stringToStringArray( $semiColonSeparatedNetworkPrefixes, ";" );
	get_network_assets( $attrNames, "basic", $networkPrefixes );
	get_network_assets( $cacheFileObjectNames, "cacheFile", $networkPrefixes );
	get_network_assets( $yetiAttrNames, "yeti", $networkPrefixes );

	//copy the asset files locally (if needed, based on their .manifest files)
	get_files_to_copy( $localAssetCacheDirectory, $attrNames, $cacheFileObjectNames, $yetiAttrNames, $sourceFiles, $destFiles );
	gather_source_file_copies( $localAssetCacheDirectory, $sourceFiles, $gatheredSourceFiles );
	
	if( currentRenderer() == "redshift")
	{
		get_redshift_cache_files( $sourceFiles, $redshiftCacheFiles );
		gather_source_file_copies( $localAssetCacheDirectory, $redshiftCacheFiles, $gatheredSourceFiles );
	}
	
	//change the asset paths in the scene to point at the ones in the cache folder
	repath_assets( $localAssetCacheDirectory, $attrNames, $cacheFileObjectNames, $yetiAttrNames );

	//does a cleanup pass through the local asset cahce directory, checks their manifests, and deletes them if they're too old.
	delete_old_assets( $localAssetCacheDirectory, $slaveLACDaysToDelete ); 
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?