Creating Textures at Import (OnAssignMaterialModel)

I’m trying to automatically create textures on import, to merge seperate specular maps into the diffuse texture and because I need to reverse the green channel on the normal maps.

I’ve managed to get it to work so that it generates the textures correctly and all, but for reasons I don’t understand it doesn’t assign them to the material. If I run the same script again (i.e. click “reimport” once more), everything works just fine.

Which is crazy, given that on the 2nd run, when I skip the generation, I do the very same things (load asset and return it).

// FbxMeshPostProcessor utility script
// Written by Roger Clark 20/04/2009
// 
// I wrote this script as the fbx models I was importing into Unity had the wrong "Scale factor"
// I also wanted all models to have a collider
// Note. When I did not set a value for generateMaterials, it appeared to default to not generating materials, 
// so I set generateMaterials to true.


class FbxMeshPostprocessor extends AssetPostprocessor  {

	public var AnisoLevel = 4;

	function OnPreprocessModel ()  {
		var modelImporter : ModelImporter = assetImporter;
		var fname:String = modelImporter.assetPath.ToLower();
		modelImporter.generateMaterials = 1;// Enable material creation
		modelImporter.addCollider = false; // This tells it to not add a collider to the object 
		modelImporter.globalScale= 1; // This changes the "Scale Factor" to 1.0

		modelImporter.generateAnimations = ModelImporterGenerateAnimations.None;
	}

