984 lines
41 KiB
C#
984 lines
41 KiB
C#
|
|
using MainShell.Common;
|
||
|
|
using MainShell.Filewritable;
|
||
|
|
using MainShell.Log;
|
||
|
|
using MainShell.Models;
|
||
|
|
using MainShell.Models.Wafer;
|
||
|
|
using MainShell.Parameter;
|
||
|
|
using MainShell.ProcessResult;
|
||
|
|
using MainShell.ParaSetting.Model;
|
||
|
|
using MainShell.Recipe.Models;
|
||
|
|
using MainShell.Vision;
|
||
|
|
using MaxwellFramework.Core.Attributes;
|
||
|
|
using MXJM.FileWritable;
|
||
|
|
using MW.WorkFlow;
|
||
|
|
using System;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using System.IO;
|
||
|
|
using System.Linq;
|
||
|
|
using System.Runtime.ExceptionServices;
|
||
|
|
using System.Threading;
|
||
|
|
using System.Threading.Channels;
|
||
|
|
using System.Threading.Tasks;
|
||
|
|
using System.Windows;
|
||
|
|
|
||
|
|
using Jm1RecheckDiePoint = JM1.JM1Params.RecheckDiePoint;
|
||
|
|
|
||
|
|
namespace MainShell.Process
|
||
|
|
{
|
||
|
|
public class DiePositionService
|
||
|
|
{
|
||
|
|
private sealed class PipelineFailureCoordinator
|
||
|
|
{
|
||
|
|
private readonly object _syncRoot = new object();
|
||
|
|
private readonly CancellationTokenSource _pipelineCancellationTokenSource;
|
||
|
|
private readonly ChannelWriter<WaferCaptureFrame> _frameWriter;
|
||
|
|
private ExceptionDispatchInfo _failureDispatchInfo;
|
||
|
|
|
||
|
|
public PipelineFailureCoordinator(CancellationTokenSource pipelineCancellationTokenSource, ChannelWriter<WaferCaptureFrame> frameWriter)
|
||
|
|
{
|
||
|
|
_pipelineCancellationTokenSource = pipelineCancellationTokenSource ?? throw new ArgumentNullException(nameof(pipelineCancellationTokenSource));
|
||
|
|
_frameWriter = frameWriter ?? throw new ArgumentNullException(nameof(frameWriter));
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool HasFailure
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
lock (_syncRoot)
|
||
|
|
{
|
||
|
|
return _failureDispatchInfo != null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public void TrySet(Exception exception)
|
||
|
|
{
|
||
|
|
if (exception == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(exception));
|
||
|
|
}
|
||
|
|
|
||
|
|
bool shouldCancelPipeline = false;
|
||
|
|
lock (_syncRoot)
|
||
|
|
{
|
||
|
|
if (_failureDispatchInfo == null)
|
||
|
|
{
|
||
|
|
_failureDispatchInfo = ExceptionDispatchInfo.Capture(exception);
|
||
|
|
shouldCancelPipeline = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!shouldCancelPipeline)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
_frameWriter.TryComplete(exception);
|
||
|
|
if (!_pipelineCancellationTokenSource.IsCancellationRequested)
|
||
|
|
{
|
||
|
|
_pipelineCancellationTokenSource.Cancel();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public void ThrowIfFailed()
|
||
|
|
{
|
||
|
|
ExceptionDispatchInfo failureDispatchInfo = null;
|
||
|
|
lock (_syncRoot)
|
||
|
|
{
|
||
|
|
failureDispatchInfo = _failureDispatchInfo;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (failureDispatchInfo != null)
|
||
|
|
{
|
||
|
|
failureDispatchInfo.Throw();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private const int DefaultRecognitionConsumerCount = 2;
|
||
|
|
private const int DefaultFrameChannelCapacity = 8;
|
||
|
|
private const double DefaultMapAngleThreshold = 3.0d;
|
||
|
|
private const double DefaultMapRotateAngle = 0.0d;
|
||
|
|
private const double DefaultMapDistanceThreshold = 0.01d;
|
||
|
|
|
||
|
|
private readonly IWaferScanPlanner _waferScanPlanner;
|
||
|
|
private readonly IWaferMachineAdapter _waferMachineAdapter;
|
||
|
|
private readonly IWaferCaptureExecutor _waferCaptureExecutor;
|
||
|
|
private readonly IWaferDieRecognizer _waferDieRecognizer;
|
||
|
|
private readonly ICommonVisionAlgorithmService _commonVisionAlgorithmService;
|
||
|
|
private readonly IWaferRecognitionResultCollector _waferRecognitionResultCollector;
|
||
|
|
private readonly IWaferDiePositionResultService _waferDiePositionResultService;
|
||
|
|
private readonly GlobalParameterContext _globalParameterContext;
|
||
|
|
private readonly FileWriteQueue _fileWriteQueue;
|
||
|
|
|
||
|
|
public DiePositionService(
|
||
|
|
IWaferScanPlanner waferScanPlanner,
|
||
|
|
IWaferMachineAdapter waferMachineAdapter,
|
||
|
|
IWaferCaptureExecutor waferCaptureExecutor,
|
||
|
|
IWaferDieRecognizer waferDieRecognizer,
|
||
|
|
ICommonVisionAlgorithmService commonVisionAlgorithmService,
|
||
|
|
IWaferRecognitionResultCollector waferRecognitionResultCollector,
|
||
|
|
IWaferDiePositionResultService waferDiePositionResultService,
|
||
|
|
GlobalParameterContext globalParameterContext,
|
||
|
|
FileWriteQueue fileWriteQueue)
|
||
|
|
{
|
||
|
|
_waferScanPlanner = waferScanPlanner ?? throw new ArgumentNullException(nameof(waferScanPlanner));
|
||
|
|
_waferMachineAdapter = waferMachineAdapter ?? throw new ArgumentNullException(nameof(waferMachineAdapter));
|
||
|
|
_waferCaptureExecutor = waferCaptureExecutor ?? throw new ArgumentNullException(nameof(waferCaptureExecutor));
|
||
|
|
_waferDieRecognizer = waferDieRecognizer ?? throw new ArgumentNullException(nameof(waferDieRecognizer));
|
||
|
|
_commonVisionAlgorithmService = commonVisionAlgorithmService ?? throw new ArgumentNullException(nameof(commonVisionAlgorithmService));
|
||
|
|
_waferRecognitionResultCollector = waferRecognitionResultCollector ?? throw new ArgumentNullException(nameof(waferRecognitionResultCollector));
|
||
|
|
_waferDiePositionResultService = waferDiePositionResultService ?? throw new ArgumentNullException(nameof(waferDiePositionResultService));
|
||
|
|
_globalParameterContext = globalParameterContext ?? throw new ArgumentNullException(nameof(globalParameterContext));
|
||
|
|
_fileWriteQueue = fileWriteQueue ?? throw new ArgumentNullException(nameof(fileWriteQueue));
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task ExecuteAsync(WorkflowContext context, ActivityControl activityControl)
|
||
|
|
{
|
||
|
|
if (context == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(context));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (activityControl == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(activityControl));
|
||
|
|
}
|
||
|
|
|
||
|
|
RecipeManager recipeManager = context.GetData<RecipeManager>(WorkflowContextKeys.RecipeManager);
|
||
|
|
ProcessResultManager processResultManager = context.GetData<ProcessResultManager>(WorkflowContextKeys.ProcessResultManager);
|
||
|
|
DiePositionProcessResult processResult = InitializeProcessResult(processResultManager);
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
await CheckCancellationAndPauseAsync(activityControl).ConfigureAwait(false);
|
||
|
|
_waferRecognitionResultCollector.Reset();
|
||
|
|
|
||
|
|
WaferDiePositionContext runtimeContext = BuildRuntimeContext(recipeManager, context, activityControl);
|
||
|
|
runtimeContext.MapAngleThreshold = ResolveMapAngleThreshold(_globalParameterContext);
|
||
|
|
runtimeContext.MapRotateAngle = ResolveMapRotateAngle(processResultManager);
|
||
|
|
runtimeContext.MapDistanceThreshold = ResolveDistanceThreshold();
|
||
|
|
WaferScanPlan scanPlan = CreateScanPlan(runtimeContext);
|
||
|
|
runtimeContext.ScanPlan = scanPlan;
|
||
|
|
runtimeContext.ExecutionSummary = await ExecuteScanWithRecognitionPipelineAsync(runtimeContext).ConfigureAwait(false);
|
||
|
|
WaferRecognitionSnapshot recognitionSnapshot = CollectSnapshot();
|
||
|
|
_waferDiePositionResultService.ApplySnapshot(runtimeContext, recognitionSnapshot);
|
||
|
|
await GenerateWaferMapAsync(runtimeContext, recognitionSnapshot).ConfigureAwait(false);
|
||
|
|
|
||
|
|
PopulateProcessResult(processResult, runtimeContext, scanPlan);
|
||
|
|
_waferDiePositionResultService.WriteProcessResult(runtimeContext, processResult);
|
||
|
|
processResult.IsSuccess = true;
|
||
|
|
processResult.ErrorMessage = null;
|
||
|
|
processResult.ErrorMessageKey = MessageKey.None;
|
||
|
|
processResult.ErrorMessageArguments = Array.Empty<string>();
|
||
|
|
|
||
|
|
context.SetData(WorkflowContextKeys.DiePositionContext, runtimeContext);
|
||
|
|
context.SetData(WorkflowContextKeys.DiePositionScanPlan, scanPlan);
|
||
|
|
|
||
|
|
LogManager.LogProcessInfo(
|
||
|
|
string.Format(
|
||
|
|
"晶圆定位:扫描执行完成。配方={0},行数={1},列数={2},扫描点数={3},采图帧数={4},识别成功数={5},识别失败数={6}。",
|
||
|
|
processResult.RecipeName,
|
||
|
|
processResult.RowCount,
|
||
|
|
processResult.ColumnCount,
|
||
|
|
processResult.ScanPointCount,
|
||
|
|
runtimeContext.ExecutionSummary == null ? 0 : runtimeContext.ExecutionSummary.ProducedFrameCount,
|
||
|
|
runtimeContext.ExecutionSummary == null ? 0 : runtimeContext.ExecutionSummary.RecognitionSuccessCount,
|
||
|
|
runtimeContext.ExecutionSummary == null ? 0 : runtimeContext.ExecutionSummary.RecognitionFailureCount));
|
||
|
|
}
|
||
|
|
catch (OperationCanceledException)
|
||
|
|
{
|
||
|
|
processResult.IsSuccess = false;
|
||
|
|
processResult.ErrorMessage = null;
|
||
|
|
processResult.ErrorMessageKey = MessageKey.None;
|
||
|
|
processResult.ErrorMessageArguments = Array.Empty<string>();
|
||
|
|
LogManager.LogProcessInfo("晶圆定位:流程已取消。");
|
||
|
|
throw;
|
||
|
|
}
|
||
|
|
catch (LocalizedProcessException ex)
|
||
|
|
{
|
||
|
|
processResult.IsSuccess = false;
|
||
|
|
processResult.ErrorMessageKey = ex.FailureMessageKey;
|
||
|
|
processResult.ErrorMessageArguments = ex.FailureMessageArguments;
|
||
|
|
processResult.ErrorMessage = ex.Message;
|
||
|
|
LogManager.LogProcessError(string.Format("晶圆定位:{0}", ex.Message));
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
processResult.IsSuccess = false;
|
||
|
|
processResult.ErrorMessageKey = MessageKey.ProcessDiePositionFailedWithReason;
|
||
|
|
processResult.ErrorMessageArguments = new string[] { ex.Message ?? string.Empty };
|
||
|
|
processResult.ErrorMessage = LanguageResourceHelper.Format(
|
||
|
|
processResult.ErrorMessageKey,
|
||
|
|
LocalizedProcessException.ConvertArguments(processResult.ErrorMessageArguments));
|
||
|
|
LogManager.LogProcessError(string.Format("晶圆定位:{0}", processResult.ErrorMessage));
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
WriteResultToContext(context, processResultManager, processResult);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static WaferDiePositionContext BuildRuntimeContext(RecipeManager recipeManager, WorkflowContext context, ActivityControl activityControl)
|
||
|
|
{
|
||
|
|
WaferRecipe waferRecipe = GetCurrentWaferRecipe(recipeManager);
|
||
|
|
WaferInfo waferInfo = GetWaferInfo(waferRecipe);
|
||
|
|
|
||
|
|
WaferDiePositionContext runtimeContext = new WaferDiePositionContext();
|
||
|
|
runtimeContext.WorkflowName = context.GetData<string>(WorkflowContextKeys.WorkflowName);
|
||
|
|
runtimeContext.RecipeName = waferRecipe.RecipeName;
|
||
|
|
runtimeContext.WaferRecipe = waferRecipe;
|
||
|
|
runtimeContext.ScanSettings = waferRecipe.ScanSettings ?? new WaferScanSettings();
|
||
|
|
runtimeContext.RowCount = waferInfo.WaferRowNum;
|
||
|
|
runtimeContext.ColumnCount = waferInfo.WaferColNum;
|
||
|
|
runtimeContext.PitchX = waferInfo.DiePitchX;
|
||
|
|
runtimeContext.PitchY = waferInfo.DiePitchY;
|
||
|
|
runtimeContext.CancellationToken = activityControl.CancellationToken;
|
||
|
|
return runtimeContext;
|
||
|
|
}
|
||
|
|
|
||
|
|
private WaferScanPlan CreateScanPlan(WaferDiePositionContext runtimeContext)
|
||
|
|
{
|
||
|
|
return _waferScanPlanner.CreatePlan(runtimeContext);
|
||
|
|
}
|
||
|
|
|
||
|
|
private async Task<WaferScanExecutionSummary> ExecuteScanWithRecognitionPipelineAsync(WaferDiePositionContext runtimeContext)
|
||
|
|
{
|
||
|
|
if (runtimeContext == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(runtimeContext));
|
||
|
|
}
|
||
|
|
|
||
|
|
CancellationToken originalCancellationToken = runtimeContext.CancellationToken;
|
||
|
|
CancellationTokenSource pipelineCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(originalCancellationToken);
|
||
|
|
try
|
||
|
|
{
|
||
|
|
runtimeContext.CancellationToken = pipelineCancellationTokenSource.Token;
|
||
|
|
EnsureFrameResults(runtimeContext);
|
||
|
|
|
||
|
|
int consumerCount = ResolveRecognitionConsumerCount(runtimeContext);
|
||
|
|
Channel<WaferCaptureFrame> frameChannel = CreateFrameChannel(runtimeContext, consumerCount);
|
||
|
|
PipelineFailureCoordinator failureCoordinator = new PipelineFailureCoordinator(pipelineCancellationTokenSource, frameChannel.Writer);
|
||
|
|
List<WaferRecognitionFrameResult> frameResults = new List<WaferRecognitionFrameResult>();
|
||
|
|
object frameResultsSyncRoot = new object();
|
||
|
|
List<Task> consumerTasks = StartRecognitionConsumers(frameChannel.Reader, runtimeContext, frameResults, frameResultsSyncRoot, consumerCount, failureCoordinator);
|
||
|
|
|
||
|
|
LogManager.LogProcessInfo($"晶圆定位:启动流式识别处理链。消费者数量={consumerCount}。");
|
||
|
|
|
||
|
|
WaferScanExecutionSummary summary = null;
|
||
|
|
try
|
||
|
|
{
|
||
|
|
summary = await _waferCaptureExecutor.ExecuteAsync(runtimeContext, frameChannel.Writer, runtimeContext.CancellationToken).ConfigureAwait(false);
|
||
|
|
}
|
||
|
|
catch (OperationCanceledException) when (failureCoordinator.HasFailure)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
failureCoordinator.TrySet(ex);
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
frameChannel.Writer.TryComplete();
|
||
|
|
}
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
await Task.WhenAll(consumerTasks).ConfigureAwait(false);
|
||
|
|
}
|
||
|
|
catch (OperationCanceledException) when (failureCoordinator.HasFailure)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
failureCoordinator.TrySet(ex);
|
||
|
|
}
|
||
|
|
|
||
|
|
failureCoordinator.ThrowIfFailed();
|
||
|
|
runtimeContext.CancellationToken.ThrowIfCancellationRequested();
|
||
|
|
ApplyFrameResults(runtimeContext, frameResults, summary);
|
||
|
|
return summary;
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
runtimeContext.CancellationToken = originalCancellationToken;
|
||
|
|
pipelineCancellationTokenSource.Dispose();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static Channel<WaferCaptureFrame> CreateFrameChannel(WaferDiePositionContext runtimeContext, int consumerCount)
|
||
|
|
{
|
||
|
|
if (runtimeContext == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(runtimeContext));
|
||
|
|
}
|
||
|
|
|
||
|
|
int capacity = ResolveFrameChannelCapacity(runtimeContext, consumerCount);
|
||
|
|
BoundedChannelOptions options = new BoundedChannelOptions(capacity);
|
||
|
|
options.SingleWriter = true;
|
||
|
|
options.SingleReader = false;
|
||
|
|
options.AllowSynchronousContinuations = false;
|
||
|
|
options.FullMode = BoundedChannelFullMode.Wait;
|
||
|
|
return Channel.CreateBounded<WaferCaptureFrame>(options);
|
||
|
|
}
|
||
|
|
|
||
|
|
private List<Task> StartRecognitionConsumers(
|
||
|
|
ChannelReader<WaferCaptureFrame> frameReader,
|
||
|
|
WaferDiePositionContext runtimeContext,
|
||
|
|
List<WaferRecognitionFrameResult> frameResults,
|
||
|
|
object frameResultsSyncRoot,
|
||
|
|
int consumerCount,
|
||
|
|
PipelineFailureCoordinator failureCoordinator)
|
||
|
|
{
|
||
|
|
if (frameReader == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(frameReader));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (runtimeContext == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(runtimeContext));
|
||
|
|
}
|
||
|
|
|
||
|
|
List<Task> consumerTasks = new List<Task>();
|
||
|
|
for (int consumerIndex = 0; consumerIndex < consumerCount; consumerIndex++)
|
||
|
|
{
|
||
|
|
consumerTasks.Add(ProcessQueuedFramesAsync(frameReader, runtimeContext, frameResults, frameResultsSyncRoot, consumerIndex + 1, failureCoordinator));
|
||
|
|
}
|
||
|
|
|
||
|
|
return consumerTasks;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void ApplyFrameResults(
|
||
|
|
WaferDiePositionContext runtimeContext,
|
||
|
|
List<WaferRecognitionFrameResult> frameResults,
|
||
|
|
WaferScanExecutionSummary summary)
|
||
|
|
{
|
||
|
|
if (runtimeContext == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(runtimeContext));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (frameResults == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(frameResults));
|
||
|
|
}
|
||
|
|
|
||
|
|
List<WaferRecognitionFrameResult> orderedResults = frameResults
|
||
|
|
.OrderBy(result => result == null ? long.MaxValue : result.SequenceId)
|
||
|
|
.ToList();
|
||
|
|
|
||
|
|
runtimeContext.FrameResults.Clear();
|
||
|
|
foreach (WaferRecognitionFrameResult frameResult in orderedResults)
|
||
|
|
{
|
||
|
|
runtimeContext.FrameResults.Add(frameResult);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (summary != null)
|
||
|
|
{
|
||
|
|
summary.ConsumedFrameCount = orderedResults.Count;
|
||
|
|
summary.RecognitionSuccessCount = orderedResults.Count(result => result != null && result.IsSuccess);
|
||
|
|
summary.RecognitionFailureCount = orderedResults.Count(result => result == null || !result.IsSuccess);
|
||
|
|
}
|
||
|
|
|
||
|
|
LogManager.LogProcessInfo($"晶圆定位:识别处理链完成。结果数={orderedResults.Count},成功数={(summary == null ? 0 : summary.RecognitionSuccessCount)},失败数={(summary == null ? 0 : summary.RecognitionFailureCount)}。");
|
||
|
|
}
|
||
|
|
|
||
|
|
private async Task ProcessQueuedFramesAsync(
|
||
|
|
ChannelReader<WaferCaptureFrame> frameReader,
|
||
|
|
WaferDiePositionContext runtimeContext,
|
||
|
|
List<WaferRecognitionFrameResult> frameResults,
|
||
|
|
object frameResultsSyncRoot,
|
||
|
|
int consumerId,
|
||
|
|
PipelineFailureCoordinator failureCoordinator)
|
||
|
|
{
|
||
|
|
if (frameReader == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(frameReader));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (runtimeContext == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(runtimeContext));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (frameResults == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(frameResults));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (frameResultsSyncRoot == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(frameResultsSyncRoot));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (failureCoordinator == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(failureCoordinator));
|
||
|
|
}
|
||
|
|
|
||
|
|
LogManager.LogProcessInfo($"晶圆定位:识别消费者 {consumerId} 已启动。");
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
while (await frameReader.WaitToReadAsync(runtimeContext.CancellationToken).ConfigureAwait(false))
|
||
|
|
{
|
||
|
|
WaferCaptureFrame captureFrame = null;
|
||
|
|
while (frameReader.TryRead(out captureFrame))
|
||
|
|
{
|
||
|
|
runtimeContext.CancellationToken.ThrowIfCancellationRequested();
|
||
|
|
|
||
|
|
WaferRecognitionFrameResult frameResult = await RecognizeFrameAsync(captureFrame, runtimeContext).ConfigureAwait(false);
|
||
|
|
_waferRecognitionResultCollector.Add(frameResult);
|
||
|
|
|
||
|
|
lock (frameResultsSyncRoot)
|
||
|
|
{
|
||
|
|
frameResults.Add(frameResult);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch (OperationCanceledException) when (failureCoordinator.HasFailure)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
failureCoordinator.TrySet(ex);
|
||
|
|
throw;
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
LogManager.LogProcessInfo($"晶圆定位:识别消费者 {consumerId} 已停止。");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async Task<WaferRecognitionFrameResult> RecognizeFrameAsync(WaferCaptureFrame captureFrame, WaferDiePositionContext runtimeContext)
|
||
|
|
{
|
||
|
|
if (captureFrame == null)
|
||
|
|
{
|
||
|
|
throw new InvalidOperationException("DiePosition: capture frame is null.");
|
||
|
|
}
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
return await _waferDieRecognizer.RecognizeAsync(captureFrame, runtimeContext, runtimeContext.CancellationToken).ConfigureAwait(false);
|
||
|
|
}
|
||
|
|
catch (OperationCanceledException)
|
||
|
|
{
|
||
|
|
throw;
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
LogManager.LogProcessError($"晶圆定位:帧识别失败。序号={captureFrame.SequenceId},详情={ex.Message}");
|
||
|
|
throw;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private WaferRecognitionSnapshot CollectSnapshot()
|
||
|
|
{
|
||
|
|
return _waferRecognitionResultCollector.CreateSnapshot();
|
||
|
|
}
|
||
|
|
|
||
|
|
private async Task GenerateWaferMapAsync(WaferDiePositionContext runtimeContext, WaferRecognitionSnapshot recognitionSnapshot)
|
||
|
|
{
|
||
|
|
if (runtimeContext == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(runtimeContext));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (recognitionSnapshot == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(recognitionSnapshot));
|
||
|
|
}
|
||
|
|
|
||
|
|
InitializeMapResults(runtimeContext);
|
||
|
|
QueueWaferMapFile(recognitionSnapshot);
|
||
|
|
|
||
|
|
await ClearWaferMapAsync(runtimeContext.CancellationToken).ConfigureAwait(false);
|
||
|
|
|
||
|
|
VisionProcessResult generateMapResult = await _commonVisionAlgorithmService.GenWaferMapXAsync(
|
||
|
|
ResolveWaferMapDirectory(),
|
||
|
|
recognitionSnapshot.OverlapRows,
|
||
|
|
recognitionSnapshot.OverlapColumns,
|
||
|
|
recognitionSnapshot.OverlapAngles,
|
||
|
|
recognitionSnapshot.RowsReal,
|
||
|
|
recognitionSnapshot.ColumnsReal,
|
||
|
|
recognitionSnapshot.Angles,
|
||
|
|
DefaultMapAngleThreshold,
|
||
|
|
ResolveRowDistance(runtimeContext),
|
||
|
|
ResolveColumnDistance(runtimeContext),
|
||
|
|
runtimeContext.RowCount,
|
||
|
|
runtimeContext.ColumnCount,
|
||
|
|
0d,
|
||
|
|
0d,
|
||
|
|
runtimeContext.MapDistanceThreshold,
|
||
|
|
runtimeContext.MapAngleThreshold,
|
||
|
|
runtimeContext.MapRotateAngle,
|
||
|
|
runtimeContext.CancellationToken).ConfigureAwait(false);
|
||
|
|
EnsureVisionSucceeded(generateMapResult, "生成晶圆图");
|
||
|
|
|
||
|
|
VisionProcessResult<(RecheckDieMap UpMap, List<Jm1RecheckDiePoint> PatchPos, List<Jm1RecheckDiePoint> ExceptPos)> getMapResult = await _commonVisionAlgorithmService.GetMapAsync(runtimeContext.CancellationToken).ConfigureAwait(false);
|
||
|
|
EnsureVisionSucceeded(getMapResult, "读取晶圆图");
|
||
|
|
|
||
|
|
runtimeContext.MapDies = ConvertMapToDies(getMapResult.Data.UpMap, runtimeContext.RowCount, runtimeContext.ColumnCount);
|
||
|
|
runtimeContext.PatchDies = ConvertPatchDies(getMapResult.Data.PatchPos, runtimeContext.RowCount, runtimeContext.ColumnCount);
|
||
|
|
runtimeContext.ExceptDies = ConvertExceptDies(getMapResult.Data.ExceptPos, runtimeContext.RowCount, runtimeContext.ColumnCount);
|
||
|
|
|
||
|
|
LogManager.LogProcessInfo($"晶圆定位:晶圆图生成完成。补点数={runtimeContext.PatchDies.Count},异常点数={runtimeContext.ExceptDies.Count}。");
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void InitializeMapResults(WaferDiePositionContext runtimeContext)
|
||
|
|
{
|
||
|
|
runtimeContext.MapDies = new Die[Math.Max(0, runtimeContext.RowCount), Math.Max(0, runtimeContext.ColumnCount)];
|
||
|
|
runtimeContext.PatchDies = new List<Die>();
|
||
|
|
runtimeContext.ExceptDies = new List<Die>();
|
||
|
|
}
|
||
|
|
|
||
|
|
private void QueueWaferMapFile(WaferRecognitionSnapshot recognitionSnapshot)
|
||
|
|
{
|
||
|
|
if (recognitionSnapshot == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(recognitionSnapshot));
|
||
|
|
}
|
||
|
|
|
||
|
|
WaferMapFile waferMapFile = CreateWaferMapFile(recognitionSnapshot);
|
||
|
|
bool enqueueSucceeded = _fileWriteQueue.Enqueue(waferMapFile);
|
||
|
|
if (!enqueueSucceeded)
|
||
|
|
{
|
||
|
|
throw new InvalidOperationException("晶圆定位:识别点文件入队保存失败。");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async Task ClearWaferMapAsync(CancellationToken cancellationToken)
|
||
|
|
{
|
||
|
|
int[] clearMapFlags = new int[] { 1, 2, 3 };
|
||
|
|
for (int index = 0; index < clearMapFlags.Length; index++)
|
||
|
|
{
|
||
|
|
int clearMapFlag = clearMapFlags[index];
|
||
|
|
VisionProcessResult clearMapResult = await _commonVisionAlgorithmService.ClearMapAsync(clearMapFlag, cancellationToken).ConfigureAwait(false);
|
||
|
|
EnsureVisionSucceeded(clearMapResult, $"清空晶圆图,标志={clearMapFlag}");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static WaferMapFile CreateWaferMapFile(WaferRecognitionSnapshot recognitionSnapshot)
|
||
|
|
{
|
||
|
|
WaferMapFile waferMapFile = new WaferMapFile();
|
||
|
|
waferMapFile.UserData = Tuple.Create(
|
||
|
|
recognitionSnapshot.RowsReal ?? Array.Empty<double>(),
|
||
|
|
recognitionSnapshot.ColumnsReal ?? Array.Empty<double>(),
|
||
|
|
recognitionSnapshot.Angles ?? Array.Empty<double>(),
|
||
|
|
recognitionSnapshot.OverlapRows ?? Array.Empty<double>(),
|
||
|
|
recognitionSnapshot.OverlapColumns ?? Array.Empty<double>(),
|
||
|
|
recognitionSnapshot.OverlapAngles ?? Array.Empty<double>());
|
||
|
|
return waferMapFile;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static string ResolveWaferMapDirectory()
|
||
|
|
{
|
||
|
|
return Path.GetDirectoryName(Paths.WaferFileNames["Row"]);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static double ResolveRowDistance(WaferDiePositionContext runtimeContext)
|
||
|
|
{
|
||
|
|
if (runtimeContext.WaferRecipe != null &&
|
||
|
|
runtimeContext.WaferRecipe.WaferInfo != null &&
|
||
|
|
runtimeContext.WaferRecipe.WaferInfo.DiePitchY > 0d)
|
||
|
|
{
|
||
|
|
return runtimeContext.WaferRecipe.WaferInfo.DiePitchY;
|
||
|
|
}
|
||
|
|
|
||
|
|
return runtimeContext.PitchY;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static double ResolveColumnDistance(WaferDiePositionContext runtimeContext)
|
||
|
|
{
|
||
|
|
if (runtimeContext.WaferRecipe != null &&
|
||
|
|
runtimeContext.WaferRecipe.WaferInfo != null &&
|
||
|
|
runtimeContext.WaferRecipe.WaferInfo.DiePitchX > 0d)
|
||
|
|
{
|
||
|
|
return runtimeContext.WaferRecipe.WaferInfo.DiePitchX;
|
||
|
|
}
|
||
|
|
|
||
|
|
return runtimeContext.PitchX;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static double ResolveDistanceThreshold()
|
||
|
|
{
|
||
|
|
return DefaultMapDistanceThreshold;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static double ResolveMapAngleThreshold(GlobalParameterContext globalParameterContext)
|
||
|
|
{
|
||
|
|
if (globalParameterContext != null &&
|
||
|
|
globalParameterContext.RunSetting != null &&
|
||
|
|
globalParameterContext.RunSetting.DieRecognizeProcessParameter != null)
|
||
|
|
{
|
||
|
|
return globalParameterContext.RunSetting.DieRecognizeProcessParameter.DieAngleThreshold;
|
||
|
|
}
|
||
|
|
|
||
|
|
return DefaultMapAngleThreshold;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static double ResolveMapRotateAngle(ProcessResultManager processResultManager)
|
||
|
|
{
|
||
|
|
if (processResultManager == null || processResultManager.SubstratePositionResult == null)
|
||
|
|
{
|
||
|
|
return DefaultMapRotateAngle;
|
||
|
|
}
|
||
|
|
|
||
|
|
return processResultManager.SubstratePositionResult.SubstrateAngle * -1d;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void EnsureVisionSucceeded(VisionProcessResult result, string operationName)
|
||
|
|
{
|
||
|
|
if (result != null && result.Succeeded)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
string message = result == null || string.IsNullOrWhiteSpace(result.Message)
|
||
|
|
? $"晶圆定位:{operationName}失败。"
|
||
|
|
: result.Message;
|
||
|
|
throw new InvalidOperationException(message, result == null ? null : result.Exception);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static Die[,] ConvertMapToDies(RecheckDieMap map, int rowCount, int columnCount)
|
||
|
|
{
|
||
|
|
Die[,] mapDies = new Die[Math.Max(0, rowCount), Math.Max(0, columnCount)];
|
||
|
|
if (map == null)
|
||
|
|
{
|
||
|
|
return mapDies;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach (KeyValuePair<int, Dictionary<int, Jm1RecheckDiePoint>> rowPair in map)
|
||
|
|
{
|
||
|
|
if (rowPair.Value == null)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach (KeyValuePair<int, Jm1RecheckDiePoint> columnPair in rowPair.Value)
|
||
|
|
{
|
||
|
|
int resolvedRowIndex;
|
||
|
|
int resolvedColumnIndex;
|
||
|
|
if (!TryResolveArrayIndex(rowPair.Key, rowCount, out resolvedRowIndex) ||
|
||
|
|
!TryResolveArrayIndex(columnPair.Key, columnCount, out resolvedColumnIndex))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
mapDies[resolvedRowIndex, resolvedColumnIndex] = CreateMapDie(columnPair.Value, resolvedRowIndex + 1, resolvedColumnIndex + 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return mapDies;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static List<Die> ConvertPatchDies(List<Jm1RecheckDiePoint> patchPoints, int rowCount, int columnCount)
|
||
|
|
{
|
||
|
|
return ConvertPointListToDies(patchPoints, rowCount, columnCount, DieStatus.Normal);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static List<Die> ConvertExceptDies(List<Jm1RecheckDiePoint> exceptPoints, int rowCount, int columnCount)
|
||
|
|
{
|
||
|
|
return ConvertPointListToDies(exceptPoints, rowCount, columnCount, DieStatus.Ng);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static List<Die> ConvertPointListToDies(List<Jm1RecheckDiePoint> points, int rowCount, int columnCount, DieStatus defaultStatus)
|
||
|
|
{
|
||
|
|
List<Die> dies = new List<Die>();
|
||
|
|
if (points == null)
|
||
|
|
{
|
||
|
|
return dies;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach (Jm1RecheckDiePoint point in points)
|
||
|
|
{
|
||
|
|
if (point == null)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
int resolvedRowIndex;
|
||
|
|
int resolvedColumnIndex;
|
||
|
|
if (!TryResolveArrayIndex(point.rowIndex, rowCount, out resolvedRowIndex) ||
|
||
|
|
!TryResolveArrayIndex(point.colIndex, columnCount, out resolvedColumnIndex))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
dies.Add(CreateListDie(point, resolvedRowIndex + 1, resolvedColumnIndex + 1, defaultStatus));
|
||
|
|
}
|
||
|
|
|
||
|
|
return dies;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static Die CreateMapDie(Jm1RecheckDiePoint point, int row, int column)
|
||
|
|
{
|
||
|
|
Die die = new Die();
|
||
|
|
die.Row = row;
|
||
|
|
die.Column = column;
|
||
|
|
if (point != null)
|
||
|
|
{
|
||
|
|
die.X = point.X;
|
||
|
|
die.Y = point.Y;
|
||
|
|
die.Angle = point.Angle;
|
||
|
|
die.Status = point.State ? DieStatus.Normal : DieStatus.Ng;
|
||
|
|
}
|
||
|
|
return die;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static Die CreateListDie(Jm1RecheckDiePoint point, int row, int column, DieStatus defaultStatus)
|
||
|
|
{
|
||
|
|
Die die = new Die();
|
||
|
|
die.Row = row;
|
||
|
|
die.Column = column;
|
||
|
|
die.X = point.X;
|
||
|
|
die.Y = point.Y;
|
||
|
|
die.Angle = point.Angle;
|
||
|
|
die.Status = defaultStatus;
|
||
|
|
return die;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static bool TryResolveArrayIndex(int rawIndex, int count, out int resolvedIndex)
|
||
|
|
{
|
||
|
|
resolvedIndex = -1;
|
||
|
|
if (count <= 0)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (rawIndex >= 1 && rawIndex <= count)
|
||
|
|
{
|
||
|
|
resolvedIndex = rawIndex - 1;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (rawIndex >= 0 && rawIndex < count)
|
||
|
|
{
|
||
|
|
resolvedIndex = rawIndex;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static int ResolveRecognitionConsumerCount(WaferDiePositionContext runtimeContext)
|
||
|
|
{
|
||
|
|
if (runtimeContext == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(runtimeContext));
|
||
|
|
}
|
||
|
|
|
||
|
|
int configuredConsumerCount = runtimeContext.ScanSettings == null
|
||
|
|
? DefaultRecognitionConsumerCount
|
||
|
|
: runtimeContext.ScanSettings.ConsumerCount;
|
||
|
|
int safeConsumerCount = Math.Max(1, configuredConsumerCount);
|
||
|
|
int plannedFrameCount = runtimeContext.ScanPlan == null ? 0 : runtimeContext.ScanPlan.PathPointCount;
|
||
|
|
if (plannedFrameCount <= 0)
|
||
|
|
{
|
||
|
|
return safeConsumerCount;
|
||
|
|
}
|
||
|
|
|
||
|
|
return Math.Min(plannedFrameCount, safeConsumerCount);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static int ResolveFrameChannelCapacity(WaferDiePositionContext runtimeContext, int consumerCount)
|
||
|
|
{
|
||
|
|
if (runtimeContext == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(runtimeContext));
|
||
|
|
}
|
||
|
|
|
||
|
|
int configuredCapacity = runtimeContext.ScanSettings == null
|
||
|
|
? DefaultFrameChannelCapacity
|
||
|
|
: runtimeContext.ScanSettings.FrameChannelCapacity;
|
||
|
|
int safeCapacity = configuredCapacity <= 0 ? DefaultFrameChannelCapacity : configuredCapacity;
|
||
|
|
return Math.Max(1, Math.Max(consumerCount, safeCapacity));
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void EnsureFrameResults(WaferDiePositionContext runtimeContext)
|
||
|
|
{
|
||
|
|
if (runtimeContext.FrameResults == null)
|
||
|
|
{
|
||
|
|
runtimeContext.FrameResults = new List<WaferRecognitionFrameResult>();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
runtimeContext.FrameResults.Clear();
|
||
|
|
}
|
||
|
|
|
||
|
|
private static WaferRecipe GetCurrentWaferRecipe(RecipeManager recipeManager)
|
||
|
|
{
|
||
|
|
if (recipeManager == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(recipeManager));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (recipeManager.CurrentWaferRecipe == null)
|
||
|
|
{
|
||
|
|
throw new LocalizedProcessException(
|
||
|
|
MessageKey.ProcessDiePositionRecipeNotLoaded,
|
||
|
|
Array.Empty<string>());
|
||
|
|
}
|
||
|
|
|
||
|
|
return recipeManager.CurrentWaferRecipe;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static WaferInfo GetWaferInfo(WaferRecipe waferRecipe)
|
||
|
|
{
|
||
|
|
if (waferRecipe == null || waferRecipe.WaferInfo == null)
|
||
|
|
{
|
||
|
|
throw new LocalizedProcessException(
|
||
|
|
MessageKey.ProcessDiePositionRecipeNotLoaded,
|
||
|
|
Array.Empty<string>());
|
||
|
|
}
|
||
|
|
|
||
|
|
WaferInfo waferInfo = waferRecipe.WaferInfo;
|
||
|
|
if (waferInfo.WaferRowNum <= 0 ||
|
||
|
|
waferInfo.WaferColNum <= 0 ||
|
||
|
|
waferInfo.DiePitchX == 0d ||
|
||
|
|
waferInfo.DiePitchY == 0d)
|
||
|
|
{
|
||
|
|
throw new LocalizedProcessException(
|
||
|
|
MessageKey.ProcessDiePositionWaferInfoMissing,
|
||
|
|
Array.Empty<string>());
|
||
|
|
}
|
||
|
|
|
||
|
|
return waferInfo;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static DiePositionProcessResult InitializeProcessResult(ProcessResultManager processResultManager)
|
||
|
|
{
|
||
|
|
if (processResultManager == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(processResultManager));
|
||
|
|
}
|
||
|
|
|
||
|
|
DiePositionProcessResult processResult = processResultManager.DiePositionResult;
|
||
|
|
processResult.IsSuccess = false;
|
||
|
|
processResult.RecipeName = null;
|
||
|
|
processResult.ErrorMessage = null;
|
||
|
|
processResult.ErrorMessageKey = MessageKey.None;
|
||
|
|
processResult.ErrorMessageArguments = Array.Empty<string>();
|
||
|
|
processResult.RowCount = 0;
|
||
|
|
processResult.ColumnCount = 0;
|
||
|
|
processResult.TheoryDieCount = 0;
|
||
|
|
processResult.ScanPointCount = 0;
|
||
|
|
processResult.RecognizedDieCount = 0;
|
||
|
|
processResult.NgDieCount = 0;
|
||
|
|
processResult.AverageSpacingX = 0;
|
||
|
|
processResult.AverageSpacingY = 0;
|
||
|
|
processResult.MapDies = new Die[0, 0];
|
||
|
|
processResult.PatchDies.Clear();
|
||
|
|
processResult.ExceptDies.Clear();
|
||
|
|
processResult.ScanPoints.Clear();
|
||
|
|
processResult.RecognizedDies.Clear();
|
||
|
|
return processResult;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void PopulateProcessResult(
|
||
|
|
DiePositionProcessResult processResult,
|
||
|
|
WaferDiePositionContext runtimeContext,
|
||
|
|
WaferScanPlan scanPlan)
|
||
|
|
{
|
||
|
|
List<Die> placeholderDies = CreatePlaceholderDies(runtimeContext);
|
||
|
|
|
||
|
|
processResult.RecipeName = runtimeContext.RecipeName;
|
||
|
|
processResult.RowCount = runtimeContext.RowCount;
|
||
|
|
processResult.ColumnCount = runtimeContext.ColumnCount;
|
||
|
|
processResult.TheoryDieCount = runtimeContext.RowCount * runtimeContext.ColumnCount;
|
||
|
|
processResult.ScanPointCount = scanPlan == null ? 0 : scanPlan.PathPointCount;
|
||
|
|
processResult.RecognizedDieCount = runtimeContext.RecognitionSnapshot == null
|
||
|
|
? placeholderDies.Count
|
||
|
|
: runtimeContext.RecognitionSnapshot.RowsReal.Length;
|
||
|
|
processResult.NgDieCount = runtimeContext.ExceptDies == null ? 0 : runtimeContext.ExceptDies.Count;
|
||
|
|
processResult.AverageSpacingX = runtimeContext.PitchX;
|
||
|
|
processResult.AverageSpacingY = runtimeContext.PitchY;
|
||
|
|
processResult.MapDies = runtimeContext.MapDies ?? new Die[0, 0];
|
||
|
|
processResult.PatchDies.Clear();
|
||
|
|
if (runtimeContext.PatchDies != null)
|
||
|
|
{
|
||
|
|
foreach (Die patchDie in runtimeContext.PatchDies)
|
||
|
|
{
|
||
|
|
processResult.PatchDies.Add(patchDie);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
processResult.ExceptDies.Clear();
|
||
|
|
if (runtimeContext.ExceptDies != null)
|
||
|
|
{
|
||
|
|
foreach (Die exceptDie in runtimeContext.ExceptDies)
|
||
|
|
{
|
||
|
|
processResult.ExceptDies.Add(exceptDie);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (scanPlan != null && scanPlan.PathPoints != null)
|
||
|
|
{
|
||
|
|
foreach (Point scanPoint in scanPlan.PathPoints)
|
||
|
|
{
|
||
|
|
processResult.ScanPoints.Add(scanPoint);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach (Die die in placeholderDies)
|
||
|
|
{
|
||
|
|
processResult.RecognizedDies.Add(die);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (runtimeContext.MapDies != null && runtimeContext.MapDies.Length > 0)
|
||
|
|
{
|
||
|
|
processResult.RecognizedDies.Clear();
|
||
|
|
foreach (Die mapDie in runtimeContext.MapDies)
|
||
|
|
{
|
||
|
|
if (mapDie != null)
|
||
|
|
{
|
||
|
|
processResult.RecognizedDies.Add(mapDie);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static List<Die> CreatePlaceholderDies(WaferDiePositionContext runtimeContext)
|
||
|
|
{
|
||
|
|
List<Die> dies = new List<Die>();
|
||
|
|
if (runtimeContext == null || runtimeContext.RowCount <= 0 || runtimeContext.ColumnCount <= 0)
|
||
|
|
{
|
||
|
|
return dies;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (int rowIndex = 0; rowIndex < runtimeContext.RowCount; rowIndex++)
|
||
|
|
{
|
||
|
|
for (int columnIndex = 0; columnIndex < runtimeContext.ColumnCount; columnIndex++)
|
||
|
|
{
|
||
|
|
Die die = new Die();
|
||
|
|
die.Row = rowIndex + 1;
|
||
|
|
die.Column = columnIndex + 1;
|
||
|
|
die.X = columnIndex * runtimeContext.PitchX;
|
||
|
|
die.Y = rowIndex * runtimeContext.PitchY;
|
||
|
|
die.Status = DieStatus.Normal;
|
||
|
|
dies.Add(die);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return dies;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static async Task CheckCancellationAndPauseAsync(ActivityControl activityControl)
|
||
|
|
{
|
||
|
|
activityControl.ThrowIfCancellationRequested();
|
||
|
|
await activityControl.CheckPauseAsync().ConfigureAwait(false);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void WriteResultToContext(
|
||
|
|
WorkflowContext context,
|
||
|
|
ProcessResultManager processResultManager,
|
||
|
|
DiePositionProcessResult processResult)
|
||
|
|
{
|
||
|
|
context.SetData(WorkflowContextKeys.DiePositionResult, processResult);
|
||
|
|
processResultManager.SaveDiePositionResult();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|