Files
Shi.Ji e31d3560bb 添加 MX-PD-盘古 项目文件
将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
2026-05-18 11:43:09 +08:00

510 lines
22 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 轴。");
}
}
}
}