添加 MX-PD-盘古 项目文件

将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
This commit is contained in:
Shi.Ji
2026-05-18 11:43:09 +08:00
parent 03632a379d
commit e31d3560bb
739 changed files with 99783 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
using MW.WorkFlow;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MainShell.Process
{
public class ChipStraighteningActivity: ActivityAbstractBase
{
public ChipStraighteningActivity(string name) : base(name)
{
}
protected override async Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
{
await Task.Delay(2000, activityControl.CancellationToken);
return ActivityResult.Success;
}
protected override void PrepareExecute(WorkflowContext context, ActivityControl activityControl)
{
// Custom preparation logic before execution
}
protected override void AfterExecute(WorkflowContext context, ActivityControl activityControl)
{
// Custom cleanup logic after execution
}
}
}

View File

@@ -0,0 +1,241 @@
using MainShell.Common;
using MainShell.Hardware;
using MainShell.Log;
using MainShell.ParaSetting.Model;
using MainShell.Vision;
using MaxwellFramework.Core.Interfaces;
using MwFramework.Device;
using MwFramework.ManagerService;
using System;
using System.Globalization;
using System.Reflection;
using System.Windows;
namespace MainShell.Process
{
public class DefaultWaferMachineAdapter : IWaferMachineAdapter
{
private readonly HardwareManager _hardwareManager;
private readonly IParamList _parameterList;
public DefaultWaferMachineAdapter(HardwareManager hardwareManager, IParameterManager parameterManager)
{
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
_parameterList = parameterManager as IParamList;
}
public CameraType GetCameraSource(WaferDiePositionContext context)
{
return CameraType.TopPositionCamera;
}
public MwCamera GetCamera(WaferDiePositionContext context)
{
return ResolveCamera(GetCameraSource(context));
}
public Point GetCameraFieldOfView(WaferDiePositionContext context)
{
DeviceFoundationSetting deviceFoundationSetting = GetDeviceFoundationSetting();
CameraFovSettingItem cameraFovSettingItem = GetCameraFovSettingItem(deviceFoundationSetting, GetCameraSource(context));
if (cameraFovSettingItem == null || cameraFovSettingItem.FovX <= 0d || cameraFovSettingItem.FovY <= 0d)
{
LogManager.LogProcessInfo("晶圆定位DefaultWaferMachineAdapter 正在使用相机视野默认回退值。");
return new Point(1.0d, 1.0d);
}
return new Point(cameraFovSettingItem.FovX, cameraFovSettingItem.FovY);
}
public double GetExposureTimeMilliseconds(WaferDiePositionContext context)
{
MwCamera camera = GetCamera(context);
if (camera == null)
{
return 0d;
}
try
{
double exposureTime = 0d;
DriverLibResult result = camera.GetExposureTime(ref exposureTime);
if (result != DriverLibResult.DriverLibNoError || double.IsNaN(exposureTime) || double.IsInfinity(exposureTime) || exposureTime < 0d)
{
return 0d;
}
return exposureTime;
}
catch (Exception)
{
return 0d;
}
}
public CameraCaptureOptions CreateSoftTriggerCaptureOptions(WaferDiePositionContext context)
{
CameraCaptureOptions options = CameraCaptureOptions.CreateSoftTrigger(5000);
options.TriggerDelay = 20d;
return options;
}
public Point AdjustPointWithinSoftLimit(Point candidatePoint, WaferDiePositionContext context, double offsetMm, out bool adjusted, out string reason)
{
IAxis axisX = _hardwareManager.Axis_PHS_X1;
IAxis axisY = _hardwareManager.Axis_Stage_Y3;
double adjustedX = candidatePoint.X;
double adjustedY = candidatePoint.Y;
bool adjustedXFlag = TryClampByAxisSoftLimit(axisX, adjustedX, offsetMm, out adjustedX, out string adjustedXReason);
bool adjustedYFlag = TryClampByAxisSoftLimit(axisY, adjustedY, offsetMm, out adjustedY, out string adjustedYReason);
adjusted = adjustedXFlag || adjustedYFlag;
if (!adjusted)
{
reason = string.Empty;
return candidatePoint;
}
reason = BuildAdjustmentReason(adjustedXReason, adjustedYReason);
return new Point(adjustedX, adjustedY);
}
private DeviceFoundationSetting GetDeviceFoundationSetting()
{
return _parameterList == null ? null : _parameterList.GetParameter<DeviceFoundationSetting>();
}
private MwCamera ResolveCamera(CameraType cameraType)
{
MwCameraExtend cameraExtend = GetCameraExtend(cameraType);
if (cameraExtend == null)
{
return null;
}
return cameraExtend.Camera;
}
private MwCameraExtend GetCameraExtend(CameraType cameraType)
{
return _hardwareManager.GetCamera(cameraType);
}
private static CameraFovSettingItem GetCameraFovSettingItem(DeviceFoundationSetting deviceFoundationSetting, CameraType cameraType)
{
if (deviceFoundationSetting == null || deviceFoundationSetting.CameraSettingItem == null)
{
return null;
}
deviceFoundationSetting.CameraSettingItem.EnsureDefaultCameras();
switch (cameraType)
{
case CameraType.TopWideCamera:
return deviceFoundationSetting.CameraSettingItem.DownCamera;
case CameraType.MapCamera:
return deviceFoundationSetting.CameraSettingItem.MapCamera;
case CameraType.TopPositionCamera:
default:
return deviceFoundationSetting.CameraSettingItem.UpCamera;
}
}
private static bool TryClampByAxisSoftLimit(IAxis axis, double candidateValue, double offsetMm, out double adjustedValue, out string reason)
{
adjustedValue = candidateValue;
reason = string.Empty;
if (axis == null)
{
return false;
}
bool hasNegativeLimit = TryGetAxisLimit(axis, true, out double negativeLimit);
bool hasPositiveLimit = TryGetAxisLimit(axis, false, out double positiveLimit);
if (!hasNegativeLimit && !hasPositiveLimit)
{
return false;
}
double minLimit = hasNegativeLimit && hasPositiveLimit ? Math.Min(negativeLimit, positiveLimit) : negativeLimit;
double maxLimit = hasNegativeLimit && hasPositiveLimit ? Math.Max(negativeLimit, positiveLimit) : positiveLimit;
double minAllowed = hasNegativeLimit ? minLimit + Math.Max(0d, offsetMm) : double.NegativeInfinity;
double maxAllowed = hasPositiveLimit ? maxLimit - Math.Max(0d, offsetMm) : double.PositiveInfinity;
if (candidateValue < minAllowed)
{
adjustedValue = minAllowed;
reason = string.Format(
CultureInfo.InvariantCulture,
"{0}: clamp to negative soft limit {1:F3} with offset {2:F3}.",
axis.Name,
adjustedValue,
offsetMm);
return true;
}
if (candidateValue > maxAllowed)
{
adjustedValue = maxAllowed;
reason = string.Format(
CultureInfo.InvariantCulture,
"{0}: clamp to positive soft limit {1:F3} with offset {2:F3}.",
axis.Name,
adjustedValue,
offsetMm);
return true;
}
return false;
}
private static bool TryGetAxisLimit(IAxis axis, bool isNegativeLimit, out double limitValue)
{
limitValue = 0d;
if (axis == null)
{
return false;
}
try
{
double axisLimit = 0d;
MotionErrorCode errorCode = isNegativeLimit ? axis.GetSoftMel(ref axisLimit) : axis.GetSoftPel(ref axisLimit);
if (errorCode != MotionErrorCode.NoError)
{
return false;
}
limitValue = axisLimit;
if (double.IsNaN(limitValue) || double.IsInfinity(limitValue))
{
return false;
}
return true;
}
catch (Exception)
{
}
return false;
}
private static string BuildAdjustmentReason(string adjustedXReason, string adjustedYReason)
{
if (string.IsNullOrWhiteSpace(adjustedXReason))
{
return adjustedYReason ?? string.Empty;
}
if (string.IsNullOrWhiteSpace(adjustedYReason))
{
return adjustedXReason;
}
return adjustedXReason + " | " + adjustedYReason;
}
}
}

View File

@@ -0,0 +1,52 @@
using MW.WorkFlow;
using MainShell.Common;
using MainShell.Log;
using MainShell.ProcessResult;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace MainShell.Process
{
public class DiePositionActivity : ActivityAbstractBase
{
private readonly DiePositionService _diePositionService;
public DiePositionActivity(string name, DiePositionService diePositionService)
: base(name)
{
_diePositionService = diePositionService ?? throw new ArgumentNullException(nameof(diePositionService));
}
protected override async Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
{
await _diePositionService.ExecuteAsync(context, activityControl).ConfigureAwait(false);
DiePositionProcessResult result = context.GetData<DiePositionProcessResult>(WorkflowContextKeys.DiePositionResult);
if (result == null || !result.IsSuccess)
{
MessageKey failureMessageKey = result != null && result.ErrorMessageKey != MessageKey.None
? result.ErrorMessageKey
: MessageKey.ProcessDiePositionFailedWithReason;
object[] failureMessageArguments = result != null
? ConvertToObjectArray(result.ErrorMessageArguments)
: new object[] { LanguageResourceHelper.GetString(MessageKey.CommonUnknownError) };
string errorMessage = result != null && !string.IsNullOrWhiteSpace(result.ErrorMessage)
? result.ErrorMessage
: LanguageResourceHelper.Format(failureMessageKey, failureMessageArguments);
LogManager.LogProcessError(string.Format("晶圆定位活动执行失败:{0}", errorMessage));
return Fail(context, failureMessageKey, failureMessageArguments);
}
return ActivityResult.Success;
}
private static object[] ConvertToObjectArray(string[] arguments)
{
return arguments == null ? Array.Empty<object>() : arguments.Cast<object>().ToArray();
}
}
}

View File

@@ -0,0 +1,983 @@
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();
}
}
}

View File

@@ -0,0 +1,11 @@
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace MainShell.Process
{
public interface IWaferCaptureExecutor
{
Task<WaferScanExecutionSummary> ExecuteAsync(WaferDiePositionContext context, ChannelWriter<WaferCaptureFrame> frameWriter, CancellationToken cancellationToken);
}
}

View File

@@ -0,0 +1,11 @@
using MainShell.ProcessResult;
namespace MainShell.Process
{
public interface IWaferDiePositionResultService
{
void ApplySnapshot(WaferDiePositionContext context, WaferRecognitionSnapshot snapshot);
void WriteProcessResult(WaferDiePositionContext context, DiePositionProcessResult processResult);
}
}

View File

@@ -0,0 +1,13 @@
using MainShell.Vision;
using System.Threading;
using System.Threading.Tasks;
namespace MainShell.Process
{
public interface IWaferDieRecognizer
{
Task<WaferRecognitionFrameResult> RecognizeAsync(WaferCaptureFrame frame, WaferDiePositionContext context, CancellationToken cancellationToken);
FindDieImageRequest BuildRequest(WaferCaptureFrame frame, WaferDiePositionContext context);
}
}

View File

@@ -0,0 +1,22 @@
using MainShell.Common;
using MainShell.Vision;
using MwFramework.Device;
using System.Windows;
namespace MainShell.Process
{
public interface IWaferMachineAdapter
{
CameraType GetCameraSource(WaferDiePositionContext context);
MwCamera GetCamera(WaferDiePositionContext context);
Point GetCameraFieldOfView(WaferDiePositionContext context);
double GetExposureTimeMilliseconds(WaferDiePositionContext context);
CameraCaptureOptions CreateSoftTriggerCaptureOptions(WaferDiePositionContext context);
Point AdjustPointWithinSoftLimit(Point candidatePoint, WaferDiePositionContext context, double offsetMm, out bool adjusted, out string reason);
}
}

View File

@@ -0,0 +1,11 @@
namespace MainShell.Process
{
public interface IWaferRecognitionResultCollector
{
void Add(WaferRecognitionFrameResult frameResult);
WaferRecognitionSnapshot CreateSnapshot();
void Reset();
}
}

View File

@@ -0,0 +1,7 @@
namespace MainShell.Process
{
public interface IWaferScanPlanner
{
WaferScanPlan CreatePlan(WaferDiePositionContext context);
}
}

View File

@@ -0,0 +1,510 @@
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 轴。");
}
}
}
}

View File

@@ -0,0 +1,27 @@
using MainShell.Models;
using System;
using System.Windows;
namespace MainShell.Process
{
public class WaferCaptureFrame
{
public long SequenceId { get; set; }
public Point TriggerPoint { get; set; }
public string FrameId { get; set; }
public DateTime TriggerTime { get; set; }
public DateTime ReceiveTime { get; set; }
public bool IsLostFrame { get; set; }
public string ImageFilePath { get; set; }
public MxImage Image { get; set; }
public string CaptureErrorMessage { get; set; }
}
}

View File

@@ -0,0 +1,71 @@
using MainShell.Recipe.Models;
using MainShell.Models.Wafer;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows;
namespace MainShell.Process
{
public class WaferDiePositionContext
{
public WaferDiePositionContext()
{
CaptureFrames = new List<WaferCaptureFrame>();
FrameResults = new List<WaferRecognitionFrameResult>();
MapDies = new Die[0, 0];
PatchDies = new List<Die>();
ExceptDies = new List<Die>();
}
public string WorkflowName { get; set; }
public string RecipeName { get; set; }
public WaferRecipe WaferRecipe { get; set; }
public WaferScanSettings ScanSettings { get; set; }
public int RowCount { get; set; }
public int ColumnCount { get; set; }
public double PitchX { get; set; }
public double PitchY { get; set; }
public WaferScanArea ScanArea { get; set; }
public CancellationToken CancellationToken { get; set; }
public List<WaferCaptureFrame> CaptureFrames { get; set; }
public List<WaferRecognitionFrameResult> FrameResults { get; set; }
public WaferRecognitionSnapshot RecognitionSnapshot { get; set; }
public WaferScanExecutionSummary ExecutionSummary { get; set; }
public bool IsManualMode { get; set; }
public bool IsVisionCheckMode { get; set; }
public string ErrorMessage { get; set; }
public string OutputDirectory { get; set; }
public WaferScanPlan ScanPlan { get; set; }
public double MapAngleThreshold { get; set; }
public double MapRotateAngle { get; set; }
public double MapDistanceThreshold { get; set; }
public Die[,] MapDies { get; set; }
public List<Die> PatchDies { get; set; }
public List<Die> ExceptDies { get; set; }
}
}

View File

@@ -0,0 +1,26 @@
using MainShell.ProcessResult;
using System;
namespace MainShell.Process
{
public class WaferDiePositionResultService : IWaferDiePositionResultService
{
public void ApplySnapshot(WaferDiePositionContext context, WaferRecognitionSnapshot snapshot)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
context.RecognitionSnapshot = snapshot;
}
public void WriteProcessResult(WaferDiePositionContext context, DiePositionProcessResult processResult)
{
if (processResult == null)
{
throw new ArgumentNullException(nameof(processResult));
}
}
}
}

