Editor/SeamlessMaterialExporter.cs
using System;
using System.IO;
using System.Text;
using Editor;
using Sandbox;
public static class SeamlessMaterialExporter
{
public static SeamlessMaterialExportResult Export( string texturePath, bool overwriteExisting )
{
var result = new SeamlessMaterialExportResult();
try
{
if ( string.IsNullOrWhiteSpace( texturePath ) || !File.Exists( texturePath ) )
{
result.Error = "Texture file was not found.";
return result;
}
var vmatPath = BuildVmatPath( texturePath, overwriteExisting );
var outputFolder = Path.GetDirectoryName( vmatPath );
if ( !string.IsNullOrWhiteSpace( outputFolder ) )
Directory.CreateDirectory( outputFolder );
var textureReferencePath = GetTextureReferencePath( texturePath, result );
var vmatContent = GenerateVmatContent( textureReferencePath );
File.WriteAllText( vmatPath, vmatContent );
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;
result.TextureReferencePath = textureReferencePath;
}
catch ( Exception ex )
{
result.Error = ex.Message;
}
return result;
}
private static string BuildVmatPath( string texturePath, bool overwriteExisting )
{
var basePath = Path.ChangeExtension( texturePath, ".vmat" );
if ( overwriteExisting || !File.Exists( basePath ) )
return basePath;
var folder = Path.GetDirectoryName( basePath ) ?? "";
var fileName = Path.GetFileNameWithoutExtension( basePath );
for ( var i = 1; i < 1000; i++ )
{
var numberedPath = Path.Combine( folder, $"{fileName}_{i:000}.vmat" );
if ( !File.Exists( numberedPath ) )
return numberedPath;
}
return basePath;
}
private static string GetTextureReferencePath( string texturePath, SeamlessMaterialExportResult result )
{
var fullTexturePath = Path.GetFullPath( texturePath );
var normalizedTexturePath = NormalizePath( fullTexturePath );
var projectAssetsPath = Sandbox.Project.Current?.GetAssetsPath();
if ( !string.IsNullOrWhiteSpace( projectAssetsPath ) )
{
var normalizedAssetsPath = NormalizePath( Path.GetFullPath( projectAssetsPath ) );
if ( !normalizedAssetsPath.EndsWith( "/" ) )
normalizedAssetsPath += "/";
if ( normalizedTexturePath.StartsWith( normalizedAssetsPath, StringComparison.OrdinalIgnoreCase ) )
return normalizedTexturePath.Substring( normalizedAssetsPath.Length );
AddWarning( result, "The exported texture and .vmat are outside the active project's Assets folder. S&box may not treat them as normal project assets; choose a folder under Assets for best editor use." );
}
else
{
AddWarning( result, "Could not find the active project's Assets folder. The .vmat will reference the texture by path, but S&box may not treat it as a normal project asset." );
}
return normalizedTexturePath;
}
private static string GenerateVmatContent( string textureReferencePath )
{
var texturePath = NormalizePath( textureReferencePath ).Replace( "\"", "" );
var sb = new StringBuilder();
sb.AppendLine( "// THIS FILE IS AUTO-GENERATED" );
sb.AppendLine( "// Generated by Seam-Less Texture Suite" );
sb.AppendLine();
sb.AppendLine( "Layer0" );
sb.AppendLine( "{" );
sb.AppendLine( "\tshader \"complex.shader\"" );
sb.AppendLine();
sb.AppendLine( "\t//---- Ambient Occlusion ----" );
sb.AppendLine( "\tg_flAmbientOcclusionDirectDiffuse \"0.000\"" );
sb.AppendLine( "\tTextureAmbientOcclusion \"materials/default/default_ao.tga\"" );
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 \"{texturePath}\"" );
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 ----" );
sb.AppendLine( "\tg_flMetalness \"0.000\"" );
sb.AppendLine();
sb.AppendLine( "\t//---- Normal ----" );
sb.AppendLine( "\tTextureNormal \"materials/default/default_normal.tga\"" );
sb.AppendLine();
sb.AppendLine( "\t//---- Roughness ----" );
sb.AppendLine( "\tg_flRoughnessScaleFactor \"1.000\"" );
sb.AppendLine( "\tTextureRoughness \"materials/default/default_rough.tga\"" );
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 void AddWarning( SeamlessMaterialExportResult result, string warning )
{
if ( string.IsNullOrWhiteSpace( warning ) )
return;
if ( string.IsNullOrWhiteSpace( result.Warning ) )
{
result.Warning = warning;
return;
}
result.Warning += $" {warning}";
}
private static string NormalizePath( string path )
{
return path?.Replace( '\\', '/' ) ?? "";
}
}
public class SeamlessMaterialExportResult
{
public bool Success { get; set; }
public string VmatPath { get; set; }
public string TextureReferencePath { get; set; }
public string Warning { get; set; }
public string Error { get; set; }
}