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 _pendingRequests = new Queue(); private readonly List _frames = new List(); private readonly TaskCompletionSource _completionSource = new TaskCompletionSource(); 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 GetFramesOrdered() { lock (_syncRoot) { List frames = new List(_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 DrainPendingAsLost(string errorMessage) { lock (_syncRoot) { List lostFrames = new List(); 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 ExecuteAsync(WaferDiePositionContext context, ChannelWriter frameWriter, CancellationToken cancellationToken) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (frameWriter == null) { throw new ArgumentNullException(nameof(frameWriter)); } List 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 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 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 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 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 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 ResolvePathPoints(WaferDiePositionContext context) { if (context == null || context.ScanPlan == null) { return new List(); } IReadOnlyList candidatePoints = context.ScanPlan.FinalPathPoints ?? context.ScanPlan.PathPoints; if (candidatePoints == null || candidatePoints.Count == 0) { return new List(); } return new List(candidatePoints); } private static void EnsureCaptureFrameList(WaferDiePositionContext context) { if (context.CaptureFrames == null) { context.CaptureFrames = new List(); } 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 轴。"); } } } }