Editor/WeaponImporter/Tool/Engine/GripLibraryStore.cs
using WeaponImporter.Core.Analysis;
using WeaponImporter.Core.Grip;
using WeaponImporter.Core.Import;
namespace WeaponImporter.Tool;
/// <summary>
/// Grip presets available to the importer: the built-in library (captured from open animation
/// packs), the project's own library (<c>Assets/weapons/grip_library.json</c>, grips extracted
/// from files the user added), and the grips of the weapon file's own first-person arms.
/// </summary>
public static class GripLibraryStore
{
public const string ProjectFile = "weapons/grip_library.json";
private static List<GripPreset> _project;
public static IReadOnlyList<GripPreset> BuiltIn { get; } = LoadBuiltIn();
/// <summary>Presets saved in this project (read once, then kept up to date by <see cref="AddAsync"/>).</summary>
public static IReadOnlyList<GripPreset> Project => _project ??= LoadProject();
public static List<GripPreset> All( IEnumerable<GripPreset> own ) => own.Concat( Project ).Concat( BuiltIn )
.GroupBy( p => p.Name ).Select( g => g.First() ).ToList();
private static List<GripPreset> LoadBuiltIn()
{
try
{
return GripLibrary.FromJson( BuiltInGrips.Json );
}
catch ( Exception e )
{
Log.Warning( $"[weapon importer] built-in grips could not be read: {e.Message}" );
return new List<GripPreset>();
}
}
private static List<GripPreset> LoadProject()
{
try
{
var abs = AssetCompiler.Absolute( ProjectFile );
return System.IO.File.Exists( abs ) ? GripLibrary.FromJson( System.IO.File.ReadAllText( abs ) ) : new List<GripPreset>();
}
catch ( Exception e )
{
Log.Warning( $"[weapon importer] {ProjectFile} could not be read: {e.Message}" );
return new List<GripPreset>();
}
}
/// <summary>
/// Extracts the grips of every file with arms holding a weapon (FBX, GLB, glTF) and adds them
/// to the project library. Heavy: runs off the main thread, cancellable. Returns how many
/// grips were added and the files that had none.
/// </summary>
public static async Task<(int Added, List<string> Skipped)> AddAsync( IEnumerable<string> files, IProgress<string> progress, CancellationToken cancel )
{
var list = files.ToList();
var found = new List<GripPreset>();
var skipped = new List<string>();
for ( var i = 0; i < list.Count; i++ )
{
cancel.ThrowIfCancellationRequested();
var file = list[i];
progress?.Report( $"Reading grips {i + 1}/{list.Count}: {System.IO.Path.GetFileName( file )}" );
try
{
var presets = await Task.Run( () =>
{
var analysis = WeaponAnalyzer.Analyze( WeaponLoader.Load( file ), new AnalyzeOptions(), cancel );
return GripExtractor.Extract( analysis, System.IO.Path.GetFileNameWithoutExtension( file ) );
}, cancel );
if ( presets.Count == 0 )
skipped.Add( file );
found.AddRange( presets );
}
catch ( OperationCanceledException )
{
throw;
}
catch ( Exception e )
{
Log.Warning( $"[weapon importer] no grips from {file}: {e.Message}" );
skipped.Add( file );
}
}
await EditorThread.SwitchToMainThread();
var project = Project.ToList();
var added = 0;
foreach ( var p in found )
{
project.RemoveAll( x => x.Name == p.Name );
project.Add( p );
added++;
}
_project = project;
AssetCompiler.WriteText( ProjectFile, GripLibrary.ToJson( project ) );
return (added, skipped);
}
/// <summary>Removes a grip from the project library.</summary>
public static void Remove( string name )
{
var project = Project.ToList();
if ( project.RemoveAll( p => p.Name == name ) == 0 )
return;
_project = project;
AssetCompiler.WriteText( ProjectFile, GripLibrary.ToJson( project ) );
}
}