Editor/Conversion/Pipeline/ConversionPipeline.cs

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.

File Access
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
	};
}