poe2-bot/src/Automata.Ui/ViewModels/AtlasViewModel.cs
2026-02-28 15:13:22 -05:00

150 lines
4.2 KiB
C#

using System.IO;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Poe2Trade.Bot;
using Poe2Trade.Navigation;
using Serilog;
namespace Poe2Trade.Ui.ViewModels;
public partial class AtlasViewModel : ObservableObject, IDisposable
{
private readonly BotOrchestrator _bot;
private readonly CancellationTokenSource _pollCts = new();
[ObservableProperty] private Bitmap? _canvasImage;
[ObservableProperty] private bool _isCapturing;
[ObservableProperty] private string _atlasStatus = "";
[ObservableProperty] private int _tilesCaptured;
public AtlasViewModel(BotOrchestrator bot)
{
_bot = bot;
_ = PollLoop(_pollCts.Token);
}
[RelayCommand]
private async Task CaptureAtlas()
{
if (IsCapturing) return;
IsCapturing = true;
AtlasStatus = "Starting...";
TilesCaptured = 0;
void OnProgress(AtlasProgress p)
{
Dispatcher.UIThread.Post(() =>
{
AtlasStatus = $"{p.Phase} ({p.TilesCaptured} tiles)";
TilesCaptured = p.TilesCaptured;
});
}
_bot.AtlasExecutor.ProgressUpdated += OnProgress;
try
{
var path = await Task.Run(() => _bot.AtlasExecutor.CaptureAtlasPanorama());
AtlasStatus = path != null ? $"Saved: {path}" : "Capture failed or cancelled";
}
catch (Exception ex)
{
AtlasStatus = $"Error: {ex.Message}";
}
finally
{
_bot.AtlasExecutor.ProgressUpdated -= OnProgress;
IsCapturing = false;
}
}
[RelayCommand]
private void StopAtlasCapture()
{
_bot.AtlasExecutor.Stop();
AtlasStatus = "Stopping...";
}
[RelayCommand]
private async Task CalibratePerspective()
{
if (IsCapturing) return;
IsCapturing = true;
AtlasStatus = "Auto-scrolling atlas...";
TilesCaptured = 0;
void OnProgress(AtlasProgress p)
{
Dispatcher.UIThread.Post(() =>
{
AtlasStatus = $"{p.Phase} ({p.TilesCaptured} frames)";
TilesCaptured = p.TilesCaptured;
});
}
_bot.AtlasExecutor.ProgressUpdated += OnProgress;
try
{
var result = await Task.Run(() => _bot.AtlasExecutor.CalibratePerspective());
if (result != null)
AtlasStatus = $"Best factor: {result.BestFactor:F3} (conf: {result.BestConfidence:F4})";
else
AtlasStatus = "Calibration cancelled (not enough frames)";
}
catch (Exception ex)
{
AtlasStatus = $"Calibration error: {ex.Message}";
Log.Error(ex, "Calibration failed");
}
finally
{
_bot.AtlasExecutor.ProgressUpdated -= OnProgress;
IsCapturing = false;
}
}
/// <summary>
/// Poll ActivePanorama for viewport snapshots at ~2fps while capturing.
/// </summary>
private async Task PollLoop(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try
{
var bytes = _bot.AtlasExecutor.ActivePanorama?.GetViewportSnapshot();
if (bytes != null)
{
var bmp = new Bitmap(new MemoryStream(bytes));
Dispatcher.UIThread.Post(() =>
{
var old = CanvasImage;
CanvasImage = bmp;
TilesCaptured = _bot.AtlasExecutor.ActivePanorama?.TilesCaptured ?? TilesCaptured;
old?.Dispose();
});
}
await Task.Delay(500, ct);
}
catch (OperationCanceledException) { break; }
catch (Exception ex)
{
Log.Debug(ex, "Atlas poll error");
await Task.Delay(1000, ct);
}
}
}
public void Dispose()
{
_pollCts.Cancel();
_pollCts.Dispose();
CanvasImage?.Dispose();
}
}