[Editor scripting] How to save a script generated mesh as an asset/FBX?

Hello,

I've got an editor script that will generate some meshes, and I would like to be able to save these meshes as assets (FBX files)

This:

AssetDatabase.CreateAsset( [mesh object here], [path to asset] );

doesn't work.

Trying to add a ModelImporter object doesn't either, and besides, if I use an FBX extension to save the asset, it will be automatically processed as a model asset, but still won't work.

Thanks.

5 Answers

5

I wrote a small editor script that adds a context menu item to the MeshFilter component. It will allow you to save the mesh in the MeshFilter as a .asset file, which you can drag into any GameObject with a MeshRenderer and MeshFilter and it will work.

You can also check the code to see how it was done.

much appreciated

–

Thank you for this!

–

Wow, this was perfect! We learn something new everyday! Thanks for putting this together!

–

Thank you.

–

great addition.

–

Use SaveAssets to commit the changes. Also - make sure that path to asset is relative to the project (not relative to Assets), and the extension is .asset

AssetDatabase.CreateAsset( [mesh object here], [path to asset] );
AssetDatabase.SaveAssets();

Or, as the comments say - save your meshes as obj files so that they get imported as model assets :D

Yeah I did that actually, but I still end up with some random data the FBX importer doesn't seem to recognize.

–

Ah, right, that's not going to work no, you're just saving the unity serialized data that way. What you could try is the obj exporter using the Mesh you already have: http://www.unifycommunity.com/wiki/index.php?title=ObjExporter

–

Thanks Mike, but, again, that's not my ultimate goal :) The only reason I want my generated mesh to be a FBX is to invoke the 'Generate Lightmap UVs' feature on it, so that I can lightmap a scene populated with the models I have generated. Devious, I know. But I tried to copy UV2s from the original models I construct my new models from, alas to no avail.

–

Correction: I got the meshes to be saved as such (they are pure 'mesh' objects), but of course, not being considered as models from Unity, I can't do to them things I can on models, such as generating lightmap uvs.

–

Try this one instead: http://www.unifycommunity.com/wiki/index.php?title=ObjExporterColorUvs then convert to fbx with an external program?

–

There is a method not involving generation of intermediate files: have a prefab template that looks almost ready and only misses a mesh. You can then either save your mesh into this prefab and assign it to `MeshFilter`'s `sharedMesh` or (useful if you want to generate multiple meshes into identical prefab) go something like:

// All generated models will be structured after this template
string modelTemplate = "Assets/Templates/Model.prefab";

void Generate(string prefabPath) 
{
    // clone the model template
    Object templatePrefab = AssetDatabase.LoadAssetAtPath(modelTemplate, typeof(GameObject));
    GameObject template = (GameObject)EditorUtility.InstantiatePrefab(templatePrefab);

    // this way links will persist when we regenerate the mesh
    Object prefab = AssetDatabase.LoadAssetAtPath(prefabPath, typeof(GameObject));
    if (!prefab) {
        prefab = EditorUtility.CreateEmptyPrefab( prefabPath );
    }

    // sort of the same...
    Mesh mesh = (Mesh)AssetDatabase.LoadAssetAtPath(prefabPath, typeof(Mesh));
    if (!mesh) {
        mesh = new Mesh();
        mesh.name = name;
        AssetDatabase.AddObjectToAsset (mesh, prefabPath);
    } else {
        mesh.Clear();
    }
    // generate your mesh in place
    BlaBlaBla(mesh);
    // assume that MeshFilter is already there. could check and AddComponent
    template.GetComponent<MeshFilter>().sharedMesh = mesh;

    // make sure 
    EditorUtility.ReplacePrefab(template, prefab, ReplacePrefabOptions.ReplaceNameBased);
    // get rid of the temporary object (otherwise it stays over in scene)
    Object.DestroyImmediate(template);
}

This is not generalized enough yet (contains a call to BlaBlaBla :-) - mesh generation function), you really should pass some sort of generator object to this function or something like that. It's because I adapted this snippet from my working code that does something slightly different. Now that I realize that it's generalizable I'll probably refactor it next time I have some time to kill.

This code makes sure that if you've instantiated your models already and decide to regenerate the mesh prefab link will stay intact. On the other hand, model is not linked to the template, if you change the template you will have to regenerate all models you have (and their instances will get updated then).

Thanks atm. However I don't see how this will save the mesh as a Model. It will create a prefab asset where you'll put you mesh in, but that won't allow you to invoke on the mesh things you can do to Models, such as recalculate normals or tangents or generate secondary UVs for lightmapping (my ultimate goal). Of course, please correct me if I'm wrong! ^^

–

Ah, do you mean you want to somehow pass it to ModelImporter? Sorry then, I misunderstood: I gues I see my own problems in other people's questions :-) But: in my script, after the meshes are saved in the prefab I see unity's own ProgressBar dialog appearing saying "importing blablabla.prefab". I wonder if model importers are called with it... Let me test.

–

nope they don't :-)

–

Holy shit xD I love you femi, it took me a long while of messing around to get your solution to work for my case, but in the end I managed to do it! Thank you so much for making a key part of my game possible!! :)

–

I had some old .asset files of Mesh data that I wanted to make accessible for external editing as FBX files. I was able to use the SDK provided in FBX Exporter | Essentials | Unity Asset Store to write my own context menu option for Mesh .asset files. The SDK enabled a large amount of control, such as saving as ascii or binary format , changing the scale and axis orientation, etc.


You can use the ExtractToFBX function in the code below to save any dynamically generated mesh (you must provide a filepath) or serialized Mesh asset file to an FBX:

using System.IO;
using UnityEngine;
using UnityEditor;
using Unity.FbxSdk;
using FbxExporters.Editor;

// Place in 'Editor' folder
public static class ExtractMeshToFBX
{
    // true: fbx file is easy-to-debug ascii, false: fbx file is binary.
    static bool saveFbxAsAscii = false;

    // The preferred axis system for the exported fbx file
    static FbxAxisSystem fbxAxisSystem = FbxAxisSystem.Max;
    // The preferred units of the exported fbx file
    static FbxSystemUnit fbxUnit = FbxSystemUnit.m;

    static string fbxFileTitle       = "TITLE HERE";
    static string fbxFileSubject     = "SUBJECT HERE";
    static string fbxFileComment     = "COMMENT HERE";
    static string fbxFileKeywords    = "KEYWORDS HERE";
    static string fbxFileAuthor      = "AUTHOR HERE";
    static string fbxFileRevision    = "1.0";
    static string fbxFileApplication = "Unity FBX SDK";


    [MenuItem("Assets/Extract to FBX", validate = true)]
    public static bool MenuExtractToFBXValidate()
    {
        if (Selection.activeObject == null)
            return false;
        return Selection.activeObject.GetType() == typeof(Mesh);
    }

    [MenuItem("Assets/Extract to FBX")]
    public static void MenuExtractToFBX()
    {
        // We assume validation worked and this is always defined.
        Mesh mesh = Selection.activeObject as Mesh;

        // Set up paths
        string meshFilePath = AssetDatabase.GetAssetPath(mesh);
        string meshDirectory = Path.GetDirectoryName(meshFilePath);

        string filename = Path.GetFileNameWithoutExtension(meshFilePath) + ".fbx";
        string filePath = Path.Combine(meshDirectory, filename);

        ExtractToFBX(mesh, filePath);
    }

    public static void ExtractToFBX(Mesh mesh, string filePath)
    {
        // Make a temporary copy of the mesh to modify it
        Mesh tempMesh = Object.Instantiate(mesh);
        tempMesh.name = mesh.name;

        // If meters, divide by 100 since default is cm. Assume centered at origin.
        if (fbxUnit == FbxSystemUnit.m)
        {
            Vector3[] vertices = tempMesh.vertices;
            for (int i = 0; i < vertices.Length; ++i)
                vertices */= 100.0f;*

tempMesh.vertices = vertices;
}
// You could handle other SystemUnits here

// FBX Manager
FbxManager manager = FbxManager.Create();
manager.SetIOSettings(FbxIOSettings.Create(manager, Globals.IOSROOT));

// FBX Exporter
FbxExporter fbxExporter = FbxExporter.Create(manager, “Exporter”);

// Binary
int fileFormat = -1;
// Ascii
if (saveFbxAsAscii)
fileFormat = manager.GetIOPluginRegistry().FindWriterIDByDescription(“FBX ascii (*.fbx)”);

fbxExporter.Initialize(filePath, fileFormat, manager.GetIOSettings());
fbxExporter.SetFileExportVersion(“FBX201400”);

// FBX Scene
FbxScene fbxScene = FbxScene.Create(manager, “Scene”);
FbxDocumentInfo sceneInfo = FbxDocumentInfo.Create(manager, “SceneInfo”);

// Set up scene info
sceneInfo.mTitle = fbxFileTitle;
sceneInfo.mSubject = fbxFileSubject;
sceneInfo.mComment = fbxFileComment;
sceneInfo.mAuthor = fbxFileAuthor;
sceneInfo.mRevision = fbxFileRevision;
sceneInfo.mKeywords = fbxFileKeywords;
sceneInfo.Original_ApplicationName.Set(fbxFileApplication);
sceneInfo.LastSaved_ApplicationName.Set(fbxFileApplication);
fbxScene.SetSceneInfo(sceneInfo);

// Set up Global settings
FbxGlobalSettings globalSettings = fbxScene.GetGlobalSettings();
globalSettings.SetSystemUnit(fbxUnit);
globalSettings.SetAxisSystem(fbxAxisSystem);

FbxNode modelNode = FbxNode.Create(fbxScene, tempMesh.name);
// Add mesh to a node in the scene
using (ModelExporter modelExporter = new ModelExporter())
{
if (!modelExporter.ExportMesh(tempMesh, modelNode))
Debug.LogError(“Problem Exporting Mesh”);
}
// add the model to the scene
fbxScene.GetRootNode().AddChild(modelNode);

// Finally actually save the scene
bool sceneSuccess = fbxExporter.Export(fbxScene);
AssetDatabase.Refresh();

// clean up temporary model
if (Application.isPlaying)
Object.Destroy(tempMesh);
else
Object.DestroyImmediate(tempMesh);
}
}
The code worked even on 2017.1 despite the above package requiring 2017.3, after first downloading the package in a later version and then manually moving the DLLs. The dependency on 2017.3 is only due to their custom UI, which this code does not use.
----------
If you want to include materials in the fbx you can easily modify the function to pass in materials which can be passed in as an argument to modelExporter.ExportMesh.

Thanks for sharing this info and your code!

–

Exactly the same situation, thanks a lot!

–

This is what I needed but it took me a while to figure out what works (“Assets/” has to be included…):

var meshFilter = Selection.gameObjects[0].GetComponent<MeshFilter>();
AssetDatabase.CreateAsset(meshFilter.sharedMesh, "Assets/savedMesh.asset");
AssetDatabase.SaveAssets();

Remove SaveAssets. CreateAsset creates a new asset file. It doesn't need to be "saved". It already exists!

–