View File

@@ -0,0 +1,104 @@
using MainShell.Vision;
using SemiconductorVisionAlgorithm.SemiParams;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MainShell.Process
{
public class WaferDieRecognizer : IWaferDieRecognizer
{
private readonly IFindDieService _findDieService;
public WaferDieRecognizer(IFindDieService findDieService)
{
_findDieService = findDieService ?? throw new ArgumentNullException(nameof(findDieService));
}
public FindDieImageRequest BuildRequest(WaferCaptureFrame frame, WaferDiePositionContext context)
{
FindDieImageRequest request = new FindDieImageRequest();
FindDieParameters parameters = new FindDieParameters();
parameters.CurrentRuler = frame == null
? new Point(0.0d, 0.0d)
: new Point(frame.TriggerPoint.X, frame.TriggerPoint.Y);
parameters.OverlapX = context != null && context.ScanSettings != null ? context.ScanSettings.OverlapRateX : 0d;
parameters.OverlapY = context != null && context.ScanSettings != null ? context.ScanSettings.OverlapRateY : 0d;
parameters.DieWidth = context != null && context.WaferRecipe != null && context.WaferRecipe.WaferInfo != null
? context.WaferRecipe.WaferInfo.DieSizeX
: 0d;
parameters.DieHeight = context != null && context.WaferRecipe != null && context.WaferRecipe.WaferInfo != null
? context.WaferRecipe.WaferInfo.DieSizeY
: 0d;
parameters.ScoreThreshold = 0.8d;
parameters.TemplatePath = string.Empty;
parameters.SerialNumber = string.Empty;
request.Parameters = parameters;
request.TimeoutMilliseconds = 3000;
return request;
}
public async Task<WaferRecognitionFrameResult> RecognizeAsync(WaferCaptureFrame frame, WaferDiePositionContext context, CancellationToken cancellationToken)
{
if (frame == null)
{
throw new ArgumentNullException(nameof(frame));
}
WaferRecognitionFrameResult frameResult = new WaferRecognitionFrameResult();
frameResult.SequenceId = frame.SequenceId;
if (frame.Image == null)
{
frameResult.IsSuccess = false;
frameResult.ErrorMessage = "DiePosition: capture frame image is null.";
return frameResult;
}
FindDieImageRequest request = BuildRequest(frame, context);
VisionProcessResult<FindDiesResult> result = await _findDieService.ProcessMultipleImageAsync(frame.Image, request, cancellationToken).ConfigureAwait(false);
if (result == null || !result.Succeeded || result.Data == null)
{
frameResult.IsSuccess = false;
frameResult.ErrorMessage = result == null ? "DiePosition: recognition returned null result." : result.Message;
return frameResult;
}
List<double> rowsReal = new List<double>();
List<double> columnsReal = new List<double>();
List<double> angles = new List<double>();
List<double> overlapRows = new List<double>();
List<double> overlapColumns = new List<double>();
List<double> overlapAngles = new List<double>();
foreach (FindDieResult item in result.Data.Items)
{
if (item.IsOverlap)
{
overlapRows.Add(item.CenterY);
overlapColumns.Add(item.CenterX);
overlapAngles.Add(item.Angle);
}
else
{
rowsReal.Add(item.CenterY);
columnsReal.Add(item.CenterX);
angles.Add(item.Angle);
}
}
frameResult.IsSuccess = true;
frameResult.ErrorMessage = null;
frameResult.RowsReal = rowsReal.ToArray();
frameResult.ColumnsReal = columnsReal.ToArray();
frameResult.Angles = angles.ToArray();
frameResult.OverlapRows = overlapRows.ToArray();
frameResult.OverlapColumns = overlapColumns.ToArray();
frameResult.OverlapAngles = overlapAngles.ToArray();
frameResult.UniqueCount = frameResult.RowsReal.Length;
frameResult.OverlapCount = frameResult.OverlapRows.Length;
return frameResult;
}
}
}

View File

@@ -0,0 +1,15 @@
using System.Windows;
namespace MainShell.Process
{
public class WaferPlannerPointAdjustment
{
public Point OriginalPoint { get; set; }
public Point AdjustedPoint { get; set; }
public bool WasAdjusted { get; set; }
public string Reason { get; set; }
}
}

View File

@@ -0,0 +1,39 @@
using System;
namespace MainShell.Process
{
public class WaferRecognitionFrameResult
{
public WaferRecognitionFrameResult()
{
RowsReal = Array.Empty<double>();
ColumnsReal = Array.Empty<double>();
Angles = Array.Empty<double>();
OverlapRows = Array.Empty<double>();
OverlapColumns = Array.Empty<double>();
OverlapAngles = Array.Empty<double>();
}
public long SequenceId { get; set; }
public bool IsSuccess { get; set; }
public string ErrorMessage { get; set; }
public double[] RowsReal { get; set; }
public double[] ColumnsReal { get; set; }
public double[] Angles { get; set; }
public double[] OverlapRows { get; set; }
public double[] OverlapColumns { get; set; }
public double[] OverlapAngles { get; set; }
public int UniqueCount { get; set; }
public int OverlapCount { get; set; }
}
}

View File

@@ -0,0 +1,83 @@
using System.Collections.Generic;
namespace MainShell.Process
{
public class WaferRecognitionResultCollector : IWaferRecognitionResultCollector
{
private readonly object _syncRoot = new object();
private readonly List<double> _rowsReal = new List<double>();
private readonly List<double> _columnsReal = new List<double>();
private readonly List<double> _angles = new List<double>();
private readonly List<double> _overlapRows = new List<double>();
private readonly List<double> _overlapColumns = new List<double>();
private readonly List<double> _overlapAngles = new List<double>();
private int _totalFrameCount;
private int _successFrameCount;
private int _failedFrameCount;
private int _lostFrameCount;
public void Add(WaferRecognitionFrameResult frameResult)
{
lock (_syncRoot)
{
_totalFrameCount++;
if (frameResult == null)
{
_failedFrameCount++;
return;
}
if (!frameResult.IsSuccess)
{
_failedFrameCount++;
return;
}
_successFrameCount++;
_rowsReal.AddRange(frameResult.RowsReal);
_columnsReal.AddRange(frameResult.ColumnsReal);
_angles.AddRange(frameResult.Angles);
_overlapRows.AddRange(frameResult.OverlapRows);
_overlapColumns.AddRange(frameResult.OverlapColumns);
_overlapAngles.AddRange(frameResult.OverlapAngles);
}
}
public WaferRecognitionSnapshot CreateSnapshot()
{
lock (_syncRoot)
{
WaferRecognitionSnapshot snapshot = new WaferRecognitionSnapshot();
snapshot.RowsReal = _rowsReal.ToArray();
snapshot.ColumnsReal = _columnsReal.ToArray();
snapshot.Angles = _angles.ToArray();
snapshot.OverlapRows = _overlapRows.ToArray();
snapshot.OverlapColumns = _overlapColumns.ToArray();
snapshot.OverlapAngles = _overlapAngles.ToArray();
snapshot.TotalFrameCount = _totalFrameCount;
snapshot.SuccessFrameCount = _successFrameCount;
snapshot.FailedFrameCount = _failedFrameCount;
snapshot.LostFrameCount = _lostFrameCount;
return snapshot;
}
}
public void Reset()
{
lock (_syncRoot)
{
_rowsReal.Clear();
_columnsReal.Clear();
_angles.Clear();
_overlapRows.Clear();
_overlapColumns.Clear();
_overlapAngles.Clear();
_totalFrameCount = 0;
_successFrameCount = 0;
_failedFrameCount = 0;
_lostFrameCount = 0;
}
}
}
}

View File

