Files

510 lines
22 KiB
C#
Raw Permalink Normal View History

using MainShell.Common;
using MainShell.Hardware;
using MainShell.Log;
using MainShell.Motion;
using MainShell.Vision;
using MwFramework.Device;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using System.Windows;
using CameraBufferReceivedEventArgs = MwFramework.Device.Model.CameraBufferReceivedEventArgs;
namespace MainShell.Process
{
public class PlaceholderWaferCaptureExecutor : IWaferCaptureExecutor
{
private sealed class PendingCaptureRequest
{
public long SequenceId { get; set; }
public Point TriggerPoint { get; set; }
public DateTime TriggerTime { get; set; }
}
private sealed class EventDrivenCaptureSession
{
private readonly object _syncRoot = new object();
private readonly Queue<PendingCaptureRequest> _pendingRequests = new Queue<PendingCaptureRequest>();
private readonly List<WaferCaptureFrame> _frames = new List<WaferCaptureFrame>();
private readonly TaskCompletionSource<bool> _completionSource = new TaskCompletionSource<bool>();
private int _expectedFrameCount;
private int _receivedFrameCount;
public void Enqueue(PendingCaptureRequest pendingRequest)
{
lock (_syncRoot)
{
_pendingRequests.Enqueue(pendingRequest);
_expectedFrameCount++;
}
}
public void AddReceivedFrame(WaferCaptureFrame frame)
{
lock (_syncRoot)
{
_frames.Add(frame);
_receivedFrameCount++;
if (_receivedFrameCount >= _expectedFrameCount && _pendingRequests.Count == 0)
{
_completionSource.TrySetResult(true);
}
}
}
public PendingCaptureRequest Dequeue()
{
lock (_syncRoot)
{
if (_pendingRequests.Count == 0)
{
return null;
}
return _pendingRequests.Dequeue();
}
}
public Task WaitForCompletionAsync(int timeoutMilliseconds, CancellationToken cancellationToken)
{
return WaitForCompletionCoreAsync(timeoutMilliseconds, cancellationToken);
}
public List<WaferCaptureFrame> GetFramesOrdered()
{
lock (_syncRoot)
{
List<WaferCaptureFrame> frames = new List<WaferCaptureFrame>(_frames);
frames.Sort((left, right) => left.SequenceId.CompareTo(right.SequenceId));
return frames;
}
}
public int ExpectedFrameCount
{
get
{
lock (_syncRoot)
{
return _expectedFrameCount;
}
}
}
public int ReceivedFrameCount
{
get
{
lock (_syncRoot)
{
return _receivedFrameCount;
}
}
}
public List<WaferCaptureFrame> DrainPendingAsLost(string errorMessage)
{
lock (_syncRoot)
{
List<WaferCaptureFrame> lostFrames = new List<WaferCaptureFrame>();
while (_pendingRequests.Count > 0)
{
PendingCaptureRequest pendingRequest = _pendingRequests.Dequeue();
WaferCaptureFrame lostFrame = new WaferCaptureFrame();
lostFrame.SequenceId = pendingRequest.SequenceId;
lostFrame.TriggerPoint = pendingRequest.TriggerPoint;
lostFrame.FrameId = Guid.NewGuid().ToString("N");
lostFrame.TriggerTime = pendingRequest.TriggerTime;
lostFrame.ReceiveTime = DateTime.Now;
lostFrame.IsLostFrame = true;
lostFrame.CaptureErrorMessage = errorMessage;
_frames.Add(lostFrame);
lostFrames.Add(lostFrame);
}
if (_receivedFrameCount >= _expectedFrameCount && _pendingRequests.Count == 0)
{
_completionSource.TrySetResult(true);
}
return lostFrames;
}
}
private async Task WaitForCompletionCoreAsync(int timeoutMilliseconds, CancellationToken cancellationToken)
{
using (CancellationTokenRegistration registration = cancellationToken.Register(() => _completionSource.TrySetCanceled()))
{
Task completedTask = await Task.WhenAny(_completionSource.Task, Task.Delay(timeoutMilliseconds, cancellationToken)).ConfigureAwait(false);
if (completedTask != _completionSource.Task)
{
throw new TimeoutException(string.Format(CultureInfo.InvariantCulture, "晶圆定位:等待相机图像超时,超时时间 {0} ms。", timeoutMilliseconds));
}
await _completionSource.Task.ConfigureAwait(false);
}
}
}
private const int DefaultMotionSettlingMilliseconds = 20;
private readonly IWaferMachineAdapter _waferMachineAdapter;
private readonly SafeAxisMotion _safeAxisMotion;
private readonly HardwareManager _hardwareManager;
public PlaceholderWaferCaptureExecutor(
IWaferMachineAdapter waferMachineAdapter,
SafeAxisMotion safeAxisMotion,
HardwareManager hardwareManager)
{
_waferMachineAdapter = waferMachineAdapter ?? throw new ArgumentNullException(nameof(waferMachineAdapter));
_safeAxisMotion = safeAxisMotion ?? throw new ArgumentNullException(nameof(safeAxisMotion));
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
}
public async Task<WaferScanExecutionSummary> ExecuteAsync(WaferDiePositionContext context, ChannelWriter<WaferCaptureFrame> frameWriter, CancellationToken cancellationToken)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (frameWriter == null)
{
throw new ArgumentNullException(nameof(frameWriter));
}
List<Point> pathPoints = ResolvePathPoints(context);
WaferScanExecutionSummary summary = new WaferScanExecutionSummary();
summary.PlannedPointCount = pathPoints.Count;
summary.ProducedFrameCount = 0;
summary.ConsumedFrameCount = 0;
summary.RecognitionSuccessCount = 0;
summary.RecognitionFailureCount = 0;
summary.LastErrorMessage = null;
if (pathPoints.Count == 0)
{
LogManager.LogProcessInfo("晶圆定位:未生成扫描点,跳过运动与采图。");
return summary;
}
EnsureCaptureFrameList(context);
CameraType cameraSource = _waferMachineAdapter.GetCameraSource(context);
CameraCaptureOptions captureOptions = _waferMachineAdapter.CreateSoftTriggerCaptureOptions(context) ?? CameraCaptureOptions.CreateSoftTrigger(5000);
MwCamera camera = _waferMachineAdapter.GetCamera(context);
IAxis axisX = _hardwareManager.Axis_PHS_X1;
IAxis axisY = _hardwareManager.Axis_Stage_Y3;
EnsureAxes(axisX, axisY);
EnsureCamera(camera, cameraSource);
int motionSettlingMilliseconds = ResolveMotionSettlingMilliseconds(captureOptions);
int exposureDelayMilliseconds = ResolveExposureDelayMilliseconds(_waferMachineAdapter.GetExposureTimeMilliseconds(context));
int completionTimeoutMilliseconds = ResolveCompletionTimeoutMilliseconds(captureOptions, exposureDelayMilliseconds, pathPoints.Count);
EventDrivenCaptureSession captureSession = new EventDrivenCaptureSession();
EventHandler<CameraBufferReceivedEventArgs> cameraBufferHandler = (sender, eventArgs) =>
{
HandleCameraBufferReceived(captureSession, frameWriter, eventArgs, cancellationToken);
};
PrepareCameraForSoftTrigger(camera);
camera.CameraBufferReceived += cameraBufferHandler;
try
{
for (int pointIndex = 0; pointIndex < pathPoints.Count; pointIndex++)
{
cancellationToken.ThrowIfCancellationRequested();
Point targetPoint = pathPoints[pointIndex];
LogManager.LogProcessInfo(
string.Format(
CultureInfo.InvariantCulture,
"晶圆定位:执行扫描点 {0}/{1}X={2:F3}Y={3:F3}。",
pointIndex + 1,
pathPoints.Count,
targetPoint.X,
targetPoint.Y));
try
{
await MoveToPointAsync(axisX, axisY, targetPoint, cancellationToken).ConfigureAwait(false);
await Task.Delay(motionSettlingMilliseconds, cancellationToken).ConfigureAwait(false);
PendingCaptureRequest pendingCaptureRequest = new PendingCaptureRequest();
pendingCaptureRequest.SequenceId = pointIndex + 1;
pendingCaptureRequest.TriggerPoint = targetPoint;
pendingCaptureRequest.TriggerTime = DateTime.Now;
captureSession.Enqueue(pendingCaptureRequest);
TriggerSoftCapture(camera, pendingCaptureRequest, exposureDelayMilliseconds);
await Task.Delay(exposureDelayMilliseconds, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
summary.LastErrorMessage = string.Format(
CultureInfo.InvariantCulture,
"晶圆定位:在扫描点 {0}/{1} 处取消流程。",
pointIndex + 1,
pathPoints.Count);
context.ErrorMessage = summary.LastErrorMessage;
throw;
}
catch (Exception ex)
{
summary.LastErrorMessage = string.Format(
CultureInfo.InvariantCulture,
"晶圆定位:扫描点 {0}/{1} 执行失败X={2:F3}Y={3:F3}。{4}",
pointIndex + 1,
pathPoints.Count,
targetPoint.X,
targetPoint.Y,
ex.Message);
context.ErrorMessage = summary.LastErrorMessage;
LogManager.LogProcessError(summary.LastErrorMessage);
throw new InvalidOperationException(summary.LastErrorMessage, ex);
}
}
await captureSession.WaitForCompletionAsync(completionTimeoutMilliseconds, cancellationToken).ConfigureAwait(false);
}
catch (TimeoutException ex)
{
List<WaferCaptureFrame> lostFrames = captureSession.DrainPendingAsLost(ex.Message);
summary.LastErrorMessage = ex.Message;
context.ErrorMessage = ex.Message;
foreach (WaferCaptureFrame lostFrame in lostFrames)
{
context.CaptureFrames.Add(lostFrame);
PublishCapturedFrame(lostFrame, frameWriter, cancellationToken);
}
throw new InvalidOperationException(ex.Message, ex);
}
finally
{
camera.CameraBufferReceived -= cameraBufferHandler;
}
List<WaferCaptureFrame> receivedFrames = captureSession.GetFramesOrdered();
context.CaptureFrames.Clear();
foreach (WaferCaptureFrame receivedFrame in receivedFrames)
{
context.CaptureFrames.Add(receivedFrame);
}
summary.ProducedFrameCount = context.CaptureFrames.Count;
summary.ConsumedFrameCount = context.CaptureFrames.Count;
if (summary.ProducedFrameCount != summary.PlannedPointCount)
{
summary.LastErrorMessage = string.Format(
CultureInfo.InvariantCulture,
"晶圆定位:采图帧数量不一致。计划={0},实际接收={1}。",
summary.PlannedPointCount,
summary.ProducedFrameCount);
context.ErrorMessage = summary.LastErrorMessage;
throw new InvalidOperationException(summary.LastErrorMessage);
}
LogManager.LogProcessInfo(
string.Format(
CultureInfo.InvariantCulture,
"晶圆定位:扫描执行完成。计划帧数={0},采图帧数={1}。",
summary.PlannedPointCount,
summary.ProducedFrameCount));
return summary;
}
private async Task MoveToPointAsync(IAxis axisX, IAxis axisY, Point targetPoint, CancellationToken cancellationToken)
{
MotionMoveRequest requestX = MotionMoveRequest.ForAxis(axisX, targetPoint.X, source: "DiePosition", tags: new string[] { "WaferScan", "TopCamera" });
MotionMoveRequest requestY = MotionMoveRequest.ForAxis(axisY, targetPoint.Y, source: "DiePosition", tags: new string[] { "WaferScan", "TopCamera" });
MotionBatchResult motionResult = await _safeAxisMotion.SafeMoveAsync(cancellationToken, requestX, requestY).ConfigureAwait(false);
motionResult.EnsureSuccess();
}
private void HandleCameraBufferReceived(
EventDrivenCaptureSession captureSession,
ChannelWriter<WaferCaptureFrame> frameWriter,
CameraBufferReceivedEventArgs eventArgs,
CancellationToken cancellationToken)
{
PendingCaptureRequest pendingCaptureRequest = captureSession.Dequeue();
if (pendingCaptureRequest == null)
{
LogManager.LogProcessInfo("晶圆定位:收到异常相机图像,因未找到待处理触发请求,已忽略该图像。");
return;
}
WaferCaptureFrame frame = new WaferCaptureFrame();
frame.SequenceId = pendingCaptureRequest.SequenceId;
frame.TriggerPoint = pendingCaptureRequest.TriggerPoint;
frame.FrameId = Guid.NewGuid().ToString("N");
frame.TriggerTime = pendingCaptureRequest.TriggerTime;
frame.ReceiveTime = DateTime.Now;
if (eventArgs == null || !eventArgs.IsValid || eventArgs.CameraData == null)
{
frame.IsLostFrame = true;
frame.CaptureErrorMessage = eventArgs == null
? "晶圆定位:相机事件参数为空。"
: string.Format(
CultureInfo.InvariantCulture,
"晶圆定位:收到无效相机图像。错误码={0},描述={1}。",
eventArgs.GrabErrorCode,
eventArgs.GrabErrorDescription ?? string.Empty);
captureSession.AddReceivedFrame(frame);
PublishCapturedFrame(frame, frameWriter, cancellationToken);
return;
}
frame.IsLostFrame = false;
frame.Image = new MainShell.Models.MxImage(eventArgs.CameraData);
frame.CaptureErrorMessage = null;
captureSession.AddReceivedFrame(frame);
PublishCapturedFrame(frame, frameWriter, cancellationToken);
}
private static void PublishCapturedFrame(WaferCaptureFrame frame, ChannelWriter<WaferCaptureFrame> frameWriter, CancellationToken cancellationToken)
{
if (frame == null)
{
return;
}
if (frameWriter == null)
{
throw new ArgumentNullException(nameof(frameWriter));
}
frameWriter.WriteAsync(frame, cancellationToken).AsTask().GetAwaiter().GetResult();
}
private static List<Point> ResolvePathPoints(WaferDiePositionContext context)
{
if (context == null || context.ScanPlan == null)
{
return new List<Point>();
}
IReadOnlyList<Point> candidatePoints = context.ScanPlan.FinalPathPoints ?? context.ScanPlan.PathPoints;
if (candidatePoints == null || candidatePoints.Count == 0)
{
return new List<Point>();
}
return new List<Point>(candidatePoints);
}
private static void EnsureCaptureFrameList(WaferDiePositionContext context)
{
if (context.CaptureFrames == null)
{
context.CaptureFrames = new List<WaferCaptureFrame>();
}
else
{
context.CaptureFrames.Clear();
}
}
private static void EnsureCamera(MwCamera camera, CameraType cameraSource)
{
if (camera == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "晶圆定位:相机“{0}”不可用。", cameraSource));
}
if (!camera.IsOpen)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "晶圆定位:相机“{0}”未打开。", cameraSource));
}
}
private static int ResolveMotionSettlingMilliseconds(CameraCaptureOptions captureOptions)
{
if (captureOptions != null && captureOptions.TriggerDelay.HasValue && captureOptions.TriggerDelay.Value > 0d)
{
return Math.Max(1, (int)Math.Ceiling(captureOptions.TriggerDelay.Value));
}
return DefaultMotionSettlingMilliseconds;
}
private static int ResolveExposureDelayMilliseconds(double exposureTimeMilliseconds)
{
if (double.IsNaN(exposureTimeMilliseconds) || double.IsInfinity(exposureTimeMilliseconds) || exposureTimeMilliseconds <= 0d)
{
return 1;
}
return Math.Max(1, (int)Math.Ceiling(exposureTimeMilliseconds));
}
private static int ResolveCompletionTimeoutMilliseconds(CameraCaptureOptions captureOptions, int exposureDelayMilliseconds, int pointCount)
{
int baseTimeout = captureOptions == null || captureOptions.TimeoutMilliseconds <= 0 ? 5000 : captureOptions.TimeoutMilliseconds;
int pipelineTimeout = exposureDelayMilliseconds * Math.Max(1, pointCount) + 1000;
return Math.Max(baseTimeout, pipelineTimeout);
}
private static void TriggerSoftCapture(MwCamera camera, PendingCaptureRequest pendingCaptureRequest, int exposureDelayMilliseconds)
{
DriverLibResult triggerResult = camera.ExcuteSoftTrigger();
if (triggerResult != DriverLibResult.DriverLibNoError)
{
throw new InvalidOperationException(
string.Format(
CultureInfo.InvariantCulture,
"晶圆定位:软触发失败。序号={0}X={1:F3}Y={2:F3},曝光等待={3} ms结果={4}。",
pendingCaptureRequest.SequenceId,
pendingCaptureRequest.TriggerPoint.X,
pendingCaptureRequest.TriggerPoint.Y,
exposureDelayMilliseconds,
triggerResult));
}
}
private static void PrepareCameraForSoftTrigger(MwCamera camera)
{
CameraTriggerMode triggerMode = (CameraTriggerMode)Enum.Parse(typeof(CameraTriggerMode), "On", true);
CameraTriggerSource triggerSource = (CameraTriggerSource)Enum.Parse(typeof(CameraTriggerSource), "Software", true);
DriverLibResult setModeResult = camera.SetTriggerMode(triggerMode);
if (setModeResult != DriverLibResult.DriverLibNoError)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "晶圆定位:设置相机触发模式失败。结果={0}。", setModeResult));
}
DriverLibResult setSourceResult = camera.SetTriggerSource(triggerSource);
if (setSourceResult != DriverLibResult.DriverLibNoError)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "晶圆定位:设置相机触发源失败。结果={0}。", setSourceResult));
}
if (!camera.IsGrabbing)
{
DriverLibResult startGrabbingResult = camera.StartGrabbing();
if (startGrabbingResult != DriverLibResult.DriverLibNoError && startGrabbingResult != DriverLibResult.DriverLibDeviceAlreadyGrabbing)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "晶圆定位:启动相机采集失败。结果={0}。", startGrabbingResult));
}
}
}
private static void EnsureAxes(IAxis axisX, IAxis axisY)
{
if (axisX == null || axisY == null)
{
throw new InvalidOperationException("晶圆定位HardwareManager 中未找到顶相机对应的 X/Y 轴。");
}
}
}
}