AutoRig/Export/VmdlGenerator.cs

Utility that generates a minimal KV3-format modeldoc vmdl text referencing an exported mesh file. It builds the file content with a fixed KV3 header, a RenderMeshFile entry pointing at the given FBX path, and an optional material remap group when both remap-from and remap-to are provided.

File Access
using System.Text;

namespace AutoRig.Export;

/// <summary>
/// Generates a minimal ModelDoc vmdl (KV3 text) referencing an exported mesh file.
/// Header and layout follow the shipped modeldoc conventions (same header line the
/// humanoid-retargeter's VmdlWriter proved compiles).
/// </summary>
public static class VmdlGenerator
{
    /// <summary>The KV3 header line used by shipped modeldoc vmdl files.</summary>
    public const string Kv3Header =
        "<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:modeldoc30:version{8c2d7a91-9c42-4bf0-883a-5a3b1762d4f1} -->";

    /// <param name="materialRemapFrom">Material name (with .vmat) as imported from the
    /// mesh file, e.g. "duck_mat.vmat"; null = no material group.</param>
    /// <param name="materialRemapTo">Project-relative vmat to bind it to.</param>
    public static string Generate( string fbxRelativePath, string modelName,
        string materialRemapFrom = null, string materialRemapTo = null )
    {
        ArgumentNullException.ThrowIfNull( fbxRelativePath );
        ArgumentNullException.ThrowIfNull( modelName );
        var path = fbxRelativePath.Replace( '\\', '/' );

        var sb = new StringBuilder();
        sb.Append( Kv3Header ).Append( '\n' );
        sb.Append( "{\n" );
        sb.Append( "\trootNode = \n" );
        sb.Append( "\t{\n" );
        sb.Append( "\t\t_class = \"RootNode\"\n" );
        sb.Append( "\t\tchildren = \n" );
        sb.Append( "\t\t[\n" );
        sb.Append( "\t\t\t{\n" );
        sb.Append( "\t\t\t\t_class = \"RenderMeshList\"\n" );
        sb.Append( "\t\t\t\tchildren = \n" );
        sb.Append( "\t\t\t\t[\n" );
        sb.Append( "\t\t\t\t\t{\n" );
        sb.Append( "\t\t\t\t\t\t_class = \"RenderMeshFile\"\n" );
        sb.Append( $"\t\t\t\t\t\tfilename = \"{path}\"\n" );
        sb.Append( "\t\t\t\t\t},\n" );
        sb.Append( "\t\t\t\t]\n" );
        sb.Append( "\t\t\t},\n" );
        if ( materialRemapFrom is not null && materialRemapTo is not null )
        {
            sb.Append( "\t\t\t{\n" );
            sb.Append( "\t\t\t\t_class = \"MaterialGroupList\"\n" );
            sb.Append( "\t\t\t\tchildren = \n" );
            sb.Append( "\t\t\t\t[\n" );
            sb.Append( "\t\t\t\t\t{\n" );
            sb.Append( "\t\t\t\t\t\t_class = \"DefaultMaterialGroup\"\n" );
            sb.Append( "\t\t\t\t\t\tremaps = \n" );
            sb.Append( "\t\t\t\t\t\t[\n" );
            sb.Append( "\t\t\t\t\t\t\t{\n" );
            sb.Append( $"\t\t\t\t\t\t\t\tfrom = \"{materialRemapFrom.Replace( '\\', '/' )}\"\n" );
            sb.Append( $"\t\t\t\t\t\t\t\tto = \"{materialRemapTo.Replace( '\\', '/' )}\"\n" );
            sb.Append( "\t\t\t\t\t\t\t},\n" );
            sb.Append( "\t\t\t\t\t\t]\n" );
            sb.Append( "\t\t\t\t\t\tuse_global_default = false\n" );
            sb.Append( "\t\t\t\t\t\tglobal_default_material = \"\"\n" );
            sb.Append( "\t\t\t\t\t},\n" );
            sb.Append( "\t\t\t\t]\n" );
            sb.Append( "\t\t\t},\n" );
        }
        sb.Append( "\t\t]\n" );
        sb.Append( "\t\tmodel_archetype = \"\"\n" );
        sb.Append( "\t\tprimary_associated_entity = \"\"\n" );
        sb.Append( "\t\tanim_graph_name = \"\"\n" );
        sb.Append( "\t}\n" );
        sb.Append( "}\n" );
        return sb.ToString();
    }
}