	function OnAssignMaterialModel(material : Material, renderer : Renderer) : Material {
		var materialPath = "Assets/_generated materials/" + material.name + ".mat";

		// Find if there is a material at the material path
		// Turn this off to always regeneration materials
		//		if (AssetDatabase.LoadAssetAtPath(materialPath, Material)) return AssetDatabase.LoadAssetAtPath(materialPath, Material);

		// set the names of the normal and specular maps we are looking for
		// this is the setting that DEXSOFT textures use - N and S suffixes
		var main = AssetDatabase.GetAssetPath(material.mainTexture);
		var bump = System.IO.Path.GetFileNameWithoutExtension(main) + "N" + System.IO.Path.GetExtension(main);
		bump = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(main), bump);
		var specular = System.IO.Path.GetFileNameWithoutExtension(main) + "S" + System.IO.Path.GetExtension(main);
		specular = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(main), specular);

		Debug.Log("doing "+material.name);

		if (AssetDatabase.LoadAssetAtPath(specular, Texture2D)) {
			// specular map exists
			if (AssetDatabase.LoadAssetAtPath(bump, Texture2D)) {
				// bump and specular exist
				material.shader = Shader.Find("Bumped Specular");
				material.SetTexture("_BumpMap", ReverseNormals(bump));
				material.mainTexture = AddSpecular(material.mainTexture, main, specular);
			} else {
				// only specular exists
				material.mainTexture = AddSpecular(material.mainTexture, main, specular);
			}
		} else if (AssetDatabase.LoadAssetAtPath(bump, Texture2D)) {
			// only bump map exists
			material.shader = Shader.Find("Bumped Diffuse");
			material.SetTexture("_BumpMap", ReverseNormals(bump));
			material.mainTexture.anisoLevel = AnisoLevel;
		} else {
			// only diffuse map exists
			material.mainTexture.anisoLevel = AnisoLevel;
		}

		AssetDatabase.CreateAsset(material, materialPath);
		return material;
	}

	function ReverseNormals(input : String) : Texture2D {
		// check if we already have one
		var destpath = System.IO.Path.Combine("Assets/_generated textures/", System.IO.Path.GetFileNameWithoutExtension(input))+".png";
		var test = AssetDatabase.LoadAssetAtPath(destpath, Texture2D);
		if (test) {
			Debug.Log("skipping already existing "+destpath);
			return test;
		}

		// there are two kinds of normal maps, for my purposes, I need to flip the green channel
		var textureImporter = AssetImporter.GetAtPath(input) as TextureImporter;
		if (!textureImporter.isReadable) {
			textureImporter.isReadable = true;
			AssetDatabase.ImportAsset(input, ImportAssetOptions.ForceUpdate);
		}

		var source = AssetDatabase.LoadAssetAtPath(input, Texture2D) as Texture2D;
		var w = source.width; var h = source.height;
		var dest = new Texture2D(w, h);
		dest.anisoLevel = AnisoLevel;

		var Pixels = source.GetPixels();
		for (var x=0;x<w;x++) for (var y=0;y<h;y++) {
			var i = x*w+y;
			Pixels[i].g = 1 - Pixels[i].g;
		}
		dest.SetPixels(Pixels);
		dest.Apply();
		
		// write texture to a .png
		AssetDatabase.CreateAsset(dest, destpath);
		var myBytes : byte[] = dest.EncodeToPNG();
		System.IO.File.WriteAllBytes(destpath, myBytes);

		// reimport the asset so it is properly assigned
		AssetDatabase.ImportAsset(destpath, ImportAssetOptions.ForceUpdate);
		dest = AssetDatabase.LoadAssetAtPath(destpath, Texture2D) as Texture2D;
		return dest;
	}

	function AddSpecular(maintex, mainpath, specular) : Texture2D {
		// check if we already have one
		var destpath = System.IO.Path.Combine("Assets/_generated textures/", System.IO.Path.GetFileNameWithoutExtension(mainpath))+"DS.png";
		var test = AssetDatabase.LoadAssetAtPath(destpath, Texture2D);
		if (test) {
			Debug.Log("skipping already existing "+destpath);
			return test;
		}
		var textureImporter = AssetImporter.GetAtPath(mainpath) as TextureImporter;
		if (!textureImporter.isReadable) {
			textureImporter.isReadable = true;
			AssetDatabase.ImportAsset(mainpath, ImportAssetOptions.ForceUpdate);
		}
		textureImporter = AssetImporter.GetAtPath(specular) as TextureImporter;
		if (!textureImporter.isReadable) {
			textureImporter.isReadable = true;
			AssetDatabase.ImportAsset(specular, ImportAssetOptions.ForceUpdate);
		}

		var w = maintex.width; var h = maintex.height;
		var dest = new Texture2D(w, h, TextureFormat.ARGB32, true);
		dest.anisoLevel = AnisoLevel;

		var MainPixels = maintex.GetPixels();
		var speculartex = AssetDatabase.LoadAssetAtPath(specular, Texture2D) as Texture2D;
		var SpecularPixels = speculartex.GetPixels();
		for (var x=0;x<w;x++) for (var y=0;y<h;y++) {
			var i = x*w+y;
			var col = new Color(MainPixels[i].r, MainPixels[i].g, MainPixels[i].b, SpecularPixels[i].grayscale);
			MainPixels[i] = col;
		}
		dest.SetPixels(MainPixels);
		dest.Apply();

		// write texture to a .png
		AssetDatabase.CreateAsset(dest, destpath);
		var myBytes : byte[] = dest.EncodeToPNG();
		System.IO.File.WriteAllBytes(destpath, myBytes);

		// reimport the asset so it is properly assigned
		textureImporter = AssetImporter.GetAtPath(destpath) as TextureImporter;
		textureImporter.textureFormat = TextureImporterFormat.DXT5;
		AssetDatabase.ImportAsset(destpath, ImportAssetOptions.ForceUpdate);
		
		dest = AssetDatabase.LoadAssetAtPath(destpath, Texture2D) as Texture2D;
		return dest;
	}

}

I’m getting closer, but no cigar.

There appears to be either a timing issue or something missing. I have 100% reproduceable failures in assigning the textures that work on the 2nd call. So either the Asset Database needs time to catch up, or something happens on refresh.

I have tried all the AssetDatabase.Refresh(), .Save(), .Load*() calls to no avail.

Anyone?

Please can you report this as a bug (menu: Help > Report A Problem). There’s no harm done if it turns out to have a logical explanation.

ok, done that.

I still can’t figure it out. It works (modified the script some more), but only if I re-import things 2-3 times.