510 lines
22 KiB
C#
510 lines
22 KiB
C#
|
|
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 轴。");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|