- Home /
Can imported assets be automatically renamed by script?
I'm trying to eliminate one small step in a process I repeat a lot as I update artwork: deleting old assets before I import replacements. Specifically, I'm importing images as sprites in a 2D project and I frequently reimport artwork to see updates in-game.
The idea was to use AssetPostprocessor.OnPostprocessSprites to look for " 1" in the AssetImporter.assetPath of a newly imported sprite. If present, delete the original with AssetDatabase.DeleteAsset and rename the new without " 1" using AssetDatabase.RenameAsset.
The trouble is RenameAsset can't find the asset at assetPath:
using UnityEngine;
using UnityEditor;
public class SpriteRenamer : AssetPostprocessor
{
void OnPostprocessSprites(Texture2D texture, Sprite[] sprites)
{
TextureImporter importer = (TextureImporter)assetImporter;
//returns "Assets/foo.png"
Debug.Log(importer.assetPath);
//returns "The source asset could not be found"
Debug.Log(AssetDatabase.RenameAsset(importer.assetPath, "bar.png"));
}
}
Hardcoding the path gave the same result...
Debug.Log(AssetDatabase.RenameAsset("Assets/foo.png", "bar.png"));
...but helped me discover renaming DOES work if I'm not trying to rename the asset currently being imported / postprocessed.
In other words, the first time I import foo.png, renaming fails, but the second time (when the imported file is actually "foo 1.png") the old foo.png is successfully renamed to bar.png.
OnPostprocessSprites claims to "get a notification when an texture of sprite(s) has completed importing" but it seems to me Unity is not actually done with the asset at that point since AssetDatabase can't see it.
Are there any other ideas to accomplish this goal? Is there anything wrong with the script above?
Final code using the accepted answer to automatically overwrite existing sprites with the same name in the same location:
void OnPostprocessSprites(Texture2D texture, Sprite[] sprites)
{
TextureImporter spriteImporter = (TextureImporter)assetImporter;
//end of directory path, start of file name
int fileNamePos = spriteImporter.assetPath.LastIndexOf("/");
//end of file name, start of file extension
int fileExtPos = spriteImporter.assetPath.LastIndexOf(".");
//parent directory with trailing slash
string filePath = spriteImporter.assetPath.Substring(0, fileNamePos + 1);
//isolated file name
string fileName = spriteImporter.assetPath.Substring(fileNamePos + 1, fileExtPos - filePath.Length);
//extension with "."
string fileExt = spriteImporter.assetPath.Substring(fileExtPos);
//check if this asset should replace another ("name 1.ext" should replace "name.ext")
if(spriteImporter.assetPath.EndsWith(" 1" + fileExt))
{
//file name without trailing " 1"
string fileNameOld = spriteImporter.assetPath.Substring(fileNamePos + 1, (fileExtPos - filePath.Length) - 2);
//move old asset to trash so new asset can have the same name
AssetDatabase.MoveAssetToTrash(filePath + fileNameOld + fileExt);
//non-empty string indicates failure
if(AssetDatabase.RenameAsset(spriteImporter.assetPath, fileNameOld) != "")
{
//ILogger refreshLogger = ???
//refreshLogger.logEnabled = false;
//calls OnPostprocessSprites (for the 2nd time)
AssetDatabase.Refresh(); //prints crash message to console
//refreshLogger.logEnabled = true;
}
else //successful RenameAsset calls OnPostprocessSprites (for the 3rd time)
{
//clear Refresh's crash message and unfortunately the whole console
//Debug.ClearDeveloperConsole();
}
}
else
{
//finally do post-processing here
Debug.Log("post-processing: "+spriteImporter.assetPath);
}
}
There are a couple ways to deal with AssetDatabase.Refresh's crash message, commented out in the code above. One temporarily disables the logger Refresh uses, but it's not Debug.logger so I'm not sure how to get it. The other idea clears the whole console after Refresh which is probably worse, so for now I'm just dealing with a dirty console.
@androgendo just an fyi to save you a metric ton of time - use System.Path ins$$anonymous$$d of string functions.
var fileNameWithoutExtension = System.Path.GetFileNameWithoutExtension(this.assetPath);
var directoryPath = System.Path.GetDirectoryName(this.assetPath)
and there's many more functions which all work perfectly with Unity (at-least in Editor classes).
Answer by Naphier · May 12, 2016 at 07:17 AM
You might need to do AssetDatabase.Refresh or maybe AssetDatabase.ImportAsset (similarly textureImporter.SaveAndReimport).
I appreciate the suggestion - I had not tried those. Unfortunately they didn't help or I'm not using them properly. I tried each just before the RenameAsset() line. For ImportAsset() I used importer.assetPath.
Refresh may have given a clue though. It prints this to the console: "A default asset was created for 'Assets/foo.png' because the asset importer crashed on it last time."
Along the same lines, I just tried AssetDatabase.SaveAssets, but there was no improvement.
Just tried it out and I got it to work, but it's not great. One thing to note before I dive in is that you don't actually want to put ".png" on the end. Just put in the new name.
So your script works just fine if the asset is already imported and you reimport. So that's telling me that the asset database hasn't finished all it needs to do. If I put in AssetDatabase.Refresh() it works just fine, but I get a message: A default asset was created for 'Assets/renamed.png' because the asset importer crashed on it last time. You can select the asset and use the 'Assets -> Reimport' menu command to try importing it again, or you can replace the asset and it will auto import again.
Ugly.... but if you can live with that message then this it the way to do it:
void OnPostprocessSprites(Texture2D texture, Sprite[] sprites)
{
Debug.Log("texture: " + texture.name);
foreach (var item in sprites)
{
Debug.Log("sprite: " + item.name);
}
TextureImporter textureImporter = (TextureImporter)assetImporter;
Debug.Log("assetPath: " + textureImporter.assetPath);
AssetDatabase.Refresh();
string result = AssetDatabase.RenameAsset(textureImporter.assetPath, "renamed2");
Debug.Log("result: " + result);
}
Using textureImporter.SaveAndReimport() is apparently a horrible idea here (sorry!) as I think it just creates an infinite loop.
An alternative would be to write a menu script like this:
using UnityEngine;
using UnityEditor;
public class RenameSprites : Editor
{
[$$anonymous$$enuItem("Tools/$$anonymous$$yTool/$$anonymous$$yEditorScript")]
static void DoIt()
{
string[] sprites = AssetDatabase.FindAssets("t:sprite");
for (int i = 0; i < sprites.Length; i++)
{
AssetDatabase.RenameAsset(AssetDatabase.GUIDToAssetPath(sprites[i]), "sprite" + i);
}
AssetDatabase.SaveAssets();
}
}
I'm not sure why OnPostprocessSprite throws that message when changing the name. It's apparently not the place to rename an imported asset. I'm not sure when a better time would be, but it seems it is interrupting the import process, which seems like it should have finished by the time OnPostprocess gets called...
I don't know what I did wrong when I tried before, but you're right, AssetDatabase.Refresh does the trick! And good point, I shouldn't include a file extension in the newName of AssetDatabase.RenameAsset.
I'll add the final code to the end of my original question for prettier formatting.
The Refresh message is ugly as you say, but it gets the job done. In the final code above I commented out a couple ideas to deal with that message.
Cool. I was hoping you could do Debug.logger.logEnabled = false, but that doesn't seem to work. I usually do asset database work in custom menus, but I understand why you're doing it in post process.