/*
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 );
}
More than 1 year has passed since last update.
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme