150 lines
4.2 KiB
C#
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();
|
|
}
|
|
}
|