Editor/PbrMaterialExporter.cs
using System;
using System.IO;
using System.Text;
using Editor;

public static class PbrMaterialExporter
{
	public static PbrMaterialExportResult Export( string vmatPath, PbrExportedMapPaths mapPaths, bool overwriteExisting )
	{
		var result = new PbrMaterialExportResult();

		try
		{
			if ( mapPaths == null || string.IsNullOrWhiteSpace( mapPaths.AlbedoPath ) )
			{
				result.Error = "Albedo map was not exported, so the material could not be created.";
				return result;
			}

			if ( File.Exists( vmatPath ) && !overwriteExisting )
			{
				result.Error = "Material file already exists.";
				return result;
			}

			var outputFolder = Path.GetDirectoryName( vmatPath );

			if ( !string.IsNullOrWhiteSpace( outputFolder ) )
				Directory.CreateDirectory( outputFolder );

			var content = GenerateVmatContent( mapPaths, warning => AddWarning( result, warning ) );
			File.WriteAllText( vmatPath, content );

			try
			{
				var asset = AssetSystem.RegisterFile( vmatPath );

				if ( asset == null )
					AddWarning( result, "The .vmat was written, but S&box did not return a registered material asset." );
			}
			catch ( Exception ex )
			{
				AddWarning( result, $"The .vmat was written, but S&box asset registration skipped it: {ex.Message}" );
			}

			result.Success = true;
			result.VmatPath = vmatPath;
		}
		catch ( Exception ex )
		{
			result.Error = ex.Message;
		}

		return result;
	}

	private static string GenerateVmatContent( PbrExportedMapPaths mapPaths, Action<string> addWarning )
	{
		var albedo = GetReferencePathOrDefault( mapPaths.AlbedoPath, "materials/default/default_color.tga", addWarning );
		var normal = GetReferencePathOrDefault( mapPaths.NormalPath, "materials/default/default_normal.tga", addWarning );
		var roughness = GetReferencePathOrDefault( mapPaths.RoughnessPath, "materials/default/default_rough.tga", addWarning );
		var ao = GetReferencePathOrDefault( mapPaths.AoPath, "materials/default/default_ao.tga", addWarning );
		var metallic = GetReferencePathOrDefault( mapPaths.MetallicPath, null, addWarning );
		var sb = new StringBuilder();

		sb.AppendLine( "// THIS FILE IS AUTO-GENERATED" );
		sb.AppendLine( "// Generated by Seam-Less PBR Map Generator" );
		sb.AppendLine();
		sb.AppendLine( "Layer0" );
		sb.AppendLine( "{" );
		sb.AppendLine( "\tshader \"shaders/complex.shader\"" );
		sb.AppendLine();
		sb.AppendLine( "\t//---- PBR ----" );
		sb.AppendLine( "\tF_SPECULAR 1" );
		sb.AppendLine();
		sb.AppendLine( "\t//---- Ambient Occlusion ----" );
		sb.AppendLine( "\tg_flAmbientOcclusionDirectDiffuse \"0.000\"" );
		sb.AppendLine( "\tg_flAmbientOcclusionDirectSpecular \"0.000\"" );
		sb.AppendLine( $"\tTextureAmbientOcclusion \"{ao}\"" );
		sb.AppendLine();
		sb.AppendLine( "\t//---- Color ----" );
		sb.AppendLine( "\tg_flModelTintAmount \"1.000\"" );
		sb.AppendLine( "\tg_vColorTint \"[1.000000 1.000000 1.000000 0.000000]\"" );
		sb.AppendLine( $"\tTextureColor \"{albedo}\"" );
		sb.AppendLine();
		sb.AppendLine( "\t//---- Fade ----" );
		sb.AppendLine( "\tg_flFadeExponent \"1.000\"" );
		sb.AppendLine();
		sb.AppendLine( "\t//---- Fog ----" );
		sb.AppendLine( "\tg_bFogEnabled \"1\"" );
		sb.AppendLine();
		sb.AppendLine( "\t//---- Metalness ----" );

		if ( !string.IsNullOrWhiteSpace( metallic ) )
			sb.AppendLine( $"\tTextureMetalness \"{metallic}\"" );
		else
			sb.AppendLine( "\tg_flMetalness \"0.000\"" );

		sb.AppendLine();
		sb.AppendLine( "\t//---- Normal ----" );
		sb.AppendLine( $"\tTextureNormal \"{normal}\"" );
		sb.AppendLine();
		sb.AppendLine( "\t//---- Roughness ----" );
		sb.AppendLine( "\tg_flRoughnessScaleFactor \"1.000\"" );
		sb.AppendLine( $"\tTextureRoughness \"{roughness}\"" );
		sb.AppendLine();
		sb.AppendLine( "\t//---- Texture Coordinates ----" );
		sb.AppendLine( "\tg_vTexCoordOffset \"[0.000 0.000]\"" );
		sb.AppendLine( "\tg_vTexCoordScale \"[1.000 1.000]\"" );
		sb.AppendLine( "\tg_vTexCoordScrollSpeed \"[0.000 0.000]\"" );
		sb.AppendLine( "}" );

		return sb.ToString();
	}

	private static string GetReferencePathOrDefault( string texturePath, string fallback, Action<string> addWarning )
	{
		if ( string.IsNullOrWhiteSpace( texturePath ) || !File.Exists( texturePath ) )
			return fallback;

		return SeamlessSuiteImageUtility.GetTextureReferencePath( texturePath, addWarning ).Replace( "\"", "" );
	}

	private static void AddWarning( PbrMaterialExportResult result, string warning )
	{
		if ( string.IsNullOrWhiteSpace( warning ) )
			return;

		if ( string.IsNullOrWhiteSpace( result.Warning ) )
		{
			result.Warning = warning;
			return;
		}

		result.Warning += $" {warning}";
	}
}

public sealed class PbrExportedMapPaths
{
	public string AlbedoPath { get; set; }
	public string HeightPath { get; set; }
	public string NormalPath { get; set; }
	public string RoughnessPath { get; set; }
	public string AoPath { get; set; }
	public string MetallicPath { get; set; }
	public string OrmPath { get; set; }
}

public sealed class PbrMaterialExportResult
{
	public bool Success { get; set; }
	public string VmatPath { get; set; }
	public string Warning { get; set; }
	public string Error { get; set; }
}