Editor-side conversion pipeline orchestrator. Accepts a file or folder and routes .mdl, .vtf, .vmt conversions and batch folder processing; delegates actual work to MdlPipeline and MaterialPipeline and aggregates results and logs.
using System;
using System.IO;
namespace GmodAssetImporter;
/// <summary>
/// Orchestrates single-file and batch GMod → s&box conversions.
/// Textures are .vtf (port to PNG). .vmt is optional material recipe (used by model import).
/// </summary>
public static class ConversionPipeline
{
public static ConversionResult Convert( string inputPath, ConversionOptions options )
{
if ( string.IsNullOrWhiteSpace( inputPath ) )
return Fail( "No input path" );
if ( string.IsNullOrWhiteSpace( options.OutputRoot ) )
return Fail( "No output root" );
if ( !options.ImportModels && !options.ImportMaterials )
return Fail( "Import models and Import materials are both off - nothing to convert" );
Directory.CreateDirectory( options.OutputRoot );
if ( Directory.Exists( inputPath ) )
return ConvertFolder( inputPath, options );
var ext = Path.GetExtension( inputPath ).ToLowerInvariant();
return ext switch
{
".mdl" => ConvertMdl( inputPath, options ),
".vtf" => MaterialPipeline.ConvertVtfFile( inputPath, options ),
".vmt" => MaterialPipeline.ConvertVmtFile( inputPath, options ),
_ => Fail( $"Unsupported input type: {ext} (use .mdl, .vtf, .vmt, or a folder)" )
};
}
public static ConversionResult ConvertFolder( string folder, ConversionOptions options )
{
var result = new ConversionResult { Success = true };
var writeLog = options.OnLog ?? (_ => { });
var search = options.Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
if ( string.IsNullOrWhiteSpace( options.GmodRoot ) )
options.GmodRoot = PathResolve.DetectGmodRoot( folder );
var counts = PathResolve.CountSourceAssets( folder, options.Recursive );
writeLog( $"Detected {counts.Mdl} MDL(s), {counts.Vmt + counts.Vtf} material file(s) ({counts.Vmt} .vmt, {counts.Vtf} .vtf)" );
var mdls = Directory.GetFiles( folder, "*.mdl", search );
var hasMaterialFiles = counts.Vtf > 0 || counts.Vmt > 0;
if ( mdls.Length == 0 && !hasMaterialFiles )
return Fail( "No .mdl, .vmt or .vtf files found in folder" );
if ( !options.ImportMaterials && hasMaterialFiles )
{
writeLog( $"Skipping {counts.Vmt} .vmt / {counts.Vtf} .vtf (Import materials off)" );
if ( mdls.Length == 0 )
return Fail( "Import materials is off and the folder has no .mdl files - nothing to convert" );
hasMaterialFiles = false;
}
if ( mdls.Length > 0 )
{
writeLog( options.ImportModels
? $"Batch: {mdls.Length} model(s)"
: $"Batch: materials for {mdls.Length} model(s) (Import models off)" );
foreach ( var mdl in mdls )
{
var one = ConvertMdl( mdl, options );
Merge( result, one );
}
}
if ( hasMaterialFiles )
{
writeLog( $"Batch: {counts.Vmt} .vmt / {counts.Vtf} .vtf -> VMAT + PNG" );
var one = MaterialPipeline.ConvertMaterialsFolder( folder, options );
Merge( result, one );
}
result.Success = result.WrittenFiles.Count > 0;
result.Message = $"Batch complete - {result.WrittenFiles.Count} file(s), {result.Warnings.Count} warning(s)";
return result;
}
static ConversionResult ConvertMdl( string mdlPath, ConversionOptions options ) => options.ImportModels
? MdlPipeline.ConvertModel( mdlPath, options )
: MdlPipeline.ConvertModelMaterials( mdlPath, options );
static void Merge( ConversionResult into, ConversionResult one )
{
into.WrittenFiles.AddRange( one.WrittenFiles );
into.Warnings.AddRange( one.Warnings );
if ( !one.Success )
{
into.Warnings.Add( one.Message );
into.Success = into.WrittenFiles.Count > 0 || into.Success;
}
}
static ConversionResult Fail( string message ) => new()
{
Success = false,
Message = message
};
}