@@ -0,0 +1,37 @@
using System;
namespace MainShell.Process
{
public class WaferRecognitionSnapshot
{
public WaferRecognitionSnapshot()
{
RowsReal = Array.Empty<double>();
ColumnsReal = Array.Empty<double>();
Angles = Array.Empty<double>();
OverlapRows = Array.Empty<double>();
OverlapColumns = Array.Empty<double>();
OverlapAngles = Array.Empty<double>();
}
public double[] RowsReal { get; set; }
public double[] ColumnsReal { get; set; }
public double[] Angles { get; set; }
public double[] OverlapRows { get; set; }
public double[] OverlapColumns { get; set; }
public double[] OverlapAngles { get; set; }
public int TotalFrameCount { get; set; }
public int SuccessFrameCount { get; set; }
public int FailedFrameCount { get; set; }
public int LostFrameCount { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
using System.Windows;
namespace MainShell.Process
{
public class WaferScanArea
{
public Point StartPoint { get; set; }
public Point EndPoint { get; set; }
public double MinX { get; set; }
public double MaxX { get; set; }
public double MinY { get; set; }
public double MaxY { get; set; }
public double WaferWidth { get; set; }
public double WaferHeight { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
namespace MainShell.Process
{
public class WaferScanExecutionSummary
{
public int PlannedPointCount { get; set; }
public int ProducedFrameCount { get; set; }
public int ConsumedFrameCount { get; set; }
public int RecognitionSuccessCount { get; set; }
public int RecognitionFailureCount { get; set; }
public string LastErrorMessage { get; set; }
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Windows;
namespace MainShell.Process
{
public class WaferScanPlan
{
public WaferScanPlan()
{
PathPoints = Array.Empty<Point>();
RawPathPoints = Array.Empty<Point>();
FinalPathPoints = Array.Empty<Point>();
Adjustments = Array.Empty<WaferPlannerPointAdjustment>();
}
public Point StartPoint { get; set; }
public Point EndPoint { get; set; }
public IReadOnlyList<Point> PathPoints { get; set; }
public IReadOnlyList<Point> RawPathPoints { get; set; }
public IReadOnlyList<Point> FinalPathPoints { get; set; }
public WaferScanArea ScanArea { get; set; }
public double StepX { get; set; }
public double StepY { get; set; }
public double OverlapX { get; set; }
public double OverlapY { get; set; }
public IReadOnlyList<WaferPlannerPointAdjustment> Adjustments { get; set; }
public string PathGenerationMessage { get; set; }
public int ScanRowCount { get; set; }
public int ScanColumnCount { get; set; }
public int SoftLimitAdjustedCount { get; set; }
public int AddedCoveragePointCount { get; set; }
public int PathPointCount
{
get
{
IReadOnlyList<Point> effectivePathPoints = FinalPathPoints ?? PathPoints;
return effectivePathPoints == null ? 0 : effectivePathPoints.Count;
}
}
}
}

View File

@@ -0,0 +1,190 @@
using MaxwellFramework.Core.Attributes;
using System;
using MainShell.Common;
using MainShell.Recipe.Models;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows;
namespace MainShell.Process
{
[Singleton]
public class WaferScanPlanner : IWaferScanPlanner
{
private readonly IWaferMachineAdapter _waferMachineAdapter;
public WaferScanPlanner(IWaferMachineAdapter waferMachineAdapter)
{
_waferMachineAdapter = waferMachineAdapter ?? throw new ArgumentNullException(nameof(waferMachineAdapter));
}
public WaferScanPlan CreatePlan(WaferDiePositionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
WaferScanSettings scanSettings = context.ScanSettings ?? throw new InvalidOperationException("DiePosition: scan settings are required for scan planning.");
Point cameraFieldOfView = _waferMachineAdapter.GetCameraFieldOfView(context);
ValidateCameraFieldOfView(cameraFieldOfView);
WaferScanArea scanArea = BuildScanArea(context, scanSettings, cameraFieldOfView);
double stepX = CalculateStep(cameraFieldOfView.X, scanSettings.OverlapRateX);
double stepY = CalculateStep(cameraFieldOfView.Y, scanSettings.OverlapRateY);
int scanColumnCount = CalculateScanCount(scanArea.WaferWidth, cameraFieldOfView.X, stepX);
int scanRowCount = CalculateScanCount(scanArea.WaferHeight, cameraFieldOfView.Y, stepY);
List<Point> rawPathPoints = BuildRawPathPoints(scanArea, scanSettings, stepX, stepY, scanColumnCount, scanRowCount);
List<WaferPlannerPointAdjustment> adjustments = new List<WaferPlannerPointAdjustment>();
List<Point> finalPathPoints = BuildAdjustedPathPoints(rawPathPoints, context, scanSettings, adjustments);
Point startPoint = finalPathPoints.Count > 0 ? finalPathPoints[0] : scanArea.StartPoint;
Point endPoint = finalPathPoints.Count > 0 ? finalPathPoints[finalPathPoints.Count - 1] : scanArea.EndPoint;
WaferScanPlan scanPlan = new WaferScanPlan();
scanPlan.ScanRowCount = scanRowCount;
scanPlan.ScanColumnCount = scanColumnCount;
scanPlan.PathPoints = finalPathPoints;
scanPlan.RawPathPoints = rawPathPoints;
scanPlan.FinalPathPoints = finalPathPoints;
scanPlan.ScanArea = scanArea;
scanPlan.StartPoint = startPoint;
scanPlan.EndPoint = endPoint;
scanPlan.StepX = stepX;
scanPlan.StepY = stepY;
scanPlan.OverlapX = NormalizeOverlap(scanSettings.OverlapRateX);
scanPlan.OverlapY = NormalizeOverlap(scanSettings.OverlapRateY);
scanPlan.Adjustments = adjustments;
scanPlan.PathGenerationMessage = string.Format(
CultureInfo.InvariantCulture,
"DiePosition: generated serpentine scan plan with {0} points ({1}x{2}), stepX={3:F3}, stepY={4:F3}, adjusted={5}.",
scanPlan.PathPointCount,
scanColumnCount,
scanRowCount,
stepX,
stepY,
adjustments.Count(x => x.WasAdjusted));
scanPlan.SoftLimitAdjustedCount = adjustments.Count(x => x.WasAdjusted);
scanPlan.AddedCoveragePointCount = 0;
context.ScanArea = scanArea;
return scanPlan;
}
private static void ValidateCameraFieldOfView(Point cameraFieldOfView)
{
if (cameraFieldOfView.X <= 0d || cameraFieldOfView.Y <= 0d)
{
throw new InvalidOperationException("DiePosition: camera field of view must be greater than zero.");
}
}
private static WaferScanArea BuildScanArea(WaferDiePositionContext context, WaferScanSettings scanSettings, Point cameraFieldOfView)
{
double waferWidth = context.WaferRecipe != null && context.WaferRecipe.WaferInfo != null ? context.WaferRecipe.WaferInfo.WaferWidth : 0d;
double waferHeight = context.WaferRecipe != null && context.WaferRecipe.WaferInfo != null ? context.WaferRecipe.WaferInfo.WaferHeight : 0d;
double effectiveWaferWidth = Math.Max(cameraFieldOfView.X, waferWidth);
double effectiveWaferHeight = Math.Max(cameraFieldOfView.Y, waferHeight);
double endX = scanSettings.StartPointX + ResolveDirectionSign(scanSettings.ScanDirectionX) * Math.Max(0d, effectiveWaferWidth - cameraFieldOfView.X);
double endY = scanSettings.StartPointY + ResolveDirectionSign(scanSettings.ScanDirectionY) * Math.Max(0d, effectiveWaferHeight - cameraFieldOfView.Y);
WaferScanArea scanArea = new WaferScanArea();
scanArea.StartPoint = new Point(scanSettings.StartPointX, scanSettings.StartPointY);
scanArea.EndPoint = new Point(endX, endY);
scanArea.MinX = Math.Min(scanArea.StartPoint.X, scanArea.EndPoint.X);
scanArea.MaxX = Math.Max(scanArea.StartPoint.X, scanArea.EndPoint.X);
scanArea.MinY = Math.Min(scanArea.StartPoint.Y, scanArea.EndPoint.Y);
scanArea.MaxY = Math.Max(scanArea.StartPoint.Y, scanArea.EndPoint.Y);
scanArea.WaferWidth = effectiveWaferWidth;
scanArea.WaferHeight = effectiveWaferHeight;
return scanArea;
}
private static double CalculateStep(double fov, double overlapRate)
{
double normalizedOverlap = NormalizeOverlap(overlapRate);
double step = fov * (1d - normalizedOverlap);
if (step <= 0d)
{
return fov;
}
return step;
}
private static double NormalizeOverlap(double overlapRate)
{
if (overlapRate < 0d)
{
return 0d;
}
if (overlapRate >= 1d)
{
return 0.95d;
}
return overlapRate;
}
private static int CalculateScanCount(double waferSpan, double fov, double step)
{
if (waferSpan <= fov)
{
return 1;
}
return (int)Math.Ceiling((waferSpan - fov) / step) + 1;
}
private static List<Point> BuildRawPathPoints(WaferScanArea scanArea, WaferScanSettings scanSettings, double stepX, double stepY, int scanColumnCount, int scanRowCount)
{
List<Point> rawPathPoints = new List<Point>();
int directionX = ResolveDirectionSign(scanSettings.ScanDirectionX);
int directionY = ResolveDirectionSign(scanSettings.ScanDirectionY);
for (int rowIndex = 0; rowIndex < scanRowCount; rowIndex++)
{
double y = scanArea.StartPoint.Y + rowIndex * stepY * directionY;
bool reverseColumns = rowIndex % 2 == 1;
for (int columnIndex = 0; columnIndex < scanColumnCount; columnIndex++)
{
int serpentineColumnIndex = reverseColumns ? (scanColumnCount - 1 - columnIndex) : columnIndex;
double x = scanArea.StartPoint.X + serpentineColumnIndex * stepX * directionX;
rawPathPoints.Add(new Point(x, y));
}
}
return rawPathPoints;
}
private List<Point> BuildAdjustedPathPoints(List<Point> rawPathPoints, WaferDiePositionContext context, WaferScanSettings scanSettings, List<WaferPlannerPointAdjustment> adjustments)
{
List<Point> adjustedPathPoints = new List<Point>();
foreach (Point rawPathPoint in rawPathPoints)
{
Point adjustedPoint = _waferMachineAdapter.AdjustPointWithinSoftLimit(
rawPathPoint,
context,
scanSettings.SoftLimitOffsetMm,
out bool adjusted,
out string reason);
WaferPlannerPointAdjustment adjustment = new WaferPlannerPointAdjustment();
adjustment.OriginalPoint = rawPathPoint;
adjustment.AdjustedPoint = adjustedPoint;
adjustment.WasAdjusted = adjusted;
adjustment.Reason = reason;
adjustments.Add(adjustment);
adjustedPathPoints.Add(adjustedPoint);
}
return adjustedPathPoints;
}
private static int ResolveDirectionSign(TransPathDirection direction)
{
return direction == TransPathDirection.Negative ? -1 : 1;
}
}
}

View File

@@ -0,0 +1,32 @@
using MainShell.Common;
using MainShell.ProcessResult;
using MW.WorkFlow;
using System;
using System.Threading.Tasks;
namespace MainShell.Process
{
public class DieRecheckActivity : ActivityAbstractBase
{
private readonly DieRecheckService _dieRecheckService;
public DieRecheckActivity(string name, DieRecheckService dieRecheckService)
: base(name)
{
_dieRecheckService = dieRecheckService ?? throw new ArgumentNullException(nameof(dieRecheckService));
}
protected override async Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
{
await _dieRecheckService.ExecuteAsync(context, activityControl).ConfigureAwait(false);
DieRecheckProcessResult result = context.GetData<DieRecheckProcessResult>(WorkflowContextKeys.DieRecheckResult);
if (result == null || !result.IsSuccess)
{
return ActivityResult.Failure;
}
return ActivityResult.Success;
}
}
}

View File

@@ -0,0 +1,86 @@
using MainShell.Common;
using MainShell.ProcessResult;
using MW.WorkFlow;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MainShell.Process
{
public class DieRecheckService
{
public async Task ExecuteAsync(WorkflowContext context, ActivityControl activityControl)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (activityControl == null)
{
throw new ArgumentNullException(nameof(activityControl));
}
ProcessResultManager processResultManager = context.GetData<ProcessResultManager>(WorkflowContextKeys.ProcessResultManager);
DieRecheckProcessResult processResult = processResultManager.DieRecheckResult;
processResult.IsSuccess = false;
DieTransferPathPlan pathPlan;
if (context.TryGetData<DieTransferPathPlan>(WorkflowContextKeys.DieTransferPathPlan, out pathPlan) && pathPlan != null)
{
processResult.PointResults = ConvertToPointResults(pathPlan);
}
else if (processResult.PointResults == null)
{
processResult.PointResults = new List<DieRecheckPointResult>();
}
for (int index = 0; index < 5; index++)
{
activityControl.ThrowIfCancellationRequested();
await activityControl.CheckPauseAsync().ConfigureAwait(false);
await Task.Delay(50, activityControl.CancellationToken).ConfigureAwait(false);
}
processResult.IsSuccess = true;
processResultManager.SaveDieRecheckResult();
context.SetData(WorkflowContextKeys.DieRecheckResult, processResult);
}
private static List<DieRecheckPointResult> ConvertToPointResults(DieTransferPathPlan pathPlan)
{
List<DieRecheckPointResult> pointResults = new List<DieRecheckPointResult>();
if (pathPlan == null || pathPlan.Steps == null)
{
return pointResults;
}
foreach (DieTransferPathStep step in pathPlan.Steps)
{
if (step == null)
{
continue;
}
pointResults.Add(new DieRecheckPointResult
{
StepIndex = step.StepIndex,
PadRow = step.PadRow,
PadColumn = step.PadColumn,
DieRow = step.DieRow,
DieColumn = step.DieColumn,
PadX = step.PadX,
PadY = step.PadY,
DieX = step.DieX,
DieY = step.DieY,
TransPathType = step.TransPathType.ToString(),
IsMissingBond = false,
XError = step.DieX - step.PadX,
YError = step.DieY - step.PadY
});
}
return pointResults;
}
}
}

View File

@@ -0,0 +1,23 @@
using MW.WorkFlow;
using System;
using System.Threading.Tasks;
namespace MainShell.Process
{
public class DieTransferActivity : ActivityAbstractBase
{
private readonly DieTransferMotionService _motionService;
public DieTransferActivity(string name, DieTransferMotionService motionService) : base(name)
{
_motionService = motionService ?? throw new ArgumentNullException(nameof(motionService));
}
protected override async Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
{
await _motionService.ExecuteAsync(context, activityControl).ConfigureAwait(false);
return ActivityResult.Success;
}
}
}

View File

@@ -0,0 +1,34 @@
namespace MainShell.Process
{
public class DieTransferDecisionResult
{
public bool IsSubstrateComplete { get; set; }
public bool IsChipExhausted { get; set; }
public bool NeedsRecheck { get; set; }
public AutoProductionRoute SuggestedRoute { get; set; } = AutoProductionRoute.None;
}
public enum CurrentChipLifecycleState
{
Empty = 0,
Loading = 1,
InUse = 2,
PendingUnload = 3,
Unloading = 4,
LoadedPendingTransfer = 5
}
public enum SubstrateLifecycleState
{
NotLoaded = 0,
Loaded = 1,
Positioned = 2,
HeightMeasured = 3,
InProcess = 4,
Complete = 5,
Unloading = 6
}
}

View File

@@ -0,0 +1,196 @@
using MaxwellFramework.Core.Attributes;
using MainShell.Common;
using MainShell.ProcessResult;
using MW.WorkFlow;
using System;
using System.Threading.Tasks;
namespace MainShell.Process
{
[Singleton]
public class DieTransferMotionService
{
private readonly AutoProductionRuntimeStateService _autoProductionRuntimeStateService;
private readonly IDieTransferPathGenerator _dieTransferPathGenerator;
public DieTransferMotionService(AutoProductionRuntimeStateService autoProductionRuntimeStateService, IDieTransferPathGenerator dieTransferPathGenerator)
{
_autoProductionRuntimeStateService = autoProductionRuntimeStateService ?? throw new ArgumentNullException(nameof(autoProductionRuntimeStateService));
_dieTransferPathGenerator = dieTransferPathGenerator ?? throw new ArgumentNullException(nameof(dieTransferPathGenerator));
}
public async Task ExecuteAsync(WorkflowContext context, ActivityControl activityControl)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (activityControl == null)
{
throw new ArgumentNullException(nameof(activityControl));
}
TryGeneratePathPlan(context);
for (var i = 0; i < 20; i++)
{
activityControl.ThrowIfCancellationRequested();
await activityControl.CheckPauseAsync().ConfigureAwait(false);
await Task.Delay(100, activityControl.CancellationToken).ConfigureAwait(false);
}
DieTransferDecisionResult decisionResult = ResolveDecisionResult(context);
context.SetData(WorkflowContextKeys.DieTransferDecisionResult, decisionResult);
context.SetRoute(WorkflowContextKeys.AutoProductionRoute, decisionResult.SuggestedRoute);
bool hasCurrentChipStateService = context.TryGetData<CurrentChipStateService>(WorkflowContextKeys.CurrentChipStateService, out CurrentChipStateService currentChipStateService) && currentChipStateService != null;
if (decisionResult.SuggestedRoute == AutoProductionRoute.ChipExhausted ||
decisionResult.SuggestedRoute == AutoProductionRoute.BothExhausted)
{
context.SetData(WorkflowContextKeys.PendingChipLoad, true);
context.SetData(WorkflowContextKeys.CurrentChipState, CurrentChipLifecycleState.PendingUnload);
if (hasCurrentChipStateService)
{
currentChipStateService.SetState(CurrentChipLifecycleState.PendingUnload);
}
}
else
{
context.SetData(WorkflowContextKeys.PendingChipLoad, false);
context.SetData(WorkflowContextKeys.CurrentChipState, CurrentChipLifecycleState.InUse);
if (hasCurrentChipStateService)
{
currentChipStateService.SetState(CurrentChipLifecycleState.InUse);
}
}
if (decisionResult.IsSubstrateComplete)
{
context.SetData(WorkflowContextKeys.SubstrateProcessState, SubstrateLifecycleState.Complete);
}
else
{
context.SetData(WorkflowContextKeys.SubstrateProcessState, SubstrateLifecycleState.InProcess);
}
SyncRuntimeStateToContext(context);
}
public DieTransferPathPlan GeneratePathPlan(DieTransferPathRequest request)
{
return _dieTransferPathGenerator.Generate(request);
}
private DieTransferDecisionResult ResolveDecisionResult(WorkflowContext context)
{
bool needsRecheck = ConsumeBooleanFlag(context, WorkflowContextKeys.TransferNeedsRecheck) || _autoProductionRuntimeStateService.ConsumeTransferNeedsRecheck();
bool explicitChipExhausted = ConsumeBooleanFlag(context, WorkflowContextKeys.TransferChipExhausted) || _autoProductionRuntimeStateService.ConsumeTransferChipExhausted();
if (needsRecheck)
{
return CreateDecisionFromRoute(AutoProductionRoute.Recheck);
}
AutoProductionRoute autoRoute;
if (context.TryGetRoute<AutoProductionRoute>(WorkflowContextKeys.AutoProductionRoute, out autoRoute))
{
if (autoRoute == AutoProductionRoute.Recheck)
{
return CreateDecisionFromRoute(autoRoute);
}
}
DieTransferRoute legacyRoute;
if (context.TryGetRoute<DieTransferRoute>(WorkflowContextKeys.DieTransferRoute, out legacyRoute) &&
legacyRoute == DieTransferRoute.Recheck)
{
return CreateDecisionFromRoute(AutoProductionRoute.Recheck);
}
bool isSubstrateComplete = UpdateSubstrateProgress(context);
bool isChipExhausted = explicitChipExhausted || UpdateCurrentChipBudget(context);
if (isSubstrateComplete && isChipExhausted)
{
return CreateDecisionFromRoute(AutoProductionRoute.BothExhausted);
}
if (isSubstrateComplete)
{
return CreateDecisionFromRoute(AutoProductionRoute.SubstrateComplete);
}
if (isChipExhausted)
{
return CreateDecisionFromRoute(AutoProductionRoute.ChipExhausted);
}
return CreateDecisionFromRoute(AutoProductionRoute.ContinueCurrentSubstrate);
}
private void TryGeneratePathPlan(WorkflowContext context)
{
DieTransferPathRequest pathRequest;
if (!context.TryGetData<DieTransferPathRequest>(WorkflowContextKeys.DieTransferPathRequest, out pathRequest) || pathRequest == null)
{
return;
}
DieTransferPathPlan pathPlan = _dieTransferPathGenerator.Generate(pathRequest);
context.SetData(WorkflowContextKeys.DieTransferPathPlan, pathPlan);
}
private bool UpdateSubstrateProgress(WorkflowContext context)
{
if (_autoProductionRuntimeStateService.CurrentSubstratePendingCount <= 0)
{
return false;
}
return _autoProductionRuntimeStateService.ConsumeSubstrateTarget();
}
private bool UpdateCurrentChipBudget(WorkflowContext context)
{
if (_autoProductionRuntimeStateService.CurrentChipRemainingCount <= 0)
{
return false;
}
return _autoProductionRuntimeStateService.ConsumeCurrentChip();
}
private static bool ConsumeBooleanFlag(WorkflowContext context, string key)
{
bool flagValue;
if (!context.TryGetData<bool>(key, out flagValue) || !flagValue)
{
return false;
}
context.SetData(key, false);
return true;
}
private void SyncRuntimeStateToContext(WorkflowContext context)
{
context.SetData(WorkflowContextKeys.CurrentSubstratePendingCount, _autoProductionRuntimeStateService.CurrentSubstratePendingCount);
context.SetData(WorkflowContextKeys.CurrentSubstrateProcessedCount, _autoProductionRuntimeStateService.CurrentSubstrateProcessedCount);
context.SetData(WorkflowContextKeys.CurrentChipRemainingCount, _autoProductionRuntimeStateService.CurrentChipRemainingCount);
context.SetData(WorkflowContextKeys.TransferNeedsRecheck, _autoProductionRuntimeStateService.TransferNeedsRecheck);
context.SetData(WorkflowContextKeys.TransferChipExhausted, _autoProductionRuntimeStateService.TransferChipExhausted);
}
private static DieTransferDecisionResult CreateDecisionFromRoute(AutoProductionRoute route)
{
DieTransferDecisionResult decisionResult = new DieTransferDecisionResult();
decisionResult.SuggestedRoute = route;
decisionResult.NeedsRecheck = route == AutoProductionRoute.Recheck;
decisionResult.IsChipExhausted = route == AutoProductionRoute.ChipExhausted || route == AutoProductionRoute.BothExhausted;
decisionResult.IsSubstrateComplete = route == AutoProductionRoute.SubstrateComplete || route == AutoProductionRoute.BothExhausted;
return decisionResult;
}
}
}

View File

@@ -0,0 +1,385 @@
# MainShell/Process/DieTransfer/Planning ˵<><CBB5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
## 1. <20><>Χ
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>˵<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> `MainShell/Process/DieTransfer/Planning` Ŀ¼<C4BF>µĹ滮<C4B9><E6BBAE><EFBFBD><EFBFBD>ʵ<EFBFBD>֣<EFBFBD><D6A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD>
- `DieTransferPathGenerator.cs`
- `DieTransferPlanningContext.cs`
- `DieTransferPathRequest.cs`
- `DieTransferPathPlan.cs`
- `DieTransferPathRegionPlan.cs`
- `DieTransferPathStep.cs`
- `DieTransferRegion.cs`
- `DieTransferRow.cs`
- `PadTransferRow.cs`
- `DieTransferRowDirection.cs`
- `SubstrateRowDirectionStrategy.cs`
- `IDieTransferPathGenerator.cs`
## 2. ģ<>鶨λ
<EFBFBD><EFBFBD>ģ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> Die Transfer <20>ġ<EFBFBD><C4A1><EFBFBD><EFBFBD><E3A1B1>ְ<EFBFBD><D6B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ִ<EFBFBD><D6B4><EFBFBD>˶<EFBFBD><CBB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰѺ<C7B0>ѡ Die <20><><EFBFBD>Ϻͺ<CFBA>ѡ Pad <20><><EFBFBD><EFBFBD>ת<EFBFBD><D7AA><EFBFBD><EFBFBD>һ<EFBFBD>ݿ<EFBFBD>ִ<EFBFBD>е<EFBFBD>·<EFBFBD><C2B7><EFBFBD>ƻ<EFBFBD><C6BB><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- <20><>ѡ Die <20><><EFBFBD><EFBFBD>
- <20><>ѡ Pad <20><><EFBFBD><EFBFBD>
- Die <20><><EFBFBD><EFBFBD>
- Substrate <20><><EFBFBD><EFBFBD>
- ·<><C2B7><EFBFBD><EFBFBD><EFBFBD>ɲ<EFBFBD><C9B2><EFBFBD>
- <20>Ƿ<EFBFBD><C7B7><EFBFBD><EFBFBD><EFBFBD> NG Die
- <20><><EFBFBD><EFBFBD><EFBFBD>з<EFBFBD><D0B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- <20><><EFBFBD><EFBFBD><EFBFBD>ɵ<EFBFBD><C9B5><EFBFBD><EFBFBD>Բ<EFBFBD><D4B2><EFBFBD> `Steps`
- <20><><EFBFBD>˺<EFBFBD><CBBA>Ŀ<EFBFBD><C4BF><EFBFBD> Die / Pad <20><><EFBFBD><EFBFBD>
- ʣ<><CAA3>δ<EFBFBD><CEB4><EFBFBD>Ե<EFBFBD> Die / Pad
- ʣ<><CAA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>нṹ<D0BD><E1B9B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߽<EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ǻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģ<EFBFBD><EFBFBD><EFBFBD>߼<EFBFBD><EFBFBD><EFBFBD><EFBFBD>˶<EFBFBD>ִ<EFBFBD>н<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֶ<EFBFBD>ģʽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>̣<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ը<EFBFBD><EFBFBD><EFBFBD>ͬһ<EFBFBD><EFBFBD>·<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
## 3. <20><><EFBFBD>Ķ<EFBFBD><C4B6><EFBFBD>ְ<EFBFBD><D6B0>
### 3.1 `DieTransferPathRequest`
·<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD>ؼ<EFBFBD><EFBFBD>ֶΣ<EFBFBD>
- `DieCandidates`<60><><EFBFBD><EFBFBD>ѡ Die <20><><EFBFBD><EFBFBD>
- `PadCandidates`<60><><EFBFBD><EFBFBD>ѡ Pad <20><><EFBFBD><EFBFBD>
- `DieRegion`<60><>Die <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- `SubstrateRegion`<60><>Pad <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- `TransPathType`<60><>·<EFBFBD><C2B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ
- `SubstrateRowDirectionStrategy`<60><>Pad <20>б<EFBFBD><D0B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- `SkipNgDie`<60><><EFBFBD>Ƿ<EFBFBD><C7B7><EFBFBD><EFBFBD><EFBFBD> NG Die
Ĭ<EFBFBD><EFBFBD>ֵ<EFBFBD><EFBFBD><EFBFBD>ñȽϺ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- Ĭ<><C4AC>ģʽ<C4A3><CABD> `Sequence`
- Ĭ<><C4AC> Pad <20>з<EFBFBD><D0B7><EFBFBD><EFBFBD><EFBFBD> `AllPositive`
- Ĭ<><C4AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD> NG Die
### 3.2 `DieTransferPlanningContext`
<EFBFBD><EFBFBD><EFBFBD>ǵ<EFBFBD>ǰģ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĵ<EFBFBD><EFBFBD>м<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ò<EFBFBD><EFBFBD>Ƕ<EFBFBD><EFBFBD>Ⱪ¶<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD> request Ԥ<><D4A4><EFBFBD><EFBFBD><EFBFBD>ɡ<EFBFBD><C9A1><EFBFBD>ֱ<EFBFBD><D6B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڲ<EFBFBD><DAB2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ġ<EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>£<EFBFBD>
1. <20><> `RegionModel` ת<><D7AA><EFBFBD>ڲ<EFBFBD>ʹ<EFBFBD>õ<EFBFBD> `DieTransferRegion`
2. <20><>ԭʼ<D4AD><CABC>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɰ<EFBFBD><C9B0>з<EFBFBD><D0B7><EFBFBD><EFBFBD><EFBFBD> `DieTransferRow` / `PadTransferRow`
3. <20><><EFBFBD><EFBFBD><EFBFBD>з<EFBFBD><D0B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>򣬰<EFBFBD>ÿһ<C3BF>еĵ<D0B5><C4B5><EFBFBD>֯<EFBFBD><D6AF>ȷ<EFBFBD><C8B7>˳<EFBFBD><CBB3>
<EFBFBD><EFBFBD><EFBFBD>仰˵<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ġ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>򡢹<EFBFBD><EFBFBD>ˡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֯<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> generator <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFA3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD> context <20><><EFBFBD><EFBFBD><EFBFBD>׶<EFBFBD><D7B6><EFBFBD><EFBFBD>ɡ<EFBFBD>
### 3.3 `DieTransferPathGenerator`
·<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڡ<EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĸ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- `Generate()`<60><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> `DieTransferPathPlan`
- `GenerateByRegion()`<60><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> `DieTransferPathRegionPlan`
- `GenerateByCandidates()`<60><><EFBFBD><EFBFBD><EFBFBD>ݴ<EFBFBD><DDB4><EFBFBD><EFBFBD><EFBFBD> Pad <20><><EFBFBD>Ϻ<EFBFBD> Die <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> `DieTransferPathRegionPlan`
- `GenerateByRows()`<60><><EFBFBD><EFBFBD><EFBFBD>ݴ<EFBFBD><DDB4><EFBFBD><EFBFBD><EFBFBD> `PadTransferRow` / `DieTransferRow` ֱ<><D6B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD> `DieTransferPathRegionPlan`
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> `Generate()` ʵ<><CAB5>ֻ<EFBFBD><D6BB>ת<EFBFBD><D7AA> `GenerateByRegion()`<60><>ֻ<EFBFBD>ǰѷ<C7B0><D1B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>խ<EFBFBD>ɻ<EFBFBD><C9BB><EFBFBD><E0A1A3><EFBFBD>ˣ<EFBFBD>
- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>÷<EFBFBD>ֻ<EFBFBD><D6BB><EFBFBD>IJ<EFBFBD><C4B2><EFBFBD><EFBFBD>б<EFBFBD><D0B1><EFBFBD>ͳ<EFBFBD><CDB3><EFBFBD><EFBFBD>Ϣ<EFBFBD><CFA2><EFBFBD><EFBFBD> `Generate()` <20><><EFBFBD><EFBFBD>
- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>÷<EFBFBD><C3B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʣ<EFBFBD><CAA3> Die / Pad <20><>ʣ<EFBFBD><CAA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӧ<EFBFBD><D3A6>ʹ<EFBFBD><CAB9> `GenerateByRegion()`
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڵĶ<EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD>£<EFBFBD>
- `GenerateByCandidates()`<60><><EFBFBD>ʺϵ<CABA><CFB5>÷<EFBFBD><C3B7><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><D6BB>ԭʼ<D4AD><CABC>ѡ<EFBFBD><EFBFBD>ϣ<EFBFBD><CFA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Լ<EFBFBD><D4BC><EFBFBD>װ `DieTransferPathRequest` <20>ij<EFBFBD><C4B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>÷<EFBFBD><C3B7><EFBFBD><EFBFBD>ڲ<EFBFBD><DAB2>Ի<EFBFBD><D4BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>е<EFBFBD> request/context Ԥ<><D4A4><EFBFBD><EFBFBD><EFBFBD>߼<EFBFBD><DFBC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɵ<EFBFBD>·<EFBFBD><C2B7>ͳһ<CDB3>ӷ<EFBFBD><D3B7>ؽ<EFBFBD><D8BD><EFBFBD><EFBFBD><EFBFBD> `Steps` <20>л<EFBFBD>ȡ<EFBFBD><C8A1>
- `GenerateByRows()`<60><><EFBFBD>ʺϵ<CABA><CFB5>÷<EFBFBD><C3B7>Ѿ<EFBFBD><D1BE><EFBFBD><EFBFBD>ɰ<EFBFBD><C9B0>з<EFBFBD><D0B7><EFBFBD><E9A1A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>NG <20><><EFBFBD>˲<EFBFBD><CBB2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>õij<C3B5><C4B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>÷<EFBFBD><C3B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¹<EFBFBD><C2B9><EFBFBD> planning context<78><74><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֱ<EFBFBD>ӰѴ<D3B0><D1B4><EFBFBD><EFBFBD><EFBFBD>չ<EFBFBD><D5B9>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD> Die / Pad<61><64><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɲ<EFBFBD><C9B2><EFBFBD><EFBFBD><EFBFBD>ʣ<EFBFBD><CAA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
### 3.4 `DieTransferPathPlan` / `DieTransferPathRegionPlan`
`DieTransferPathPlan` <20><>ʾ<EFBFBD><CABE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E6BBAE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- `TransPathType`
- `Steps`
- `AvailableDieCount`
- `AvailablePadCount`
- `GeneratedStepCount`
`DieTransferPathRegionPlan` <20>ڴ˻<DAB4><CBBB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>չ<EFBFBD><D5B9>ʣ<EFBFBD><CAA3><EFBFBD><EFBFBD>Ϣ<EFBFBD><CFA2>
- `RemainingDies`
- `RemainingPads`
- `RemainingDieRows`
- `RemainingPadRows`
- `RemainingDieRegion`
- `RemainingSubstrateRegion`
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ζ<EFBFBD>Ÿ<EFBFBD>ģ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD>·<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܸ<EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD>ּ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ġ<EFBFBD>
### 3.5 `DieTransferPathStep`
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Խ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>󣬼<EFBFBD>¼<EFBFBD><EFBFBD>
- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> `StepIndex`
- Die <20><><EFBFBD>к<EFBFBD><D0BA><EFBFBD><EFBFBD><EFBFBD>
- Pad <20><><EFBFBD>к<EFBFBD><D0BA><EFBFBD><EFBFBD><EFBFBD>
- <20><>ǰ·<C7B0><C2B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƵñȽ<EFBFBD>ֱ<EFBFBD>ӣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> UI <20><>ִ<EFBFBD>в㶼<D0B2><E3B6BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѡ<EFBFBD>
## 4. <20><><EFBFBD>ɷ<EFBFBD><C9B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
·<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>£<EFBFBD>
1. <20><><EFBFBD>÷<EFBFBD><C3B7><EFBFBD><EFBFBD><EFBFBD> `DieTransferPathRequest`
2. `DieTransferPathGenerator.GenerateByRegion()` У<><D0A3> request <20>ǿ<EFBFBD>
3. `DieTransferPlanningContext.Create(request)` <20><><EFBFBD>ɹ滮<C9B9><E6BBAE><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
4. <20><> context <20><>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Die <20>б<EFBFBD><D0B1><EFBFBD> Pad <20>б<EFBFBD>
5. <20><> `TransPathType` ѡ<><D1A1><EFBFBD><EFBFBD><EFBFBD>ɲ<EFBFBD><C9B2><EFBFBD>
6. <20><><EFBFBD><EFBFBD> `DieTransferPathStep` <20>б<EFBFBD>
7. <20><><EFBFBD><EFBFBD>δ<EFBFBD><CEB4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ե<EFBFBD>ʣ<EFBFBD><CAA3> Die / Pad
8. <20><><EFBFBD><EFBFBD>ʣ<EFBFBD><CAA3><EFBFBD>нṹ<D0BD><E1B9B9>ʣ<EFBFBD><CAA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
9. <20><>װ `DieTransferPathRegionPlan` <20><><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD>԰<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><EFBFBD><EFBFBD>̣<EFBFBD>
- <20><>һ<EFBFBD>Σ<EFBFBD>Ԥ<EFBFBD><D4A4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>õ<EFBFBD><C3B5>ȶ<EFBFBD>˳<EFBFBD><CBB3>
- <20>ڶ<EFBFBD><DAB6>Σ<EFBFBD><CEA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Die <20><> Pad <20><>һһ<D2BB><D2BB><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD>Ǹı<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ı䡰·<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- `GenerateByCandidates()`<60><><EFBFBD>Ӻ<EFBFBD>ѡ<EFBFBD><D1A1><EFBFBD>Ͻ<EFBFBD><CFBD><EFBFBD>ٸ<EFBFBD><D9B8><EFBFBD> `GenerateByRegion()` <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- `GenerateByRows()`<60><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> request/context <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֱ<EFBFBD>Ӵ<EFBFBD><D3B4>ж<EFBFBD><D0B6><EFBFBD>չ<EFBFBD><D5B9>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϻ<EFBFBD><CFBA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Խ׶<D4BD>
## 5. Ԥ<><D4A4><EFBFBD><EFBFBD><EFBFBD>׶ν<D7B6><CEBD><EFBFBD>
### 5.1 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
`DieTransferPlanningContext.Create()` <20><><EFBFBD>Ȱ<EFBFBD> `request.DieRegion` <20><> `request.SubstrateRegion` ת<><D7AA> `DieTransferRegion`<60><>
`DieTransferRegion.Contains(row, column)` <20><><EFBFBD><EFBFBD><EFBFBD>жϵ<D0B6><CFB5>Ƿ<EFBFBD><C7B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڡ<EFBFBD>
<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>£<EFBFBD>
- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD>գ<EFBFBD><D5A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- <20><><EFBFBD><EFBFBD>Ϊ<EFBFBD>գ<EFBFBD><D5A3><EFBFBD>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD><EFBFBD>ˣ<EFBFBD><CBA3><EFBFBD><EFBFBD>к<EFBFBD>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD><EFBFBD>ɲ<EFBFBD><C9B2><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӿ<EFBFBD>ע<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȷ˵<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD>ҵ<EFBFBD><EFBFBD>Լ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
### 5.2 <20><><EFBFBD>з<EFBFBD><D0B7><EFBFBD>
Pad <20><> Die <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֱ<EFBFBD><D6B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>򣬶<EFBFBD><F2A3ACB6><EFBFBD><EFBFBD>Ȱ<EFBFBD> `Row` <20><><EFBFBD><EFBFBD><E9A3AC><EFBFBD><EFBFBD>ÿһ<C3BF><D2BB><EFBFBD>ڲ<EFBFBD><DAB2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Pad ͨ<><CDA8> `CreatePadRows()` <20><><EFBFBD><EFBFBD> `PadTransferRow`<60><>
- <20>ȹ<EFBFBD><C8B9>˿ն<CBBF><D5B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- <20><> `Row` <20><><EFBFBD><EFBFBD>
- ÿ<>鰴 `Column` <20><><EFBFBD>򱣴<EFBFBD>ԭʼ<D4AD><CABC><EFBFBD><EFBFBD>
- <20>ٸ<EFBFBD><D9B8>ݲ<EFBFBD><DDB2>Ծ<EFBFBD><D4BE><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD><EFBFBD>ն<EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD>
Die ͨ<><CDA8> `CreateDieRows()` <20><><EFBFBD><EFBFBD> `DieTransferRow`<60><>
- <20>ȹ<EFBFBD><C8B9>˿ն<CBBF><D5B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- <20><> `Row` <20><><EFBFBD><EFBFBD>
- ÿ<>鰴 `Column` <20><><EFBFBD>򱣴<EFBFBD>
- <20>з<EFBFBD><D0B7><EFBFBD><EFBFBD>̶<EFBFBD><CCB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD>η<EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD>
- `SkipNgDie` <20><><EFBFBD>ж<EFBFBD><D0B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ч
### 5.3 <20>з<EFBFBD><D0B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Pad <20><><EFBFBD>з<EFBFBD><D0B7><EFBFBD><EFBFBD><EFBFBD> `SubstrateRowDirectionStrategy` <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- `AllPositive`<60><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ж<EFBFBD><D0B6><EFBFBD>С<EFBFBD>е<EFBFBD><D0B5><EFBFBD><EFBFBD><EFBFBD>
- `AllNegative`<60><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ж<EFBFBD><D0B6>Ӵ<EFBFBD><D3B4>е<EFBFBD>С<EFBFBD><D0A1>
- `Serpentine`<60><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ż<EFBFBD><C5BC><EFBFBD>з<EFBFBD><D0B7><EFBFBD>
Die <20><><EFBFBD>з<EFBFBD><D0B7><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD>̶<EFBFBD><CCB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ι<EFBFBD><CEB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ `CreateDieRows()` <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ `useSerpentineDirection` <20>̶<EFBFBD><CCB6><EFBFBD><EFBFBD><EFBFBD> `true`<60><>
- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- ż<><C5BC><EFBFBD>з<EFBFBD><D0B7><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ζ<EFBFBD>ŵ<EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Die <20>ı<EFBFBD><C4B1><EFBFBD>˳<EFBFBD><CBB3><EFBFBD><EFBFBD> Pad <20><><EFBFBD><EFBFBD>ǿԼ<C7BF><D4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>÷<EFBFBD><C3B7>޷<EFBFBD><DEB7><EFBFBD> request <20>е<EFBFBD><D0B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Die <20>з<EFBFBD><D0B7><EFBFBD><EFBFBD><EFBFBD>
### 5.4 <20><><EFBFBD>õ<EFBFBD><C3B5><EFBFBD>ȡ
<EFBFBD>ڷ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɺ<EFBFBD><EFBFBD><EFBFBD>context ͨ<><CDA8><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7><EFBFBD>չƽ<D5B9><C6BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><D0B1><EFBFBD>
- `GetOrderedPads()`
- `GetOrderedDies()`
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ᰴ `RowIndex` <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>У<EFBFBD><D0A3><EFBFBD>ƴ<EFBFBD><C6B4>ÿ<EFBFBD>еĿ<D0B5><C4BF>õ<EFBFBD><C3B5><EFBFBD><EFBFBD>С<EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> Die <20><><EFBFBD>ġ<EFBFBD><C4A1><EFBFBD><EFBFBD>á<EFBFBD><C3A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> `SkipNgDie` Ӱ<>
- `SkipNgDie = true` ʱ<><CAB1>`DieStatus.Ng` <20><EFBFBD>ų<EFBFBD>
- `SkipNgDie = false` ʱ<><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Die <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD>ˣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɵIJ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> request <20><><EFBFBD><EFBFBD>ԭʼ<D4AD><CABC>ѡ<EFBFBD><D1A1><EFBFBD>ϣ<EFBFBD><CFA3><EFBFBD><EFBFBD>ǡ<EFBFBD><C7A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ˡ<EFBFBD><CBA1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>NG <20><><EFBFBD>˺<EFBFBD><CBBA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>С<EFBFBD><D0A1><EFBFBD>
## 6. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɷ<EFBFBD><C9B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
### 6.1 ˳<><CBB3><EFBFBD><EFBFBD><EFBFBD><EFBFBD> `BuildSequenceSteps`
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֱ<EFBFBD>ӵIJ<EFBFBD><EFBFBD>ԡ<EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><EFBFBD>
1. <20>ȵõ<C8B5> `orderedDies` <20><> `orderedPads`
2. ȡ `Math.Min(dies.Count, pads.Count)` <20><>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD><EFBFBD>ɲ<EFBFBD><C9B2><EFBFBD>
3. <20><> `i` <20><> Die <20><><EFBFBD><EFBFBD> `i` <20><> Pad ֱ<><D6B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
4. <20><><EFBFBD><EFBFBD> `CreateStep()` <20><><EFBFBD>ɲ<EFBFBD><C9B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
α<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>£<EFBFBD>
```text
stepCount = min(orderedDies.Count, orderedPads.Count)
for i in [0 .. stepCount - 1]
step = CreateStep(i + 1, orderedDies[i], orderedPads[i])
steps.Add(step)
```
<EFBFBD>ò<EFBFBD><EFBFBD>Ե<EFBFBD><EFBFBD>ص<EFBFBD><EFBFBD>ǣ<EFBFBD>
- <20><>ȫ<EFBFBD><C8AB><EFBFBD><EFBFBD>Ԥ<EFBFBD><D4A4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˳<EFBFBD><CBB3>
- <20><EFBFBD>򵥣<EFBFBD><F2B5A5A3><EFBFBD><EFBFBD>Ӷȵ<D3B6>
- <20><><EFBFBD><EFBFBD><EFBFBD>ȶ<EFBFBD><C8B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>׷<EFBFBD><D7B7>
- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD>ʼ<EFBFBD><CABC>ξ<EFBFBD><CEBE>룬ֻ<EBA3AC><D6BB><EFBFBD><EFBFBD><EFBFBD>ź<EFBFBD><C5BA><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ķ<EFBFBD>Ӧ<EFBFBD><D3A6>ϵ
<EFBFBD><EFBFBD><EFBFBD>ó<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĸ<EFBFBD><C4B8><EFBFBD><EFBFBD><EFBFBD>˳<EFBFBD><CBB3>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>
- <20><><EFBFBD><EFBFBD><EFBFBD>Ѿ<EFBFBD><D1BE><EFBFBD>֤ Die / Pad <20><><EFBFBD><EFBFBD>һһ<D2BB><D2BB>Ӧ
- ϣ<><CFA3>·<EFBFBD><C2B7><EFBFBD><EFBFBD>Ԥ<EFBFBD><EFBFBD><E2A1A2><EFBFBD><EFBFBD>У<EFBFBD><D0A3><EFBFBD>͸<EFBFBD><CDB8><EFBFBD>
### 6.2 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> `BuildNearestSteps`
<EFBFBD><EFBFBD><EFBFBD>Ǹ<EFBFBD>ƫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ż<EFBFBD><EFBFBD>IJ<EFBFBD><EFBFBD>ԡ<EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><EFBFBD>
1. <20>ȸ<EFBFBD><C8B8><EFBFBD>һ<EFBFBD><D2BB> `remainingDies`
2. <20><> Pad <20>ļȶ<C4BC>˳<EFBFBD><CBB3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ÿ<EFBFBD><C3BF> Pad
3. <20>ڵ<EFBFBD>ǰʣ<C7B0><CAA3> Die <20>У<EFBFBD><D0A3>ҳ<EFBFBD><D2B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Pad <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Die
4. <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD>Ժ󣬰Ѹ<F3A3ACB0> Die <20><> `remainingDies` <20><><EFBFBD>Ƴ<EFBFBD>
5. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB> Pad<61><64>ֱ<EFBFBD><D6B1> Die <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Pad <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
α<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>£<EFBFBD>
```text
remainingDies = orderedDies
for each pad in orderedPads
if remainingDies is empty
break
die = SelectNearestDie(pad, remainingDies)
steps.Add(CreateStep(stepIndex, die, pad))
remainingDies.Remove(die)
stepIndex++
```
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Բ<EFBFBD><EFBFBD><EFBFBD>ȫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǡ<EFBFBD><EFBFBD><EFBFBD> Pad ˳<><CBB3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̰<EFBFBD><CCB0>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Die<69><65><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ŵ<EFBFBD><EFBFBD>ǣ<EFBFBD>
- ʵ<>ּ<EFBFBD><D6BC><EFBFBD>
- <20>Ծֲ<D4BE><D6B2>ƶ<EFBFBD><C6B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ż<EFBFBD><C5BB><EFBFBD><EFBFBD><EFBFBD>
- <20><><EFBFBD><EFBFBD>ȫ˳<C8AB><CBB3><EFBFBD><EFBFBD><EFBFBD>Ը<EFBFBD><D4B8><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ռ<EFBFBD>λ<EFBFBD><CEBB>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҳ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȷ<EFBFBD><EFBFBD>
- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<E3B7A8><D2BB><EFBFBD><EFBFBD>ȫ<EFBFBD><C8AB><EFBFBD><EFBFBD><EFBFBD>Ž<EFBFBD>
- ǰ<><C7B0> Pad <20><>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD>Ӱ<EFBFBD><D3B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Pad <20>Ŀ<EFBFBD>ѡ Die
- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD>̲<EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD>ˣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ԡ<EFBFBD><EFBFBD><EFBFBD>׼ȷ<EFBFBD>ı<EFBFBD><EFBFBD><EFBFBD>Ӧ<EFBFBD><EFBFBD><EFBFBD>ǣ<EFBFBD>
- <20><> Pad ˳<><CBB3>ִ<EFBFBD>еľֲ<C4BE>̰<EFBFBD><CCB0><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƥ<EFBFBD><C6A5>
### 6.3 <20><><EFBFBD><EFBFBD> Die <20><>ѡȡ<D1A1><C8A1><EFBFBD><EFBFBD> `SelectNearestDie`
`SelectNearestDie()` <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD>ʣ<EFBFBD><CAA3> Die<69><65><EFBFBD><EFBFBD>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>С<EFBFBD><D0A1><EFBFBD>Ǹ<EFBFBD><C7B8><EFBFBD>
<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽΪ<EFBFBD><EFBFBD>
```text
(die.X - pad.X)^2 + (die.Y - pad.Y)^2
```
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD><EFBFBD><EFBFBD>ƽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ŷ<EFBFBD>Ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڡ<EFBFBD><EFBFBD>Ƚ<EFBFBD>˭<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǵȼ۵ģ<EFBFBD><EFBFBD>Ҽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʡ<EFBFBD><EFBFBD>
<EFBFBD><EFBFBD>Ҫע<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
1. `CalculateDistance()` <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȷ<EFBFBD><C8B7>ʵ<EFBFBD>ʸ<EFBFBD><CAB8><EFBFBD> `CalculateSquaredDistance()`
2. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD><EBB0B4><EFBFBD>ȱ<EFBFBD><C8B1><EFBFBD><EFBFBD><EFBFBD>˭<EFBFBD>ͱ<EFBFBD><CDB1><EFBFBD>˭<EFBFBD><CBAD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD>ڶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- <20><>ǰ tie-break <20><><EFBFBD><EFBFBD>ֱ<EFBFBD>Ӱ<EFBFBD> `Row`<60><>`Column` <20>Ƚ<EFBFBD>
- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> `remainingDies` <20>ĵ<EFBFBD>ǰ˳<C7B0><CBB3>
- <20><> `remainingDies` <20><>˳<EFBFBD><CBB3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Die <20>е<EFBFBD><D0B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֯<EFBFBD><D6AF><EFBFBD><EFBFBD>
Ҳ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>˵<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Die <20><>ͬһ<CDAC><D2BB> Pad <20>Ⱦ<EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʤ<EFBFBD><CAA4><EFBFBD>߱<EFBFBD><DFB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɡ<EFBFBD>Die Ԥ<><D4A4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˳<EFBFBD>򡱾<EFBFBD><F2A1B1BE><EFBFBD><EFBFBD><EFBFBD>
## 7. ʣ<><CAA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɷ<EFBFBD>ʽ
·<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɺ<EFBFBD><EFBFBD><EFBFBD>`GenerateByRegion()` <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD>㡰ʣ<E3A1B0><CAA3>״̬<D7B4><CCAC>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˵<EFBFBD><EFBFBD>ʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֱ<EFBFBD><EFBFBD>ǣ<EFBFBD>
- `GenerateByCandidates()`<60><><EFBFBD><EFBFBD> `GenerateByRegion()` <20><>ȫһ<C8AB>£<EFBFBD><C2A3><EFBFBD>Ϊ<EFBFBD>ڲ<EFBFBD><DAB2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װ request <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- `GenerateByRows()`<60><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>÷<EFBFBD><C3B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ж<EFBFBD><D0B6><EFBFBD><EFBFBD>е<EFBFBD> `RowIndex`<60><>`Direction`<60><>`SkipNgDie` <20><><EFBFBD>м<EFBFBD><D0BC><EFBFBD><EFBFBD>ã<EFBFBD>ֻ<EFBFBD><D6BB><EFBFBD>˵<EFBFBD><CBB5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD>õ<EFBFBD>λ<EFBFBD><CEBB><EFBFBD>ٹ<EFBFBD><D9B9><EFBFBD> `RemainingPadRows` / `RemainingDieRows`
### 7.1 ʣ<><CAA3> Die / Pad <20><><EFBFBD><EFBFBD>
ͨ<EFBFBD><EFBFBD> `CreateRemainingPads()` <20><> `CreateRemainingDies()`<60><>
- <20>Ӳ<EFBFBD><D3B2><EFBFBD><EFBFBD>б<EFBFBD><D0B1><EFBFBD><EFBFBD><EFBFBD>ȡ<EFBFBD>Ѿ<EFBFBD>ʹ<EFBFBD>ù<EFBFBD><C3B9><EFBFBD> `(row, column)` <20><>
- <20><>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>򼯺<EFBFBD><F2BCAFBA><EFBFBD><EFBFBD>
- <20>õ<EFBFBD>δ<EFBFBD><CEB4><EFBFBD><EFBFBD>ֹ滮<D6B9>ĵ<EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD><EFBFBD> `(row, column)` <20><>ΪΨһ<CEA8><D2BB><EFBFBD><EFBFBD>˵<EFBFBD><CBB5><EFBFBD><EFBFBD>ǰģ<C7B0><C4A3>Ĭ<EFBFBD><C4AC>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>λ<EFBFBD><CEBB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ψһ<CEA8><D2BB>ʶ<EFBFBD><CAB6>
### 7.2 ʣ<><CAA3><EFBFBD>нṹ<D0BD>ؽ<EFBFBD>
ʣ<EFBFBD><EFBFBD> Pad / Die <20><><EFBFBD><EFBFBD>ֻ<EFBFBD>Ǽ򵥷<C7BC><F2B5A5B7><EFBFBD><EFBFBD>б<EFBFBD><D0B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɣ<EFBFBD>
- `RemainingPadRows`
- `RemainingDieRows`
<EFBFBD><EFBFBD><EFBFBD>dz<EFBFBD><EFBFBD>м<EFBFBD>ֵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ι<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݽṹ<EFBFBD><EFBFBD>
### 7.3 ʣ<><CAA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ؽ<EFBFBD>
`CreateRegionFromPads()` <20><> `CreateRegionFromDies()` <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʣ<EFBFBD><CAA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD><C2BC><EFBFBD><EFBFBD><EFBFBD>С<EFBFBD><D0A1>Χ<EFBFBD><CEA7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- <20><>С<EFBFBD><D0A1>
- <20><>С<EFBFBD><D0A1>
- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD>ʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>򷵻<EFBFBD> `null`<60><>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>õ<EFBFBD><EFBFBD>÷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>жϣ<EFBFBD>
- <20><>ǰ<EFBFBD>Ƿ<EFBFBD><C7B7><EFBFBD>ȫ<EFBFBD><C8AB><EFBFBD><EFBFBD><E6BBAE><EFBFBD><EFBFBD>
- <20><>ʣ<EFBFBD><CAA3><EFBFBD><EFBFBD>һƬ<D2BB><C6AC><EFBFBD><EFBFBD>δ<EFBFBD><CEB4><EFBFBD><EFBFBD>
## 8. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ľ<EFBFBD><C4BD><EFBFBD>
### 8.1 <20>ŵ<EFBFBD>
<EFBFBD><EFBFBD>ģ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ŵ<EFBFBD><EFBFBD>Ƚ<EFBFBD><EFBFBD><EFBFBD>ȷ<EFBFBD><EFBFBD>
- ְ<><D6B0><EFBFBD><EFBFBD>һ<EFBFBD><D2BB>רע<D7A8>ڹ滮<DAB9><E6BBAE><EFBFBD><EFBFBD>ִ<EFBFBD><D6B4>
- request / context / result <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E1B9B9><EFBFBD><EFBFBD>
- ˳<><CBB3><EFBFBD><EFBFBD><EFBFBD>Ժ<EFBFBD><D4BA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ա߽<D4B1><DFBD><EFBFBD>ȷ
- ʣ<><CAA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǿ<EFBFBD>˸<EFBFBD><CBB8><EFBFBD><EFBFBD><EFBFBD>
- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɶ<EFBFBD><C9B6>ԽϺã<CFBA><C3A3><EFBFBD>չ<EFBFBD><D5B9><EFBFBD><EFBFBD>Ҳ<EFBFBD>Ƚ<EFBFBD><C8BD><EFBFBD><EFBFBD><EFBFBD>
### 8.2 <20><>Ҫע<D2AA><D7A2><EFBFBD>ĵ<EFBFBD>
<EFBFBD><EFBFBD>ǰʵ<EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Թ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȱ<EFBFBD>ݣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>м<EFBFBD><EFBFBD><EFBFBD>ά<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ע<EFBFBD><EFBFBD><EFBFBD>
1. `CalculateDistance()` ʵ<><CAB5><EFBFBD><EFBFBD>ƽ<EFBFBD><C6BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EBA3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ע<EFBFBD><D7A2>
2. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ǿֲ<C7BE>̰<EFBFBD>ģ<EFBFBD><C4A3><EFBFBD><EFBFBD><EFBFBD>ȫ<EFBFBD><C8AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƥ<EFBFBD><EFBFBD><E4A3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD><EFBFBD><EFBFBD>ȷ
3. <20>Ⱦ<EFBFBD>ʱ<EFBFBD><CAB1>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Die Ԥ<><D4A4><EFBFBD><EFBFBD>˳<EFBFBD>򣬽<EFBFBD><F2A3ACBD><EFBFBD><EFBFBD><EFBFBD>ȷд<C8B7><D0B4>˵<EFBFBD><CBB5>
4. `AvailableDieCount` / `AvailablePadCount` <20><>ʾ<EFBFBD><CABE><EFBFBD>˺<EFBFBD><CBBA>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ԭʼ<D4AD><CABC>ѡ<EFBFBD><D1A1>
5. <20><><EFBFBD><EFBFBD>Ϊ `null` ʱ<><CAB1>ʾ<EFBFBD><CABE><EFBFBD><EFBFBD><EFBFBD>ˣ<EFBFBD><CBA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽԼ<CABD><D4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E9B2B9>˵<EFBFBD><CBB5>
6. `Generate()` <20><><EFBFBD>ػ<EFBFBD><D8BB><EFBFBD><E0A3AC>ʵ<EFBFBD><CAB5><EFBFBD>ڲ<EFBFBD><DAB2><EFBFBD><EFBFBD>ɵ<EFBFBD><C9B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƻ<EFBFBD><C6BB><EFBFBD><EFBFBD>󣬵<EFBFBD><F3A3ACB5>÷<EFBFBD><C3B7><EFBFBD><EFBFBD><EFBFBD>ȷ<EFBFBD>Լ<EFBFBD><D4BC>Ƿ<EFBFBD><C7B7><EFBFBD>Ҫʣ<D2AA><CAA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ
### 8.3 <20>ɸĽ<C9B8><C4BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ع<EFBFBD><D8B9>ĵ<EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģ<EFBFBD>
- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǿ<EFBFBD>дģ<D0B4>ͣ<EFBFBD><CDA3><EFBFBD><EFBFBD>ڱ<EFBFBD><DAB1>ⲿ<EFBFBD>޸ĵĿ<C4B5><C4BF><EFBFBD>
- `DieTransferPathGenerator` <20>б<EFBFBD><D0B1><EFBFBD><EFBFBD><EFBFBD>δ<EFBFBD><CEB4>ʹ<EFBFBD>õ<EFBFBD> `OrderDies()` / `OrderPads()` ˽<>з<EFBFBD><D0B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ں<EFBFBD><DABA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- <20><><EFBFBD><EFBFBD><EFBFBD>ռ<EFBFBD><D5BC><EFBFBD>ͳһʹ<D2BB><CAB9> `MainShell.Process`<60><><EFBFBD><EFBFBD>Ŀ¼<C4BF><C2BC><EFBFBD>β<EFBFBD><CEB2><EFBFBD><EFBFBD><EFBFBD>ȫһһ<D2BB><D2BB>Ӧ<EFBFBD><D3A6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϵ<EFBFBD>ǰ<EFBFBD>ֿ<EFBFBD><D6BF><EFBFBD><EFBFBD><EFBFBD>
## 9. <20>ܽ<EFBFBD>
Planning ģ<><C4A3><EFBFBD>ĺ<EFBFBD><C4BA><EFBFBD>˼·<CBBC><C2B7><EFBFBD>Ը<EFBFBD><D4B8><EFBFBD>Ϊһ<CEAA><EFBFBD><E4BBB0>
<EFBFBD>ȰѺ<EFBFBD>ѡ Die / Pad <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>з<EFBFBD><D0B7><EFBFBD><EFBFBD><EFBFBD>NG <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȶ<EFBFBD>˳<EFBFBD><CBB3><EFBFBD><EFBFBD><EFBFBD>ٰ<EFBFBD><D9B0><EFBFBD>˳<EFBFBD><CBB3><EFBFBD><EFBFBD><EFBFBD>ԡ<EFBFBD><D4A1>򡰾ֲ<F2A1B0BE>̰<EFBFBD><CCB0><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ԡ<EFBFBD><D4A1><EFBFBD><EFBFBD><EFBFBD> `DieTransferPathStep` <20>б<EFBFBD><D0B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>󲹳<EFBFBD>ʣ<EFBFBD><CAA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʣ<EFBFBD><CAA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ<EFBFBD><CFA2>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӿ<EFBFBD>ά<EFBFBD><EFBFBD><EFBFBD>Կ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD><EFBFBD><EFBFBD>Ѿ<EFBFBD><EFBFBD>߱<EFBFBD><EFBFBD>ϺõĻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><EFBFBD><EFBFBD>ǰѲ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ع<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĵ<EFBFBD><EFBFBD><EFBFBD>ע<EFBFBD>͵Ĺ̶<EFBFBD>˵<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- <20><><EFBFBD><EFBFBD>ģʽ<C4A3>Ǿֲ<C7BE>̰<EFBFBD>ģ<EFBFBD><C4A3><EFBFBD><EFBFBD><EFBFBD>ȫ<EFBFBD><C8AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- <20><><EFBFBD><EFBFBD><EFBFBD>Ƚ<EFBFBD>ʹ<EFBFBD><CAB9>ƽ<EFBFBD><C6BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
- <20>Ⱦ<EFBFBD>ʱ<EFBFBD><CAB1>ѭ Die Ԥ<><D4A4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ⱥ<EFBFBD>˳<EFBFBD><CBB3>

View File

@@ -0,0 +1,347 @@
using MainShell.Common;
using MainShell.Models.Wafer;
using MainShell.Recipe.BaseBoard.Model;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MainShell.Process
{
public class DieTransferPathGenerator : IDieTransferPathGenerator
{
public DieTransferPathPlan Generate(DieTransferPathRequest request)
{
return GenerateByRegion(request);
}
public DieTransferPathRegionPlan GenerateByRegion(DieTransferPathRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
DieTransferPlanningContext planningContext = DieTransferPlanningContext.Create(request);
int effectiveCount = Math.Min(planningContext.AvailablePadCount, planningContext.AvailableDieCount);
List<PadTransferRow> remainingPadRows;
List<DieTransferRow> remainingDieRows;
List<PadTransferRow> activePadRows = DieTransferPlanningContext.TrimPadRows(planningContext.PadRows, effectiveCount, out remainingPadRows);
List<DieTransferRow> activeDieRows = DieTransferPlanningContext.TrimDieRows(planningContext.DieRows, effectiveCount, out remainingDieRows);
DieTransferPathRegionPlan pathPlan = GenerateByRows(activePadRows, activeDieRows, planningContext.TransPathType);
List<Pad> remainingPads = GetOrderedPads(remainingPadRows);
List<Die> remainingDies = GetOrderedDies(remainingDieRows);
pathPlan.AvailableDieCount = planningContext.AvailableDieCount;
pathPlan.AvailablePadCount = planningContext.AvailablePadCount;
pathPlan.RemainingPads = remainingPads;
pathPlan.RemainingDies = remainingDies;
pathPlan.RemainingPadRows = DieTransferPlanningContext.CreatePadRows(remainingPads, planningContext.SubstrateRegion, planningContext.PadRowDirectionStrategy);
pathPlan.RemainingDieRows = DieTransferPlanningContext.CreateDieRows(remainingDies, planningContext.DieRegion, planningContext.SkipNgDie, planningContext.DieRowDirectionStrategy);
pathPlan.RemainingSubstrateRegion = CreateRegionFromPads(remainingPads);
pathPlan.RemainingDieRegion = CreateRegionFromDies(remainingDies);
return pathPlan;
}
public DieTransferPathRegionPlan GenerateByCandidates(
IReadOnlyCollection<Pad> padCandidates,
IReadOnlyCollection<Die> dieCandidates,
TransPathType transPathType,
bool skipNgDie,
DieTransferRowTraversalStrategy padRowDirectionStrategy,
DieTransferRowTraversalStrategy dieRowDirectionStrategy)
{
DieTransferPathRequest request = new DieTransferPathRequest();
request.PadCandidates = padCandidates ?? Array.Empty<Pad>();
request.DieCandidates = dieCandidates ?? Array.Empty<Die>();
request.TransPathType = transPathType;
request.SkipNgDie = skipNgDie;
request.PadRowDirectionStrategy = padRowDirectionStrategy;
request.DieRowDirectionStrategy = dieRowDirectionStrategy;
return GenerateByRegion(request);
}
public DieTransferPathRegionPlan GenerateByRows(
IReadOnlyCollection<PadTransferRow> padRows,
IReadOnlyCollection<DieTransferRow> dieRows,
TransPathType transPathType)
{
IReadOnlyCollection<PadTransferRow> safePadRows = padRows ?? Array.Empty<PadTransferRow>();
IReadOnlyCollection<DieTransferRow> safeDieRows = dieRows ?? Array.Empty<DieTransferRow>();
List<Pad> orderedPads = GetOrderedPads(safePadRows);
List<Die> orderedDies = GetOrderedDies(safeDieRows);
List<DieTransferPathStep> steps = CreateSteps(orderedDies, orderedPads, transPathType);
List<Pad> remainingPads = CreateRemainingPads(orderedPads, steps);
List<Die> remainingDies = CreateRemainingDies(orderedDies, steps);
DieTransferPathRegionPlan pathPlan = new DieTransferPathRegionPlan();
pathPlan.TransPathType = transPathType;
pathPlan.AvailablePadCount = GetAvailablePadCount(safePadRows);
pathPlan.AvailableDieCount = GetAvailableDieCount(safeDieRows);
pathPlan.Steps = steps;
pathPlan.RemainingPads = remainingPads;
pathPlan.RemainingDies = remainingDies;
pathPlan.RemainingPadRows = CreateRemainingPadRows(safePadRows, remainingPads);
pathPlan.RemainingDieRows = CreateRemainingDieRows(safeDieRows, remainingDies);
pathPlan.RemainingSubstrateRegion = CreateRegionFromPads(remainingPads);
pathPlan.RemainingDieRegion = CreateRegionFromDies(remainingDies);
return pathPlan;
}
private static List<DieTransferPathStep> CreateSteps(IReadOnlyList<Die> dies, IReadOnlyList<Pad> pads, TransPathType transPathType)
{
return transPathType == TransPathType.Nearest
? BuildNearestSteps(dies, pads, transPathType)
: BuildSequenceSteps(dies, pads, transPathType);
}
private static List<DieTransferPathStep> BuildSequenceSteps(IReadOnlyList<Die> dies, IReadOnlyList<Pad> pads, TransPathType transPathType)
{
int stepCount = Math.Min(dies.Count, pads.Count);
List<DieTransferPathStep> steps = new List<DieTransferPathStep>(stepCount);
for (int i = 0; i < stepCount; i++)
{
steps.Add(CreateStep(i + 1, dies[i], pads[i], transPathType));
}
return steps;
}
private static List<DieTransferPathStep> BuildNearestSteps(IReadOnlyList<Die> dies, IReadOnlyList<Pad> pads, TransPathType transPathType)
{
List<Die> remainingDies = (dies ?? Array.Empty<Die>()).Where(die => die != null).ToList();
List<DieTransferPathStep> steps = new List<DieTransferPathStep>(Math.Min(remainingDies.Count, pads.Count));
int stepIndex = 1;
foreach (Pad pad in pads)
{
if (remainingDies.Count == 0)
{
break;
}
Die nearestDie = SelectNearestDie(pad, remainingDies);
steps.Add(CreateStep(stepIndex, nearestDie, pad, transPathType));
remainingDies.Remove(nearestDie);
stepIndex++;
}
return steps;
}
private static Die SelectNearestDie(Pad pad, IReadOnlyList<Die> dies)
{
Die selectedDie = null;
double bestDistance = double.MaxValue;
for (int index = 0; index < dies.Count; index++)
{
Die die = dies[index];
double currentDistance = CalculateSquaredDistance(die.X, die.Y, pad.X, pad.Y);
if (currentDistance < bestDistance)
{
selectedDie = die;
bestDistance = currentDistance;
}
}
return selectedDie;
}
private static double CalculateSquaredDistance(double sourceX, double sourceY, double targetX, double targetY)
{
double deltaX = sourceX - targetX;
double deltaY = sourceY - targetY;
return (deltaX * deltaX) + (deltaY * deltaY);
}
private static DieTransferPathStep CreateStep(int stepIndex, Die die, Pad pad, TransPathType transPathType)
{
DieTransferPathStep step = new DieTransferPathStep();
step.StepIndex = stepIndex;
step.DieRow = die.Row;
step.DieColumn = die.Column;
step.PadRow = pad.Row;
step.PadColumn = pad.Column;
step.DieX = die.X;
step.DieY = die.Y;
step.PadX = pad.X;
step.PadY = pad.Y;
step.TransPathType = transPathType;
return step;
}
private static int GetAvailablePadCount(IEnumerable<PadTransferRow> padRows)
{
return (padRows ?? Array.Empty<PadTransferRow>())
.Where(row => row != null)
.Sum(row => row.AvailableCount);
}
private static int GetAvailableDieCount(IEnumerable<DieTransferRow> dieRows)
{
return (dieRows ?? Array.Empty<DieTransferRow>())
.Where(row => row != null)
.Sum(row => row.AvailableCount);
}
private static List<Pad> GetOrderedPads(IEnumerable<PadTransferRow> padRows)
{
List<Pad> orderedPads = new List<Pad>();
foreach (PadTransferRow padRow in (padRows ?? Array.Empty<PadTransferRow>()).Where(row => row != null).OrderBy(row => row.RowIndex))
{
orderedPads.AddRange(padRow.GetAvailablePads());
}
return orderedPads;
}
private static List<Die> GetOrderedDies(IEnumerable<DieTransferRow> dieRows)
{
List<Die> orderedDies = new List<Die>();
foreach (DieTransferRow dieRow in (dieRows ?? Array.Empty<DieTransferRow>()).Where(row => row != null).OrderBy(row => row.RowIndex))
{
orderedDies.AddRange(dieRow.GetAvailableDies());
}
return orderedDies;
}
private static List<PadTransferRow> CreateRemainingPadRows(IEnumerable<PadTransferRow> sourceRows, IReadOnlyCollection<Pad> remainingPads)
{
HashSet<string> remainingPadKeys = CreatePointKeys(remainingPads, pad => pad.Row, pad => pad.Column);
List<PadTransferRow> remainingPadRows = new List<PadTransferRow>();
foreach (PadTransferRow sourceRow in (sourceRows ?? Array.Empty<PadTransferRow>()).Where(row => row != null).OrderBy(row => row.RowIndex))
{
List<Pad> rowPads = (sourceRow.Pads ?? new List<Pad>())
.Where(pad => pad != null && remainingPadKeys.Contains(CreatePointKey(pad.Row, pad.Column)))
.OrderBy(pad => pad.Column)
.ToList();
if (rowPads.Count == 0)
{
continue;
}
PadTransferRow remainingRow = new PadTransferRow();
remainingRow.RowIndex = sourceRow.RowIndex;
remainingRow.Direction = sourceRow.Direction;
remainingRow.Pads = rowPads;
remainingPadRows.Add(remainingRow);
}
return remainingPadRows;
}
private static List<DieTransferRow> CreateRemainingDieRows(IEnumerable<DieTransferRow> sourceRows, IReadOnlyCollection<Die> remainingDies)
{
HashSet<string> remainingDieKeys = CreatePointKeys(remainingDies, die => die.Row, die => die.Column);
List<DieTransferRow> remainingDieRows = new List<DieTransferRow>();
foreach (DieTransferRow sourceRow in (sourceRows ?? Array.Empty<DieTransferRow>()).Where(row => row != null).OrderBy(row => row.RowIndex))
{
List<Die> rowDies = (sourceRow.Dies ?? new List<Die>())
.Where(die => die != null && remainingDieKeys.Contains(CreatePointKey(die.Row, die.Column)))
.OrderBy(die => die.Column)
.ToList();
if (rowDies.Count == 0)
{
continue;
}
DieTransferRow remainingRow = new DieTransferRow();
remainingRow.RowIndex = sourceRow.RowIndex;
remainingRow.Direction = sourceRow.Direction;
remainingRow.SkipNgDie = sourceRow.SkipNgDie;
remainingRow.Dies = rowDies;
remainingDieRows.Add(remainingRow);
}
return remainingDieRows;
}
private static HashSet<string> CreatePointKeys<TPoint>(IEnumerable<TPoint> points, Func<TPoint, int> getRow, Func<TPoint, int> getColumn)
where TPoint : class
{
HashSet<string> pointKeys = new HashSet<string>();
foreach (TPoint point in points ?? Array.Empty<TPoint>())
{
if (point == null)
{
continue;
}
pointKeys.Add(CreatePointKey(getRow(point), getColumn(point)));
}
return pointKeys;
}
private static List<Pad> CreateRemainingPads(IReadOnlyList<Pad> orderedPads, IReadOnlyList<DieTransferPathStep> steps)
{
HashSet<string> usedPadKeys = new HashSet<string>();
foreach (DieTransferPathStep step in steps ?? Array.Empty<DieTransferPathStep>())
{
usedPadKeys.Add(CreatePointKey(step.PadRow, step.PadColumn));
}
return (orderedPads ?? Array.Empty<Pad>())
.Where(pad => pad != null && !usedPadKeys.Contains(CreatePointKey(pad.Row, pad.Column)))
.ToList();
}
private static List<Die> CreateRemainingDies(IReadOnlyList<Die> orderedDies, IReadOnlyList<DieTransferPathStep> steps)
{
HashSet<string> usedDieKeys = new HashSet<string>();
foreach (DieTransferPathStep step in steps ?? Array.Empty<DieTransferPathStep>())
{
usedDieKeys.Add(CreatePointKey(step.DieRow, step.DieColumn));
}
return (orderedDies ?? Array.Empty<Die>())
.Where(die => die != null && !usedDieKeys.Contains(CreatePointKey(die.Row, die.Column)))
.ToList();
}
private static string CreatePointKey(int row, int column)
{
return $"{row}:{column}";
}
private static DieTransferRegion CreateRegionFromPads(IEnumerable<Pad> pads)
{
List<Pad> availablePads = (pads ?? Array.Empty<Pad>()).Where(pad => pad != null).ToList();
if (availablePads.Count == 0)
{
return null;
}
DieTransferRegion region = new DieTransferRegion();
region.StartRow = availablePads.Min(pad => pad.Row);
region.StartCol = availablePads.Min(pad => pad.Column);
region.EndRow = availablePads.Max(pad => pad.Row);
region.EndCol = availablePads.Max(pad => pad.Column);
return region;
}
private static DieTransferRegion CreateRegionFromDies(IEnumerable<Die> dies)
{
List<Die> availableDies = (dies ?? Array.Empty<Die>()).Where(die => die != null).ToList();
if (availableDies.Count == 0)
{
return null;
}
DieTransferRegion region = new DieTransferRegion();
region.StartRow = availableDies.Min(die => die.Row);
region.StartCol = availableDies.Min(die => die.Column);
region.EndRow = availableDies.Max(die => die.Row);
region.EndCol = availableDies.Max(die => die.Column);
return region;
}
}
}

View File

@@ -0,0 +1,30 @@
using MainShell.Common;
using System;
using System.Collections.Generic;
namespace MainShell.Process
{
public class DieTransferPathPlan
{
public DieTransferPathPlan()
{
Steps = Array.Empty<DieTransferPathStep>();
}
public TransPathType TransPathType { get; set; }
public IReadOnlyList<DieTransferPathStep> Steps { get; set; }
public int AvailableDieCount { get; set; }
public int AvailablePadCount { get; set; }
public int GeneratedStepCount
{
get
{
return Steps == null ? 0 : Steps.Count;
}
}
}
}

View File

@@ -0,0 +1,30 @@
using MainShell.Models.Wafer;
using MainShell.Recipe.BaseBoard.Model;
using System;
using System.Collections.Generic;
namespace MainShell.Process
{
public class DieTransferPathRegionPlan : DieTransferPathPlan
{
public DieTransferPathRegionPlan()
{
RemainingDieRows = Array.Empty<DieTransferRow>();
RemainingPadRows = Array.Empty<PadTransferRow>();
RemainingDies = Array.Empty<Die>();
RemainingPads = Array.Empty<Pad>();
}
public DieTransferRegion RemainingDieRegion { get; set; }
public DieTransferRegion RemainingSubstrateRegion { get; set; }
public IReadOnlyList<DieTransferRow> RemainingDieRows { get; set; }
public IReadOnlyList<PadTransferRow> RemainingPadRows { get; set; }
public IReadOnlyList<Die> RemainingDies { get; set; }
public IReadOnlyList<Pad> RemainingPads { get; set; }
}
}

View File

@@ -0,0 +1,38 @@
using MainShell.Common;
using MainShell.Models;
using MainShell.Models.Wafer;
using MainShell.Recipe.BaseBoard.Model;
using System;
using System.Collections.Generic;
namespace MainShell.Process
{
public class DieTransferPathRequest
{
public DieTransferPathRequest()
{
DieCandidates = Array.Empty<Die>();
PadCandidates = Array.Empty<Pad>();
TransPathType = TransPathType.Sequence;
PadRowDirectionStrategy = DieTransferRowTraversalStrategy.AllPositive;
DieRowDirectionStrategy = DieTransferRowTraversalStrategy.Serpentine;
SkipNgDie = true;
}
public IReadOnlyCollection<Die> DieCandidates { get; set; }
public IReadOnlyCollection<Pad> PadCandidates { get; set; }
public RegionModel DieRegion { get; set; }
public RegionModel SubstrateRegion { get; set; }
public TransPathType TransPathType { get; set; }
public DieTransferRowTraversalStrategy PadRowDirectionStrategy { get; set; }
public DieTransferRowTraversalStrategy DieRowDirectionStrategy { get; set; }
public bool SkipNgDie { get; set; }
}
}

View File

@@ -0,0 +1,27 @@
using MainShell.Common;
namespace MainShell.Process
{
public class DieTransferPathStep
{
public int StepIndex { get; set; }
public int DieRow { get; set; }
public int DieColumn { get; set; }
public int PadRow { get; set; }
public int PadColumn { get; set; }
public double DieX { get; set; }
public double DieY { get; set; }
public double PadX { get; set; }
public double PadY { get; set; }
public TransPathType TransPathType { get; set; }
}
}

View File

@@ -0,0 +1,258 @@
using MainShell.Common;
using MainShell.Models;
using MainShell.Models.Wafer;
using MainShell.Recipe.BaseBoard.Model;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MainShell.Process
{
public class DieTransferPlanningContext
{
public DieTransferPlanningContext()
{
PadRows = new List<PadTransferRow>();
DieRows = new List<DieTransferRow>();
TransPathType = TransPathType.Sequence;
PadRowDirectionStrategy = DieTransferRowTraversalStrategy.AllPositive;
DieRowDirectionStrategy = DieTransferRowTraversalStrategy.Serpentine;
SkipNgDie = true;
}
public DieTransferRegion DieRegion { get; set; }
public DieTransferRegion SubstrateRegion { get; set; }
public TransPathType TransPathType { get; set; }
public DieTransferRowTraversalStrategy PadRowDirectionStrategy { get; set; }
public DieTransferRowTraversalStrategy DieRowDirectionStrategy { get; set; }
public bool SkipNgDie { get; set; }
public List<PadTransferRow> PadRows { get; set; }
public List<DieTransferRow> DieRows { get; set; }
public int AvailablePadCount
{
get
{
return PadRows == null ? 0 : PadRows.Sum(row => row.AvailableCount);
}
}
public int AvailableDieCount
{
get
{
return DieRows == null ? 0 : DieRows.Sum(row => row.AvailableCount);
}
}
public List<Pad> GetOrderedPads()
{
List<Pad> pads = new List<Pad>();
if (PadRows == null)
{
return pads;
}
foreach (PadTransferRow row in PadRows.OrderBy(item => item.RowIndex))
{
pads.AddRange(row.GetAvailablePads());
}
return pads;
}
public List<Die> GetOrderedDies()
{
List<Die> dies = new List<Die>();
if (DieRows == null)
{
return dies;
}
foreach (DieTransferRow row in DieRows.OrderBy(item => item.RowIndex))
{
dies.AddRange(row.GetAvailableDies());
}
return dies;
}
public static DieTransferPlanningContext Create(DieTransferPathRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
DieTransferPlanningContext context = new DieTransferPlanningContext();
context.DieRegion = DieTransferRegion.FromRegionModel(request.DieRegion);
context.SubstrateRegion = DieTransferRegion.FromRegionModel(request.SubstrateRegion);
context.TransPathType = request.TransPathType;
context.PadRowDirectionStrategy = request.PadRowDirectionStrategy;
context.DieRowDirectionStrategy = request.DieRowDirectionStrategy;
context.SkipNgDie = request.SkipNgDie;
context.PadRows = CreatePadRows(request.PadCandidates, context.SubstrateRegion, context.PadRowDirectionStrategy);
context.DieRows = CreateDieRows(request.DieCandidates, context.DieRegion, request.SkipNgDie, context.DieRowDirectionStrategy);
return context;
}
public static List<PadTransferRow> CreatePadRows(IReadOnlyCollection<Pad> padCandidates, DieTransferRegion region, DieTransferRowTraversalStrategy rowTraversalStrategy)
{
IEnumerable<Pad> filteredPads = (padCandidates ?? Array.Empty<Pad>())
.Where(pad => pad != null && IsInRegion(pad.Row, pad.Column, region));
List<PadTransferRow> rows = new List<PadTransferRow>();
foreach (IGrouping<int, Pad> rowGroup in filteredPads.GroupBy(pad => pad.Row).OrderBy(group => group.Key))
{
PadTransferRow row = new PadTransferRow();
row.RowIndex = rowGroup.Key;
row.Direction = ResolveRowDirection(rowGroup.Key, rowTraversalStrategy);
row.Pads = rowGroup.OrderBy(pad => pad.Column).ToList();
rows.Add(row);
}
return rows;
}
public static List<DieTransferRow> CreateDieRows(IReadOnlyCollection<Die> dieCandidates, DieTransferRegion region, bool skipNgDie, DieTransferRowTraversalStrategy rowTraversalStrategy)
{
IEnumerable<Die> filteredDies = (dieCandidates ?? Array.Empty<Die>())
.Where(die => die != null && IsInRegion(die.Row, die.Column, region));
List<DieTransferRow> rows = new List<DieTransferRow>();
foreach (IGrouping<int, Die> rowGroup in filteredDies.GroupBy(die => die.Row).OrderBy(group => group.Key))
{
DieTransferRow row = new DieTransferRow();
row.RowIndex = rowGroup.Key;
row.Direction = ResolveRowDirection(rowGroup.Key, rowTraversalStrategy);
row.SkipNgDie = skipNgDie;
row.Dies = rowGroup.OrderBy(die => die.Column).ToList();
rows.Add(row);
}
return rows;
}
public static List<PadTransferRow> TrimPadRows(IReadOnlyCollection<PadTransferRow> sourceRows, int targetCount, out List<PadTransferRow> remainingRows)
{
List<PadTransferRow> activeRows = new List<PadTransferRow>();
remainingRows = new List<PadTransferRow>();
int remainingCount = Math.Max(0, targetCount);
foreach (PadTransferRow sourceRow in (sourceRows ?? Array.Empty<PadTransferRow>()).Where(row => row != null).OrderBy(row => row.RowIndex))
{
List<Pad> availablePads = sourceRow.GetAvailablePads().ToList();
if (availablePads.Count == 0)
{
continue;
}
if (remainingCount <= 0)
{
remainingRows.Add(CreatePadRow(sourceRow.RowIndex, sourceRow.Direction, availablePads));
continue;
}
if (availablePads.Count <= remainingCount)
{
activeRows.Add(CreatePadRow(sourceRow.RowIndex, sourceRow.Direction, availablePads));
remainingCount -= availablePads.Count;
continue;
}
activeRows.Add(CreatePadRow(sourceRow.RowIndex, sourceRow.Direction, availablePads.Take(remainingCount)));
remainingRows.Add(CreatePadRow(sourceRow.RowIndex, sourceRow.Direction, availablePads.Skip(remainingCount)));
remainingCount = 0;
}
return activeRows;
}
public static List<DieTransferRow> TrimDieRows(IReadOnlyCollection<DieTransferRow> sourceRows, int targetCount, out List<DieTransferRow> remainingRows)
{
List<DieTransferRow> activeRows = new List<DieTransferRow>();
remainingRows = new List<DieTransferRow>();
int remainingCount = Math.Max(0, targetCount);
foreach (DieTransferRow sourceRow in (sourceRows ?? Array.Empty<DieTransferRow>()).Where(row => row != null).OrderBy(row => row.RowIndex))
{
List<Die> availableDies = sourceRow.GetAvailableDies().ToList();
if (availableDies.Count == 0)
{
continue;
}
if (remainingCount <= 0)
{
remainingRows.Add(CreateDieRow(sourceRow.RowIndex, sourceRow.Direction, sourceRow.SkipNgDie, availableDies));
continue;
}
if (availableDies.Count <= remainingCount)
{
activeRows.Add(CreateDieRow(sourceRow.RowIndex, sourceRow.Direction, sourceRow.SkipNgDie, availableDies));
remainingCount -= availableDies.Count;
continue;
}
activeRows.Add(CreateDieRow(sourceRow.RowIndex, sourceRow.Direction, sourceRow.SkipNgDie, availableDies.Take(remainingCount)));
remainingRows.Add(CreateDieRow(sourceRow.RowIndex, sourceRow.Direction, sourceRow.SkipNgDie, availableDies.Skip(remainingCount)));
remainingCount = 0;
}
return activeRows;
}
private static bool IsInRegion(int row, int column, DieTransferRegion region)
{
if (region == null)
{
return true;
}
return region.Contains(row, column);
}
private static DieTransferRowDirection ResolveRowDirection(int rowIndex, DieTransferRowTraversalStrategy rowTraversalStrategy)
{
switch (rowTraversalStrategy)
{
case DieTransferRowTraversalStrategy.AllNegative:
return DieTransferRowDirection.Negative;
case DieTransferRowTraversalStrategy.Serpentine:
return rowIndex % 2 == 0 ? DieTransferRowDirection.Negative : DieTransferRowDirection.Positive;
case DieTransferRowTraversalStrategy.AllPositive:
default:
return DieTransferRowDirection.Positive;
}
}
private static PadTransferRow CreatePadRow(int rowIndex, DieTransferRowDirection direction, IEnumerable<Pad> pads)
{
PadTransferRow row = new PadTransferRow();
row.RowIndex = rowIndex;
row.Direction = direction;
row.Pads = (pads ?? Array.Empty<Pad>()).Where(pad => pad != null).ToList();
return row;
}
private static DieTransferRow CreateDieRow(int rowIndex, DieTransferRowDirection direction, bool skipNgDie, IEnumerable<Die> dies)
{
DieTransferRow row = new DieTransferRow();
row.RowIndex = rowIndex;
row.Direction = direction;
row.SkipNgDie = skipNgDie;
row.Dies = (dies ?? Array.Empty<Die>()).Where(die => die != null).ToList();
return row;
}
}
}

View File

@@ -0,0 +1,52 @@
using MainShell.Models;
using System;
namespace MainShell.Process
{
public class DieTransferRegion
{
public int StartRow { get; set; }
public int StartCol { get; set; }
public int EndRow { get; set; }
public int EndCol { get; set; }
public int RowCount
{
get
{
return EndRow - StartRow + 1;
}
}
public int ColCount
{
get
{
return EndCol - StartCol + 1;
}
}
public bool Contains(int row, int column)
{
return row >= StartRow && row <= EndRow && column >= StartCol && column <= EndCol;
}
public static DieTransferRegion FromRegionModel(RegionModel regionModel)
{
if (regionModel == null)
{
return null;
}
DieTransferRegion region = new DieTransferRegion();
region.StartRow = regionModel.StartRow;
region.StartCol = regionModel.StartCol;
region.EndRow = regionModel.EndRow;
region.EndCol = regionModel.EndCol;
return region;
}
}
}

View File

@@ -0,0 +1,55 @@
using MainShell.Models.Wafer;
using System.Collections.Generic;
using System.Linq;
namespace MainShell.Process
{
public class DieTransferRow
{
public DieTransferRow()
{
Dies = new List<Die>();
Direction = DieTransferRowDirection.Positive;
}
public int RowIndex { get; set; }
public DieTransferRowDirection Direction { get; set; }
public bool SkipNgDie { get; set; }
public List<Die> Dies { get; set; }
public int AvailableCount
{
get
{
return GetAvailableDies().Count;
}
}
public IReadOnlyList<Die> GetOrderedDies()
{
if (Dies == null)
{
return new List<Die>();
}
IEnumerable<Die> orderedDies = Direction == DieTransferRowDirection.Positive
? Dies.OrderBy(die => die.Column)
: Dies.OrderByDescending(die => die.Column);
return orderedDies.ToList();
}
public IReadOnlyList<Die> GetAvailableDies()
{
IEnumerable<Die> availableDies = GetOrderedDies().Where(die => die != null);
if (SkipNgDie)
{
availableDies = availableDies.Where(die => die.Status != DieStatus.Ng);
}
return availableDies.ToList();
}
}
}

View File

@@ -0,0 +1,8 @@
namespace MainShell.Process
{
public enum DieTransferRowDirection
{
Positive = 0,
Negative = 1
}
}

View File

@@ -0,0 +1,27 @@
using MainShell.Common;
using MainShell.Models.Wafer;
using MainShell.Recipe.BaseBoard.Model;
using System.Collections.Generic;
namespace MainShell.Process
{
public interface IDieTransferPathGenerator
{
DieTransferPathPlan Generate(DieTransferPathRequest request);
DieTransferPathRegionPlan GenerateByRegion(DieTransferPathRequest request);
DieTransferPathRegionPlan GenerateByCandidates(
IReadOnlyCollection<Pad> padCandidates,
IReadOnlyCollection<Die> dieCandidates,
TransPathType transPathType,
bool skipNgDie,
DieTransferRowTraversalStrategy padRowDirectionStrategy,
DieTransferRowTraversalStrategy dieRowDirectionStrategy);
DieTransferPathRegionPlan GenerateByRows(
IReadOnlyCollection<PadTransferRow> padRows,
IReadOnlyCollection<DieTransferRow> dieRows,
TransPathType transPathType);
}
}

View File

@@ -0,0 +1,47 @@
using MainShell.Recipe.BaseBoard.Model;
using System.Collections.Generic;
using System.Linq;
namespace MainShell.Process
{
public class PadTransferRow
{
public PadTransferRow()
{
Pads = new List<Pad>();
Direction = DieTransferRowDirection.Positive;
}
public int RowIndex { get; set; }
public DieTransferRowDirection Direction { get; set; }
public List<Pad> Pads { get; set; }
public int AvailableCount
{
get
{
return Pads == null ? 0 : Pads.Count(pad => pad != null);
}
}
public IReadOnlyList<Pad> GetOrderedPads()
{
if (Pads == null)
{
return new List<Pad>();
}
IEnumerable<Pad> orderedPads = Direction == DieTransferRowDirection.Positive
? Pads.OrderBy(pad => pad.Column)
: Pads.OrderByDescending(pad => pad.Column);
return orderedPads.ToList();
}
public IReadOnlyList<Pad> GetAvailablePads()
{
return GetOrderedPads().Where(pad => pad != null).ToList();
}
}
}

View File

@@ -0,0 +1,79 @@
using MainShell.Common;
using MW.WorkFlow;
using System.Threading.Tasks;
namespace MainShell.Process
{
public class PreTransferValidationActivity : ActivityAbstractBase
{
public PreTransferValidationActivity(string name)
: base(name)
{
}
protected override Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
{
activityControl.ThrowIfCancellationRequested();
SubstrateLifecycleState substrateState;
if (!context.TryGetData<SubstrateLifecycleState>(WorkflowContextKeys.SubstrateProcessState, out substrateState) ||
substrateState != SubstrateLifecycleState.HeightMeasured)
{
return Task.FromResult(Fail(
context,
MessageKey.ProcessStepFailedWithReason,
new object[]
{
Name,
"<22><><EFBFBD><EFBFBD>״̬δ<CCAC><CEB4><EFBFBD>ɲ<EFBFBD><C9B2><EFBFBD>У<EFBFBD>顣"
}));
}
bool pendingChipLoad;
if (!context.TryGetData<bool>(WorkflowContextKeys.PendingChipLoad, out pendingChipLoad))
{
pendingChipLoad = false;
}
if (pendingChipLoad)
{
return Task.FromResult(Fail(
context,
MessageKey.ProcessStepFailedWithReason,
new object[]
{
Name,
"оƬ<D0BE><C6AC>δ<EFBFBD><CEB4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͬ<EFBFBD><CDAC><EFBFBD><EFBFBD>"
}));
}
CurrentChipLifecycleState chipState;
if (!context.TryGetData<CurrentChipLifecycleState>(WorkflowContextKeys.CurrentChipState, out chipState))
{
return Task.FromResult(Fail(
context,
MessageKey.ProcessStepFailedWithReason,
new object[]
{
Name,
"<22><>ǰоƬ״̬ȱʧ<C8B1><CAA7>"
}));
}
if (chipState != CurrentChipLifecycleState.LoadedPendingTransfer &&
chipState != CurrentChipLifecycleState.InUse)
{
return Task.FromResult(Fail(
context,
MessageKey.ProcessStepFailedWithReason,
new object[]
{
Name,
"оƬ״̬δ<CCAC><EFBFBD><EFB5BD>װǰҪ<C7B0><D2AA><EFBFBD><EFBFBD>"
}));
}
return Task.FromResult(ActivityResult.Success);
}
}
}

View File

@@ -0,0 +1,27 @@
using MW.WorkFlow;
using System.Diagnostics;
using System.Threading.Tasks;
namespace MainShell.Process
{
public class WaferAngleAdjustmentActivity : ActivityAbstractBase
{
public WaferAngleAdjustmentActivity(string name) : base(name)
{
}
protected override async Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
{
Trace.WriteLine("Executing WaferAngleAdjustmentActivity");
for (int i = 0; i < 20; i++)
{
activityControl.ThrowIfCancellationRequested();
await activityControl.CheckPauseAsync().ConfigureAwait(false);
await Task.Delay(100, activityControl.CancellationToken).ConfigureAwait(false);
}
return ActivityResult.Success;
}
}
}