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 _frameWriter; private ExceptionDispatchInfo _failureDispatchInfo; public PipelineFailureCoordinator(CancellationTokenSource pipelineCancellationTokenSource, ChannelWriter 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(WorkflowContextKeys.RecipeManager); ProcessResultManager processResultManager = context.GetData(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(); 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(); 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(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 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 frameChannel = CreateFrameChannel(runtimeContext, consumerCount); PipelineFailureCoordinator failureCoordinator = new PipelineFailureCoordinator(pipelineCancellationTokenSource, frameChannel.Writer); List frameResults = new List(); object frameResultsSyncRoot = new object(); List 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 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(options); } private List StartRecognitionConsumers( ChannelReader frameReader, WaferDiePositionContext runtimeContext, List frameResults, object frameResultsSyncRoot, int consumerCount, PipelineFailureCoordinator failureCoordinator) { if (frameReader == null) { throw new ArgumentNullException(nameof(frameReader)); } if (runtimeContext == null) { throw new ArgumentNullException(nameof(runtimeContext)); } List consumerTasks = new List(); 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 frameResults, WaferScanExecutionSummary summary) { if (runtimeContext == null) { throw new ArgumentNullException(nameof(runtimeContext)); } if (frameResults == null) { throw new ArgumentNullException(nameof(frameResults)); } List 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 frameReader, WaferDiePositionContext runtimeContext, List 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 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 PatchPos, List 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(); runtimeContext.ExceptDies = new List(); } 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(), recognitionSnapshot.ColumnsReal ?? Array.Empty(), recognitionSnapshot.Angles ?? Array.Empty(), recognitionSnapshot.OverlapRows ?? Array.Empty(), recognitionSnapshot.OverlapColumns ?? Array.Empty(), recognitionSnapshot.OverlapAngles ?? Array.Empty()); 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> rowPair in map) { if (rowPair.Value == null) { continue; } foreach (KeyValuePair 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 ConvertPatchDies(List patchPoints, int rowCount, int columnCount) { return ConvertPointListToDies(patchPoints, rowCount, columnCount, DieStatus.Normal); } private static List ConvertExceptDies(List exceptPoints, int rowCount, int columnCount) { return ConvertPointListToDies(exceptPoints, rowCount, columnCount, DieStatus.Ng); } private static List ConvertPointListToDies(List points, int rowCount, int columnCount, DieStatus defaultStatus) { List dies = new List(); 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(); 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()); } return recipeManager.CurrentWaferRecipe; } private static WaferInfo GetWaferInfo(WaferRecipe waferRecipe) { if (waferRecipe == null || waferRecipe.WaferInfo == null) { throw new LocalizedProcessException( MessageKey.ProcessDiePositionRecipeNotLoaded, Array.Empty()); } WaferInfo waferInfo = waferRecipe.WaferInfo; if (waferInfo.WaferRowNum <= 0 || waferInfo.WaferColNum <= 0 || waferInfo.DiePitchX == 0d || waferInfo.DiePitchY == 0d) { throw new LocalizedProcessException( MessageKey.ProcessDiePositionWaferInfoMissing, Array.Empty()); } 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(); 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 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 CreatePlaceholderDies(WaferDiePositionContext runtimeContext) { List dies = new List(); 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(); } } }