添加 MX-PD-盘古 项目文件
将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
using MainShell.ProcessResult;
|
||||
using MainShell.Recipe.Models;
|
||||
using System;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class AutoProductionRuntimeStateService
|
||||
{
|
||||
private readonly ProcessResultManager _processResultManager;
|
||||
|
||||
public AutoProductionRuntimeStateService(ProcessResultManager processResultManager)
|
||||
{
|
||||
_processResultManager = processResultManager ?? throw new ArgumentNullException(nameof(processResultManager));
|
||||
}
|
||||
|
||||
public int CurrentSubstratePendingCount
|
||||
{
|
||||
get { return _processResultManager.AutoProductionRuntimeState.CurrentSubstratePendingCount; }
|
||||
}
|
||||
|
||||
public int CurrentSubstrateProcessedCount
|
||||
{
|
||||
get { return _processResultManager.AutoProductionRuntimeState.CurrentSubstrateProcessedCount; }
|
||||
}
|
||||
|
||||
public int CurrentChipRemainingCount
|
||||
{
|
||||
get { return _processResultManager.AutoProductionRuntimeState.CurrentChipRemainingCount; }
|
||||
}
|
||||
|
||||
public bool TransferNeedsRecheck
|
||||
{
|
||||
get { return _processResultManager.AutoProductionRuntimeState.TransferNeedsRecheck; }
|
||||
}
|
||||
|
||||
public bool TransferChipExhausted
|
||||
{
|
||||
get { return _processResultManager.AutoProductionRuntimeState.TransferChipExhausted; }
|
||||
}
|
||||
|
||||
public void PrepareForRun(RecipeManager recipeManager, CurrentChipLifecycleState currentChipState, bool isResume)
|
||||
{
|
||||
if (isResume)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int defaultSubstrateTargetCount = ResolveDefaultSubstrateTargetCount(recipeManager);
|
||||
AutoProductionRuntimeState runtimeState = CloneState();
|
||||
|
||||
runtimeState.CurrentSubstratePendingCount = Math.Max(0, defaultSubstrateTargetCount);
|
||||
runtimeState.CurrentSubstrateProcessedCount = 0;
|
||||
|
||||
if (currentChipState == CurrentChipLifecycleState.Empty)
|
||||
{
|
||||
runtimeState.CurrentChipRemainingCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
runtimeState.CurrentChipRemainingCount = 1;
|
||||
}
|
||||
|
||||
runtimeState.TransferNeedsRecheck = false;
|
||||
runtimeState.TransferChipExhausted = false;
|
||||
runtimeState.LastUpdatedTime = DateTime.Now;
|
||||
_processResultManager.DashboardState.PendingCount = runtimeState.CurrentSubstratePendingCount;
|
||||
_processResultManager.DashboardState.ProcessedCount = runtimeState.CurrentSubstrateProcessedCount;
|
||||
_processResultManager.SaveDashboardState();
|
||||
_processResultManager.SaveAutoProductionRuntimeState(runtimeState);
|
||||
}
|
||||
|
||||
public bool ConsumeSubstrateTarget()
|
||||
{
|
||||
AutoProductionRuntimeState runtimeState = CloneState();
|
||||
if (runtimeState.CurrentSubstratePendingCount <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
runtimeState.CurrentSubstratePendingCount = Math.Max(0, runtimeState.CurrentSubstratePendingCount - 1);
|
||||
runtimeState.CurrentSubstrateProcessedCount = runtimeState.CurrentSubstrateProcessedCount + 1;
|
||||
runtimeState.LastUpdatedTime = DateTime.Now;
|
||||
_processResultManager.DashboardState.PendingCount = runtimeState.CurrentSubstratePendingCount;
|
||||
_processResultManager.DashboardState.ProcessedCount = runtimeState.CurrentSubstrateProcessedCount;
|
||||
_processResultManager.SaveDashboardState();
|
||||
_processResultManager.SaveAutoProductionRuntimeState(runtimeState);
|
||||
return runtimeState.CurrentSubstratePendingCount == 0;
|
||||
}
|
||||
|
||||
public bool ConsumeCurrentChip()
|
||||
{
|
||||
AutoProductionRuntimeState runtimeState = CloneState();
|
||||
if (runtimeState.CurrentChipRemainingCount <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
runtimeState.CurrentChipRemainingCount = Math.Max(0, runtimeState.CurrentChipRemainingCount - 1);
|
||||
runtimeState.LastUpdatedTime = DateTime.Now;
|
||||
_processResultManager.SaveAutoProductionRuntimeState(runtimeState);
|
||||
return runtimeState.CurrentChipRemainingCount == 0;
|
||||
}
|
||||
|
||||
public bool ConsumeTransferNeedsRecheck()
|
||||
{
|
||||
if (!_processResultManager.AutoProductionRuntimeState.TransferNeedsRecheck)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoProductionRuntimeState runtimeState = CloneState();
|
||||
runtimeState.TransferNeedsRecheck = false;
|
||||
runtimeState.LastUpdatedTime = DateTime.Now;
|
||||
_processResultManager.SaveAutoProductionRuntimeState(runtimeState);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ConsumeTransferChipExhausted()
|
||||
{
|
||||
if (!_processResultManager.AutoProductionRuntimeState.TransferChipExhausted)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoProductionRuntimeState runtimeState = CloneState();
|
||||
runtimeState.TransferChipExhausted = false;
|
||||
runtimeState.LastUpdatedTime = DateTime.Now;
|
||||
_processResultManager.SaveAutoProductionRuntimeState(runtimeState);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetCurrentChipRemainingCount(int remainingCount)
|
||||
{
|
||||
AutoProductionRuntimeState runtimeState = CloneState();
|
||||
runtimeState.CurrentChipRemainingCount = Math.Max(0, remainingCount);
|
||||
runtimeState.LastUpdatedTime = DateTime.Now;
|
||||
_processResultManager.SaveAutoProductionRuntimeState(runtimeState);
|
||||
}
|
||||
|
||||
public void MarkTransferNeedsRecheck()
|
||||
{
|
||||
AutoProductionRuntimeState runtimeState = CloneState();
|
||||
runtimeState.TransferNeedsRecheck = true;
|
||||
runtimeState.LastUpdatedTime = DateTime.Now;
|
||||
_processResultManager.SaveAutoProductionRuntimeState(runtimeState);
|
||||
}
|
||||
|
||||
public void MarkTransferChipExhausted()
|
||||
{
|
||||
AutoProductionRuntimeState runtimeState = CloneState();
|
||||
runtimeState.TransferChipExhausted = true;
|
||||
runtimeState.LastUpdatedTime = DateTime.Now;
|
||||
_processResultManager.SaveAutoProductionRuntimeState(runtimeState);
|
||||
}
|
||||
|
||||
private AutoProductionRuntimeState CloneState()
|
||||
{
|
||||
AutoProductionRuntimeState source = _processResultManager.AutoProductionRuntimeState;
|
||||
return new AutoProductionRuntimeState
|
||||
{
|
||||
CurrentSubstratePendingCount = source.CurrentSubstratePendingCount,
|
||||
CurrentSubstrateProcessedCount = source.CurrentSubstrateProcessedCount,
|
||||
CurrentChipRemainingCount = source.CurrentChipRemainingCount,
|
||||
TransferNeedsRecheck = source.TransferNeedsRecheck,
|
||||
TransferChipExhausted = source.TransferChipExhausted,
|
||||
LastUpdatedTime = source.LastUpdatedTime
|
||||
};
|
||||
}
|
||||
|
||||
private static int ResolveDefaultSubstrateTargetCount(RecipeManager recipeManager)
|
||||
{
|
||||
if (recipeManager == null || recipeManager.CurrentSubstrateRecipe == null || recipeManager.CurrentSubstrateRecipe.SubstrateInfo == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rowNumber = Math.Max(0, recipeManager.CurrentSubstrateRecipe.SubstrateInfo.RowNumber);
|
||||
int colNumber = Math.Max(0, recipeManager.CurrentSubstrateRecipe.SubstrateInfo.ColNumber);
|
||||
if (rowNumber == 0 || colNumber == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return rowNumber * colNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.ProcessResult;
|
||||
using MainShell.Recipe.Models;
|
||||
using MW.WorkFlow;
|
||||
using Stylet;
|
||||
using System;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class AutoProductionWorkflowBuilder
|
||||
{
|
||||
private readonly RecipeManager _recipeManager;
|
||||
private readonly ProcessResultManager _processResultManager;
|
||||
private readonly IWorkflowRuntimeTracker _workflowRuntimeTracker;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly PreparationAreaService _preparationAreaService;
|
||||
private readonly CurrentChipStateService _currentChipStateService;
|
||||
private readonly AutoProductionRuntimeStateService _autoProductionRuntimeStateService;
|
||||
private readonly DieTransferMotionService _dieTransferMotionService;
|
||||
private readonly SubstratePositionMotionService _substratePositionMotionService;
|
||||
private readonly SubstrateHeightMeasureService _substrateHeightMeasureService;
|
||||
private readonly DiePositionService _diePositionService;
|
||||
private readonly DieRecheckService _dieRecheckService;
|
||||
|
||||
public AutoProductionWorkflowBuilder(
|
||||
RecipeManager recipeManager,
|
||||
ProcessResultManager processResultManager,
|
||||
IWorkflowRuntimeTracker workflowRuntimeTracker,
|
||||
IEventAggregator eventAggregator,
|
||||
PreparationAreaService preparationAreaService,
|
||||
CurrentChipStateService currentChipStateService,
|
||||
AutoProductionRuntimeStateService autoProductionRuntimeStateService,
|
||||
DieTransferMotionService dieTransferMotionService,
|
||||
SubstratePositionMotionService substratePositionMotionService,
|
||||
SubstrateHeightMeasureService substrateHeightMeasureService,
|
||||
DiePositionService diePositionService,
|
||||
DieRecheckService dieRecheckService)
|
||||
{
|
||||
_recipeManager = recipeManager ?? throw new ArgumentNullException(nameof(recipeManager));
|
||||
_processResultManager = processResultManager ?? throw new ArgumentNullException(nameof(processResultManager));
|
||||
_workflowRuntimeTracker = workflowRuntimeTracker ?? throw new ArgumentNullException(nameof(workflowRuntimeTracker));
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_preparationAreaService = preparationAreaService ?? throw new ArgumentNullException(nameof(preparationAreaService));
|
||||
_currentChipStateService = currentChipStateService ?? throw new ArgumentNullException(nameof(currentChipStateService));
|
||||
_autoProductionRuntimeStateService = autoProductionRuntimeStateService ?? throw new ArgumentNullException(nameof(autoProductionRuntimeStateService));
|
||||
_dieTransferMotionService = dieTransferMotionService ?? throw new ArgumentNullException(nameof(dieTransferMotionService));
|
||||
_substratePositionMotionService = substratePositionMotionService ?? throw new ArgumentNullException(nameof(substratePositionMotionService));
|
||||
_substrateHeightMeasureService = substrateHeightMeasureService ?? throw new ArgumentNullException(nameof(substrateHeightMeasureService));
|
||||
_diePositionService = diePositionService ?? throw new ArgumentNullException(nameof(diePositionService));
|
||||
_dieRecheckService = dieRecheckService ?? throw new ArgumentNullException(nameof(dieRecheckService));
|
||||
}
|
||||
|
||||
public WorkflowStartRequest Create(string selectedFlowName)
|
||||
{
|
||||
WorkflowDefinition definition = CreateAutoProductionWorkflowDefinition();
|
||||
WorkflowContext context = CreateAutoWorkflowContext();
|
||||
string startStepId = ResolveRequestedStartStepId(selectedFlowName);
|
||||
return new WorkflowStartRequest(definition, context, startStepId);
|
||||
}
|
||||
|
||||
private WorkflowContext CreateAutoWorkflowContext()
|
||||
{
|
||||
string resumeStepId;
|
||||
bool isResume = _processResultManager.TryGetResumeStepId(ProcessFlowName.AutoProduction, out resumeStepId);
|
||||
CurrentChipLifecycleState currentChipState = ResolveInitialChipState(_currentChipStateService.CurrentState, _preparationAreaService.CurrentStatus, resumeStepId, isResume);
|
||||
bool pendingChipLoad = ResolveInitialPendingChipLoad(currentChipState);
|
||||
SubstrateLifecycleState substrateProcessState = ResolveInitialSubstrateState(resumeStepId, isResume);
|
||||
|
||||
_autoProductionRuntimeStateService.PrepareForRun(_recipeManager, currentChipState, isResume);
|
||||
|
||||
WorkflowContext context = new WorkflowContext();
|
||||
context[WorkflowContextKeys.EventAggregator] = _eventAggregator;
|
||||
context[WorkflowContextKeys.RecipeManager] = _recipeManager;
|
||||
context[WorkflowContextKeys.ProcessResultManager] = _processResultManager;
|
||||
context[WorkflowContextKeys.WorkflowRuntimeTracker] = _workflowRuntimeTracker;
|
||||
context[WorkflowContextKeys.WorkflowName] = ProcessFlowName.AutoProduction;
|
||||
context[WorkflowContextKeys.PreparationAreaService] = _preparationAreaService;
|
||||
context[WorkflowContextKeys.CurrentChipStateService] = _currentChipStateService;
|
||||
context[WorkflowContextKeys.AutoProductionRuntimeStateService] = _autoProductionRuntimeStateService;
|
||||
context[WorkflowContextKeys.PreparationAreaStatus] = _preparationAreaService.CurrentStatus;
|
||||
context[WorkflowContextKeys.PendingChipLoad] = pendingChipLoad;
|
||||
context[WorkflowContextKeys.CurrentChipState] = currentChipState;
|
||||
context[WorkflowContextKeys.SubstrateProcessState] = substrateProcessState;
|
||||
context[WorkflowContextKeys.CurrentSubstratePendingCount] = _autoProductionRuntimeStateService.CurrentSubstratePendingCount;
|
||||
context[WorkflowContextKeys.CurrentSubstrateProcessedCount] = _autoProductionRuntimeStateService.CurrentSubstrateProcessedCount;
|
||||
context[WorkflowContextKeys.CurrentChipRemainingCount] = _autoProductionRuntimeStateService.CurrentChipRemainingCount;
|
||||
context[WorkflowContextKeys.TransferNeedsRecheck] = _autoProductionRuntimeStateService.TransferNeedsRecheck;
|
||||
context[WorkflowContextKeys.TransferChipExhausted] = _autoProductionRuntimeStateService.TransferChipExhausted;
|
||||
return context;
|
||||
}
|
||||
|
||||
private static CurrentChipLifecycleState ResolveInitialChipState(CurrentChipLifecycleState currentChipState, ChipPreparationStatus preparationStatus, string resumeStepId, bool isResume)
|
||||
{
|
||||
if (!isResume)
|
||||
{
|
||||
return currentChipState;
|
||||
}
|
||||
|
||||
if (currentChipState != CurrentChipLifecycleState.Empty)
|
||||
{
|
||||
return currentChipState;
|
||||
}
|
||||
|
||||
if (preparationStatus == ChipPreparationStatus.Loading)
|
||||
{
|
||||
return CurrentChipLifecycleState.Loading;
|
||||
}
|
||||
|
||||
if (preparationStatus == ChipPreparationStatus.Loaded && IsPreTransferOrSyncStep(resumeStepId))
|
||||
{
|
||||
return CurrentChipLifecycleState.LoadedPendingTransfer;
|
||||
}
|
||||
|
||||
return currentChipState;
|
||||
}
|
||||
|
||||
private static bool ResolveInitialPendingChipLoad(CurrentChipLifecycleState currentChipState)
|
||||
{
|
||||
return currentChipState == CurrentChipLifecycleState.Empty ||
|
||||
currentChipState == CurrentChipLifecycleState.Loading;
|
||||
}
|
||||
|
||||
private static SubstrateLifecycleState ResolveInitialSubstrateState(string resumeStepId, bool isResume)
|
||||
{
|
||||
if (!isResume || string.IsNullOrWhiteSpace(resumeStepId))
|
||||
{
|
||||
return SubstrateLifecycleState.NotLoaded;
|
||||
}
|
||||
|
||||
if (string.Equals(resumeStepId, WorkflowStepIds.AutoProduction.SubstratePositionEntry, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return SubstrateLifecycleState.Loaded;
|
||||
}
|
||||
|
||||
if (string.Equals(resumeStepId, WorkflowStepIds.AutoProduction.SubstrateHeightMeasureEntry, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return SubstrateLifecycleState.Positioned;
|
||||
}
|
||||
|
||||
if (IsPreTransferOrSyncStep(resumeStepId))
|
||||
{
|
||||
return SubstrateLifecycleState.HeightMeasured;
|
||||
}
|
||||
|
||||
if (string.Equals(resumeStepId, WorkflowStepIds.AutoProduction.ChipUnloadEntry, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return SubstrateLifecycleState.InProcess;
|
||||
}
|
||||
|
||||
if (string.Equals(resumeStepId, WorkflowStepIds.AutoProduction.SubstrateUnloadEntry, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return SubstrateLifecycleState.Complete;
|
||||
}
|
||||
|
||||
return SubstrateLifecycleState.NotLoaded;
|
||||
}
|
||||
|
||||
private static bool IsPreTransferOrSyncStep(string resumeStepId)
|
||||
{
|
||||
return string.Equals(resumeStepId, WorkflowStepIds.AutoProduction.ChipPreparationSyncEntry, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(resumeStepId, WorkflowStepIds.AutoProduction.PreTransferValidationEntry, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(resumeStepId, WorkflowStepIds.AutoProduction.ChipStraighteningEntry, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(resumeStepId, WorkflowStepIds.AutoProduction.DiePositionEntry, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(resumeStepId, WorkflowStepIds.AutoProduction.DieTransferEntry, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(resumeStepId, WorkflowStepIds.AutoProduction.DieRecheckEntry, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private WorkflowDefinition CreateAutoProductionWorkflowDefinition()
|
||||
{
|
||||
string substrateLoadStepId = WorkflowStepIdResolver.GetAutoProductionEntryStepId(ProcessFlowName.SubstrateLoadFlow);
|
||||
string substratePositionStepId = WorkflowStepIdResolver.GetAutoProductionEntryStepId(ProcessFlowName.SubstratePositionFlow);
|
||||
string substrateHeightMeasureStepId = WorkflowStepIdResolver.GetAutoProductionEntryStepId(ProcessFlowName.SubstrateHeightMeasureFlow);
|
||||
string chipPreparationSyncStepId = WorkflowStepIdResolver.GetAutoProductionEntryStepId(ProcessFlowName.ChipPreparationSyncFlow);
|
||||
string preTransferValidationStepId = WorkflowStepIdResolver.GetAutoProductionEntryStepId(ProcessFlowName.PreTransferValidationFlow);
|
||||
string waferAngleAdjustmentStepId = WorkflowStepIdResolver.GetAutoProductionEntryStepId(ProcessFlowName.ChipStraighteningFlow);
|
||||
string diePositionStepId = WorkflowStepIdResolver.GetAutoProductionEntryStepId(ProcessFlowName.DiePositionFlow);
|
||||
string dieTransferStepId = WorkflowStepIdResolver.GetAutoProductionEntryStepId(ProcessFlowName.DieTransferFlow);
|
||||
string dieRecheckStepId = WorkflowStepIdResolver.GetAutoProductionEntryStepId(ProcessFlowName.DieRecheckFlow);
|
||||
string chipUnloadStepId = WorkflowStepIdResolver.GetAutoProductionEntryStepId(ProcessFlowName.ChipUnloadFlow);
|
||||
string substrateUnloadStepId = WorkflowStepIdResolver.GetAutoProductionEntryStepId(ProcessFlowName.SubstrateUnloadFlow);
|
||||
|
||||
WorkflowDefinition definition = new WorkflowDefinition(substrateLoadStepId);
|
||||
|
||||
definition.AddStep(
|
||||
new WorkflowStep(
|
||||
substrateLoadStepId,
|
||||
new CompositeActivity(
|
||||
ProcessFlowName.SubstrateLoadFlow,
|
||||
new IActivity[]
|
||||
{
|
||||
new PreparationSignalActivity(ProcessFlowName.PreparationSignalFlow),
|
||||
new ChipPreparationAutoLoadStartActivity(ProcessFlowName.ChipPreparationAutoLoadStartFlow),
|
||||
new SubstrateLoadActivity(ProcessFlowName.SubstrateLoadFlow)
|
||||
}))
|
||||
.WithFlowName(ProcessFlowName.SubstrateLoadFlow)
|
||||
.WithNextStep(substratePositionStepId));
|
||||
|
||||
definition.AddStep(CreateFlowStep(
|
||||
substratePositionStepId,
|
||||
ProcessFlowName.SubstratePositionFlow,
|
||||
new SubstratePositionActivity(ProcessFlowName.SubstratePositionFlow, _substratePositionMotionService),
|
||||
substrateHeightMeasureStepId));
|
||||
|
||||
definition.AddStep(
|
||||
new WorkflowStep(
|
||||
substrateHeightMeasureStepId,
|
||||
new SubstrateHeightMeasureActivity(ProcessFlowName.SubstrateHeightMeasureFlow, _substrateHeightMeasureService))
|
||||
.WithFlowName(ProcessFlowName.SubstrateHeightMeasureFlow)
|
||||
.WithNextStep(chipPreparationSyncStepId));
|
||||
|
||||
definition.AddStep(CreateFlowStep(
|
||||
chipPreparationSyncStepId,
|
||||
ProcessFlowName.ChipPreparationSyncFlow,
|
||||
new ChipPreparationSyncActivity(ProcessFlowName.ChipPreparationSyncFlow),
|
||||
preTransferValidationStepId));
|
||||
|
||||
definition.AddStep(CreateFlowStep(
|
||||
preTransferValidationStepId,
|
||||
ProcessFlowName.PreTransferValidationFlow,
|
||||
new PreTransferValidationActivity(ProcessFlowName.PreTransferValidationFlow),
|
||||
waferAngleAdjustmentStepId));
|
||||
|
||||
definition.AddStep(CreateFlowStep(
|
||||
waferAngleAdjustmentStepId,
|
||||
ProcessFlowName.ChipStraighteningFlow,
|
||||
new WaferAngleAdjustmentActivity(ProcessFlowName.ChipStraighteningFlow),
|
||||
diePositionStepId));
|
||||
|
||||
definition.AddStep(CreateFlowStep(
|
||||
diePositionStepId,
|
||||
ProcessFlowName.DiePositionFlow,
|
||||
new DiePositionActivity(ProcessFlowName.DiePositionFlow, _diePositionService),
|
||||
dieTransferStepId));
|
||||
|
||||
definition.AddStep(CreateDieTransferStep(dieTransferStepId, waferAngleAdjustmentStepId, dieRecheckStepId, chipUnloadStepId, substrateUnloadStepId));
|
||||
|
||||
definition.AddStep(CreateFlowStep(
|
||||
dieRecheckStepId,
|
||||
ProcessFlowName.DieRecheckFlow,
|
||||
new DieRecheckActivity(ProcessFlowName.DieRecheckFlow, _dieRecheckService),
|
||||
dieTransferStepId));
|
||||
|
||||
definition.AddStep(
|
||||
new WorkflowStep(
|
||||
chipUnloadStepId,
|
||||
new ChipUnloadActivity(ProcessFlowName.ChipUnloadFlow))
|
||||
.WithFlowName(ProcessFlowName.ChipUnloadFlow)
|
||||
.AddJumpOnRouteValueOnSuccess(WorkflowContextKeys.AutoProductionRoute, AutoProductionRoute.BothExhausted, substrateUnloadStepId)
|
||||
.WithNextStep(substrateLoadStepId));
|
||||
|
||||
definition.AddStep(
|
||||
new WorkflowStep(
|
||||
substrateUnloadStepId,
|
||||
new SubstrateUnloadActivity(ProcessFlowName.SubstrateUnloadFlow))
|
||||
.WithFlowName(ProcessFlowName.SubstrateUnloadFlow)
|
||||
.WithNextStep(substrateLoadStepId));
|
||||
|
||||
return definition;
|
||||
}
|
||||
|
||||
private WorkflowStep CreateDieTransferStep(string stepId, string continueStepId, string recheckStepId, string chipUnloadStepId, string unloadStepId)
|
||||
{
|
||||
return new WorkflowStep(stepId, CreateDieTransferExecutionActivity())
|
||||
.WithFlowName(ProcessFlowName.DieTransferFlow)
|
||||
.AddJumpOnRouteValueOnSuccess(WorkflowContextKeys.AutoProductionRoute, AutoProductionRoute.Recheck, recheckStepId)
|
||||
.AddJumpOnRouteValueOnSuccess(WorkflowContextKeys.AutoProductionRoute, AutoProductionRoute.ContinueCurrentSubstrate, continueStepId)
|
||||
.AddJumpOnRouteValueOnSuccess(WorkflowContextKeys.AutoProductionRoute, AutoProductionRoute.ChipExhausted, chipUnloadStepId)
|
||||
.AddJumpOnRouteValueOnSuccess(WorkflowContextKeys.AutoProductionRoute, AutoProductionRoute.BothExhausted, chipUnloadStepId)
|
||||
.AddJumpOnRouteValueOnSuccess(WorkflowContextKeys.AutoProductionRoute, AutoProductionRoute.SubstrateComplete, unloadStepId)
|
||||
.WithNextStep(unloadStepId);
|
||||
}
|
||||
|
||||
private IActivity CreateDieTransferExecutionActivity()
|
||||
{
|
||||
return new CompositeActivity(
|
||||
ProcessFlowName.DieTransferFlow,
|
||||
new IActivity[]
|
||||
{
|
||||
new DieTransferActivity(ProcessFlowName.DieTransferFlow, _dieTransferMotionService),
|
||||
new ChipPreparationConsumeActivity("ChipLoadConsume")
|
||||
});
|
||||
}
|
||||
|
||||
private static WorkflowStep CreateFlowStep(string stepId, string flowName, IActivity activity, string defaultNextStepId)
|
||||
{
|
||||
WorkflowStep step = new WorkflowStep(stepId, activity)
|
||||
.WithFlowName(flowName);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(defaultNextStepId))
|
||||
{
|
||||
step.WithNextStep(defaultNextStepId);
|
||||
}
|
||||
|
||||
return step;
|
||||
}
|
||||
|
||||
private string ResolveRequestedStartStepId(string selectedFlowName)
|
||||
{
|
||||
if (_processResultManager.TryGetResumeStepId(ProcessFlowName.AutoProduction, out var _))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return WorkflowStepIdResolver.GetAutoProductionEntryStepId(selectedFlowName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.DeviceMaintance.Model;
|
||||
using MainShell.EventArgsFolder;
|
||||
using MainShell.Parameter;
|
||||
using MainShell.ParaSetting.Model;
|
||||
using MaxwellFramework.Core.Attributes;
|
||||
using MW.WorkFlow;
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using StyletIoC;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
[Singleton]
|
||||
public class NeedleZCalibrationActivity : ActivityAbstractBase
|
||||
{
|
||||
private readonly NeedleZCalibrationService _needleZCalibrationService;
|
||||
|
||||
|
||||
public NeedleZCalibrationActivity(NeedleZCalibrationService needleZCalibrationService) : base("NeedleZCalibrationActivity")
|
||||
{
|
||||
_needleZCalibrationService = needleZCalibrationService ?? throw new ArgumentNullException(nameof(needleZCalibrationService));
|
||||
}
|
||||
|
||||
protected override async Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
NeedleZCalibrationWorkflowData workflowData = GetWorkflowData(context);
|
||||
IParamList paramList = workflowData.GetParameterList();
|
||||
NeedleCalibrationSetting needleCalibrationSetting = paramList?.GetParameter<NeedleCalibrationSetting>();
|
||||
EquipmentParaSysSetting equipmentParaSysSetting = paramList?.GetParameter<EquipmentParaSysSetting>();
|
||||
|
||||
try
|
||||
{
|
||||
NeedleZCalibrationExecutionContext executionContext = _needleZCalibrationService.CreateExecutionContext(
|
||||
needleCalibrationSetting,
|
||||
equipmentParaSysSetting,
|
||||
workflowData.TouchCount,
|
||||
workflowData.AutoRaiseZ1);
|
||||
IReadOnlyList<double> results = await _needleZCalibrationService.ExecuteCalibrationAsync(
|
||||
executionContext,
|
||||
activityControl,
|
||||
workflowData.CancellationToken,
|
||||
result => PublishResult(workflowData, result));
|
||||
WriteResults(context, results);
|
||||
return ActivityResult.Success;
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Fail(context, MessageKey.ProcessStepFailedWithReason, new object[] { Name, ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private static void PublishResult(NeedleZCalibrationWorkflowData workflowData, double result)
|
||||
{
|
||||
workflowData.EventAggregator?.PublishOnUIThread(new NeedleZCalibrationResultEventArgs(result));
|
||||
}
|
||||
|
||||
private static void WriteResults(WorkflowContext context, IReadOnlyList<double> results)
|
||||
{
|
||||
if (context == null || results == null || results.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
context.SetData(WorkflowContextKeys.NeedleZCalibrationResults, results);
|
||||
context.SetData(WorkflowContextKeys.NeedleZCalibrationLastResult, results.Last());
|
||||
context.SetData(WorkflowContextKeys.NeedleZCalibrationAverageResult, results.Average());
|
||||
}
|
||||
|
||||
private static NeedleZCalibrationWorkflowData GetWorkflowData(WorkflowContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
NeedleZCalibrationWorkflowData workflowData = null;
|
||||
if (!context.TryGetData(WorkflowContextKeys.NeedleZCalibrationWorkflowData, out workflowData) || workflowData == null)
|
||||
{
|
||||
throw new InvalidOperationException("δ<>ṩ<EFBFBD><E1B9A9>ͷZ<CDB7>Ե<EFBFBD><D4B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݡ<EFBFBD>");
|
||||
}
|
||||
|
||||
return workflowData;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.DeviceMaintance.Model;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Motion;
|
||||
using MainShell.Parameter;
|
||||
using MainShell.ParaSetting.Model;
|
||||
using MaxwellFramework.Core.Attributes;
|
||||
using MaxwellFramework.Core.Interfaces;
|
||||
using MW.WorkFlow;
|
||||
using MwFramework.ManagerService;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
[Singleton]
|
||||
public class NeedleZCalibrationService
|
||||
{
|
||||
private const double Precision = 0.001;
|
||||
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly SafeAxisMotion _safeAxisMotion;
|
||||
private readonly IParamList _paramList;
|
||||
private readonly IDeviceIoMonitorService _deviceIoMonitorService;
|
||||
|
||||
public NeedleZCalibrationService(HardwareManager hardwareManager, SafeAxisMotion safeAxisMotion, IParameterManager parameterManager, IDeviceIoMonitorService deviceIoMonitorService)
|
||||
{
|
||||
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
|
||||
_safeAxisMotion = safeAxisMotion ?? throw new ArgumentNullException(nameof(safeAxisMotion));
|
||||
_paramList = parameterManager as IParamList;
|
||||
_deviceIoMonitorService = deviceIoMonitorService ?? throw new ArgumentNullException(nameof(deviceIoMonitorService));
|
||||
}
|
||||
|
||||
public NeedleZCalibrationExecutionContext GetExecutionContext(int? inputTouchCount, bool? inputAutoRaiseZ1)
|
||||
{
|
||||
NeedleCalibrationSetting needleCalibrationSetting = _paramList?.GetParameter<NeedleCalibrationSetting>();
|
||||
EquipmentParaSysSetting equipmentParaSysSetting = _paramList?.GetParameter<EquipmentParaSysSetting>();
|
||||
return CreateExecutionContext(needleCalibrationSetting, equipmentParaSysSetting, inputTouchCount, inputAutoRaiseZ1);
|
||||
}
|
||||
|
||||
public NeedleZCalibrationExecutionContext CreateExecutionContext(NeedleCalibrationSetting needleCalibrationSetting, EquipmentParaSysSetting equipmentParaSysSetting, int? inputTouchCount, bool? inputAutoRaiseZ1)
|
||||
{
|
||||
ValidateCalibrationSetting(needleCalibrationSetting);
|
||||
|
||||
NeedleZCalibrationItem item = needleCalibrationSetting.NeedleZCalibrationItem;
|
||||
ValidateCalibrationItem(item);
|
||||
|
||||
int touchCount = Math.Max(1, inputTouchCount ?? item.NeedleTouchCount);
|
||||
bool autoRaiseZ1 = inputAutoRaiseZ1 ?? true;
|
||||
double safeHeight = ResolveSafeHeight(equipmentParaSysSetting);
|
||||
|
||||
return new NeedleZCalibrationExecutionContext(needleCalibrationSetting, item, touchCount, autoRaiseZ1, safeHeight);
|
||||
}
|
||||
|
||||
public async Task<double> ExecuteSingleTouchAsync(bool autoRaiseZ1, CancellationToken cancellationToken)
|
||||
{
|
||||
return await ExecuteSingleTouchAsync(autoRaiseZ1, cancellationToken, null);
|
||||
}
|
||||
|
||||
public async Task<double> ExecuteSingleTouchAsync(bool autoRaiseZ1, CancellationToken cancellationToken, Action<double> onResult)
|
||||
{
|
||||
NeedleZCalibrationExecutionContext executionContext = GetExecutionContext(1, autoRaiseZ1);
|
||||
IReadOnlyList<double> results = await ExecuteCalibrationAsync(executionContext, null, cancellationToken, onResult);
|
||||
if (results == null || results.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("<22><><EFBFBD>ζԵ<CEB6>δ<EFBFBD><CEB4><EFBFBD>ؽ<EFBFBD><D8BD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
}
|
||||
|
||||
return results[0];
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<double>> ExecuteCalibrationAsync(NeedleZCalibrationExecutionContext executionContext, ActivityControl activityControl, CancellationToken cancellationToken, Action<double> onResult)
|
||||
{
|
||||
if (executionContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(executionContext));
|
||||
}
|
||||
|
||||
List<double> results = new List<double>();
|
||||
for (int i = 0; i < executionContext.TouchCount; i++)
|
||||
{
|
||||
await CheckControlAsync(activityControl, cancellationToken);
|
||||
double result = await ExecuteSingleCalibrationAsync(executionContext, activityControl, cancellationToken);
|
||||
results.Add(result);
|
||||
onResult?.Invoke(result);
|
||||
}
|
||||
|
||||
if (executionContext.AutoRaiseZ1)
|
||||
{
|
||||
await CheckControlAsync(activityControl, cancellationToken);
|
||||
_safeAxisMotion.MoveAbs(_hardwareManager.Axis_Z1, executionContext.Item.Z1StartPosition, cancellationToken);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private async Task<double> ExecuteSingleCalibrationAsync(NeedleZCalibrationExecutionContext executionContext, ActivityControl activityControl, CancellationToken cancellationToken)
|
||||
{
|
||||
bool foundSignal = false;
|
||||
double start = executionContext.Item.Z1StartPosition;
|
||||
double end = start - executionContext.Item.Z1DropMaxPosition;
|
||||
|
||||
await CheckControlAsync(activityControl, cancellationToken);
|
||||
_safeAxisMotion.MoveAbs(_hardwareManager.Axis_Z1, start, cancellationToken);
|
||||
|
||||
while (Math.Abs(end - start) > Precision)
|
||||
{
|
||||
await CheckControlAsync(activityControl, cancellationToken);
|
||||
double mid = (start + end) / 2.0;
|
||||
if (mid < executionContext.SafeHeight)
|
||||
{
|
||||
throw new InvalidOperationException(string.Format("Ŀ<><C4BF>λ<EFBFBD><CEBB>[{0}]<5D><><EFBFBD><EFBFBD>Z1<5A>Ե<EFBFBD><D4B5><EFBFBD>ȫ<EFBFBD>߶<EFBFBD>[{1}]<5D><><EFBFBD>˶<EFBFBD><CBB6><EFBFBD><EFBFBD><EFBFBD>ȫ<EFBFBD><C8AB>", mid, executionContext.SafeHeight));
|
||||
}
|
||||
|
||||
_safeAxisMotion.MoveAbs(_hardwareManager.Axis_Z1, mid, cancellationToken);
|
||||
if (IsToolSetterTriggered())
|
||||
{
|
||||
end = mid;
|
||||
foundSignal = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
start = mid;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundSignal)
|
||||
{
|
||||
throw new InvalidOperationException("<22>ڷ<EFBFBD>Χ<EFBFBD><CEA7>δ<EFBFBD>ҵ<EFBFBD><D2B5>Ե<EFBFBD><D4B5>źţ<C5BA><C5A3>Ե<EFBFBD>ʧ<EFBFBD>ܡ<EFBFBD>\r\n<><6E><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ե<EFBFBD><D4B5><EFBFBD>λ<EFBFBD><CEBB><EFBFBD><EFBFBD><EFBFBD>в<EFBFBD><D0B2>ź<EFBFBD>״̬<D7B4><CCAC>");
|
||||
}
|
||||
|
||||
return _hardwareManager.Axis_Z1.State != null ? _hardwareManager.Axis_Z1.State.ActualPos : _hardwareManager.Axis_Z1.GetPositionImmediate();
|
||||
}
|
||||
|
||||
private static async Task CheckControlAsync(ActivityControl activityControl, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (activityControl == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
activityControl.CancellationToken.ThrowIfCancellationRequested();
|
||||
await activityControl.CheckPauseAsync();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
activityControl.CancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
private bool IsToolSetterTriggered()
|
||||
{
|
||||
return _deviceIoMonitorService.IsPointOn(DeviceIoNames.Stage.ToolSetterSignal);
|
||||
}
|
||||
private static void ValidateCalibrationSetting(NeedleCalibrationSetting needleCalibrationSetting)
|
||||
{
|
||||
if (needleCalibrationSetting == null || needleCalibrationSetting.NeedleZCalibrationItem == null)
|
||||
{
|
||||
throw new InvalidOperationException("δ<><CEB4>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD>ͷ<EFBFBD>Ե<EFBFBD><D4B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateCalibrationItem(NeedleZCalibrationItem item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
if (item.Z1Speed <= 0)
|
||||
{
|
||||
throw new InvalidOperationException("Z1<5A>ٶȱ<D9B6><C8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>0<EFBFBD><30>");
|
||||
}
|
||||
|
||||
if (item.Z1DropMaxPosition <= 0)
|
||||
{
|
||||
throw new InvalidOperationException("Z1<5A>½<EFBFBD><C2BD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD>ñ<EFBFBD><C3B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>0<EFBFBD><30>");
|
||||
}
|
||||
}
|
||||
|
||||
private static double ResolveSafeHeight(EquipmentParaSysSetting equipmentParaSysSetting)
|
||||
{
|
||||
if (equipmentParaSysSetting == null || equipmentParaSysSetting.SafeParaSysItem == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return equipmentParaSysSetting.SafeParaSysItem.NeedleCalibSafeHeight;
|
||||
}
|
||||
}
|
||||
|
||||
public class NeedleZCalibrationExecutionContext
|
||||
{
|
||||
public NeedleZCalibrationExecutionContext(NeedleCalibrationSetting needleCalibrationSetting, NeedleZCalibrationItem item, int touchCount, bool autoRaiseZ1, double safeHeight)
|
||||
{
|
||||
NeedleCalibrationSetting = needleCalibrationSetting ?? throw new ArgumentNullException(nameof(needleCalibrationSetting));
|
||||
Item = item ?? throw new ArgumentNullException(nameof(item));
|
||||
TouchCount = touchCount;
|
||||
AutoRaiseZ1 = autoRaiseZ1;
|
||||
SafeHeight = safeHeight;
|
||||
}
|
||||
|
||||
public NeedleCalibrationSetting NeedleCalibrationSetting { get; private set; }
|
||||
|
||||
public NeedleZCalibrationItem Item { get; private set; }
|
||||
|
||||
public int TouchCount { get; private set; }
|
||||
|
||||
public bool AutoRaiseZ1 { get; private set; }
|
||||
|
||||
public double SafeHeight { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using MainShell.DeviceMaintance.Model;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Motion;
|
||||
using MainShell.Parameter;
|
||||
using MaxwellFramework.Core.Interfaces;
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class NeedleZCalibrationWorkflowData
|
||||
{
|
||||
public NeedleZCalibrationWorkflowData(string workflowName, int touchCount, bool autoRaiseZ1, CancellationToken cancellationToken, IEventAggregator eventAggregator, HardwareManager hardwareManager, SafeAxisMotion safeAxisMotion, IParameterManager parameterManager, IDeviceIoMonitorService deviceIoMonitorService)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(workflowName))
|
||||
{
|
||||
throw new ArgumentException("workflowName");
|
||||
}
|
||||
|
||||
WorkflowName = workflowName;
|
||||
TouchCount = touchCount;
|
||||
AutoRaiseZ1 = autoRaiseZ1;
|
||||
CancellationToken = cancellationToken;
|
||||
EventAggregator = eventAggregator;
|
||||
HardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
|
||||
SafeAxisMotion = safeAxisMotion ?? throw new ArgumentNullException(nameof(safeAxisMotion));
|
||||
ParameterManager = parameterManager ?? throw new ArgumentNullException(nameof(parameterManager));
|
||||
DeviceIoMonitorService = deviceIoMonitorService ?? throw new ArgumentNullException(nameof(deviceIoMonitorService));
|
||||
}
|
||||
|
||||
public string WorkflowName { get; private set; }
|
||||
|
||||
public int TouchCount { get; private set; }
|
||||
|
||||
public bool AutoRaiseZ1 { get; private set; }
|
||||
|
||||
public CancellationToken CancellationToken { get; private set; }
|
||||
|
||||
public IEventAggregator EventAggregator { get; private set; }
|
||||
|
||||
public HardwareManager HardwareManager { get; private set; }
|
||||
|
||||
public SafeAxisMotion SafeAxisMotion { get; private set; }
|
||||
|
||||
public IParameterManager ParameterManager { get; private set; }
|
||||
|
||||
public IDeviceIoMonitorService DeviceIoMonitorService { get; private set; }
|
||||
|
||||
public IParamList GetParameterList()
|
||||
{
|
||||
return ParameterManager as IParamList;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Recipe.Models;
|
||||
using MW.WorkFlow;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class ChipLoadRequestActivity : ActivityAbstractBase
|
||||
{
|
||||
public ChipLoadRequestActivity(string name, TimeSpan? timeout = null) : base(name)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
activityControl.ThrowIfCancellationRequested();
|
||||
|
||||
PreparationAreaService preparationAreaService;
|
||||
if (!context.TryGetData<PreparationAreaService>(WorkflowContextKeys.PreparationAreaService, out preparationAreaService) || preparationAreaService == null)
|
||||
{
|
||||
throw new InvalidOperationException("δ<>ڹ<EFBFBD><DAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҵ<EFBFBD><EFBFBD><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
}
|
||||
|
||||
ChipPreparationRequest request = new ChipPreparationRequest
|
||||
{
|
||||
RecipeName = ResolveRecipeName(context),
|
||||
Action = ChipPreparationAction.Load,
|
||||
SourceStepId = ResolveSourceStepId(context)
|
||||
};
|
||||
|
||||
string rejectReason;
|
||||
if (preparationAreaService.TryRequestPrepare(request, out rejectReason))
|
||||
{
|
||||
context.SetData(WorkflowContextKeys.PreparationAreaStatus, preparationAreaService.CurrentStatus);
|
||||
return Task.FromResult(ActivityResult.Success);
|
||||
}
|
||||
|
||||
return Task.FromResult(Fail(
|
||||
context,
|
||||
MessageKey.ProcessStepFailedWithReason,
|
||||
new object[]
|
||||
{
|
||||
Name,
|
||||
string.IsNullOrWhiteSpace(rejectReason) ? "оƬ<D0BE><C6AC>̨<EFBFBD><CCA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܾ<F3B1BBBE><DCBE><EFBFBD>" : rejectReason
|
||||
}));
|
||||
}
|
||||
|
||||
private static string ResolveRecipeName(WorkflowContext context)
|
||||
{
|
||||
if (context.TryGetData<RecipeManager>(WorkflowContextKeys.RecipeManager, out var recipeManager) &&
|
||||
recipeManager != null &&
|
||||
recipeManager.CurrentWaferRecipe != null)
|
||||
{
|
||||
return recipeManager.CurrentWaferRecipe.RecipeName;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static string ResolveSourceStepId(WorkflowContext context)
|
||||
{
|
||||
if (context.TryGetData<string>(WorkflowContextKeys.CurrentStepId, out var stepId))
|
||||
{
|
||||
return stepId;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Recipe.Models;
|
||||
using MW.WorkFlow;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class ChipPreparationAutoLoadStartActivity : ActivityAbstractBase
|
||||
{
|
||||
public ChipPreparationAutoLoadStartActivity(string name)
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
activityControl.ThrowIfCancellationRequested();
|
||||
|
||||
bool pendingChipLoad;
|
||||
if (context.TryGetData<bool>(WorkflowContextKeys.PendingChipLoad, out pendingChipLoad) && !pendingChipLoad)
|
||||
{
|
||||
return Task.FromResult(ActivityResult.Success);
|
||||
}
|
||||
|
||||
CurrentChipStateService currentChipStateService;
|
||||
if (!context.TryGetData<CurrentChipStateService>(WorkflowContextKeys.CurrentChipStateService, out currentChipStateService) || currentChipStateService == null)
|
||||
{
|
||||
throw new InvalidOperationException("δ<>ڹ<EFBFBD><DAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҵ<EFBFBD><D2B5><EFBFBD>ǰоƬ״̬<D7B4><CCAC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
}
|
||||
|
||||
if (currentChipStateService.CurrentState != CurrentChipLifecycleState.Empty)
|
||||
{
|
||||
context.SetData(WorkflowContextKeys.PendingChipLoad, false);
|
||||
context.SetData(WorkflowContextKeys.CurrentChipState, currentChipStateService.CurrentState);
|
||||
return Task.FromResult(ActivityResult.Success);
|
||||
}
|
||||
|
||||
PreparationAreaService preparationAreaService;
|
||||
if (!context.TryGetData<PreparationAreaService>(WorkflowContextKeys.PreparationAreaService, out preparationAreaService) || preparationAreaService == null)
|
||||
{
|
||||
throw new InvalidOperationException("δ<>ڹ<EFBFBD><DAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҵ<EFBFBD><EFBFBD><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
}
|
||||
|
||||
ChipPreparationRequest request = new ChipPreparationRequest
|
||||
{
|
||||
RecipeName = ResolveRecipeName(context),
|
||||
Action = ChipPreparationAction.Load,
|
||||
SourceStepId = ResolveSourceStepId(context)
|
||||
};
|
||||
|
||||
string rejectReason;
|
||||
if (preparationAreaService.TryRequestPrepare(request, out rejectReason) || CanIgnoreReject(preparationAreaService.CurrentStatus))
|
||||
{
|
||||
currentChipStateService.SetState(CurrentChipLifecycleState.Loading);
|
||||
context.SetData(WorkflowContextKeys.CurrentChipState, CurrentChipLifecycleState.Loading);
|
||||
context.SetData(WorkflowContextKeys.PreparationAreaStatus, preparationAreaService.CurrentStatus);
|
||||
return Task.FromResult(ActivityResult.Success);
|
||||
}
|
||||
|
||||
return Task.FromResult(Fail(
|
||||
context,
|
||||
MessageKey.ProcessStepFailedWithReason,
|
||||
new object[]
|
||||
{
|
||||
Name,
|
||||
string.IsNullOrWhiteSpace(rejectReason) ? "оƬ<D0BE>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD>ܡ<EFBFBD>" : rejectReason
|
||||
}));
|
||||
}
|
||||
|
||||
private static bool CanIgnoreReject(ChipPreparationStatus status)
|
||||
{
|
||||
return status == ChipPreparationStatus.Preparing ||
|
||||
status == ChipPreparationStatus.Loading ||
|
||||
status == ChipPreparationStatus.Loaded;
|
||||
}
|
||||
|
||||
private static string ResolveRecipeName(WorkflowContext context)
|
||||
{
|
||||
RecipeManager recipeManager;
|
||||
if (context.TryGetData<RecipeManager>(WorkflowContextKeys.RecipeManager, out recipeManager) &&
|
||||
recipeManager != null &&
|
||||
recipeManager.CurrentWaferRecipe != null)
|
||||
{
|
||||
return recipeManager.CurrentWaferRecipe.RecipeName;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static string ResolveSourceStepId(WorkflowContext context)
|
||||
{
|
||||
string stepId;
|
||||
if (context.TryGetData<string>(WorkflowContextKeys.CurrentStepId, out stepId))
|
||||
{
|
||||
return stepId;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using MainShell.Common;
|
||||
using MW.WorkFlow;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class ChipPreparationConsumeActivity : ActivityAbstractBase
|
||||
{
|
||||
public ChipPreparationConsumeActivity(string name)
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
activityControl.ThrowIfCancellationRequested();
|
||||
|
||||
CurrentChipLifecycleState currentChipState;
|
||||
if (!context.TryGetData<CurrentChipLifecycleState>(WorkflowContextKeys.CurrentChipState, out currentChipState) ||
|
||||
currentChipState != CurrentChipLifecycleState.LoadedPendingTransfer)
|
||||
{
|
||||
return Task.FromResult(ActivityResult.Success);
|
||||
}
|
||||
|
||||
PreparationAreaService preparationAreaService;
|
||||
if (!context.TryGetData<PreparationAreaService>(WorkflowContextKeys.PreparationAreaService, out preparationAreaService) || preparationAreaService == null)
|
||||
{
|
||||
throw new InvalidOperationException("δ<>ڹ<EFBFBD><DAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҵ<EFBFBD><EFBFBD><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
}
|
||||
|
||||
AutoProductionRuntimeStateService autoProductionRuntimeStateService;
|
||||
if (!context.TryGetData<AutoProductionRuntimeStateService>(WorkflowContextKeys.AutoProductionRuntimeStateService, out autoProductionRuntimeStateService) || autoProductionRuntimeStateService == null)
|
||||
{
|
||||
throw new InvalidOperationException("δ<>ڹ<EFBFBD><DAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҵ<EFBFBD><D2B5>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̬<EFBFBD><CCAC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
}
|
||||
|
||||
string rejectReason;
|
||||
if (preparationAreaService.TryConsumeReady(out rejectReason))
|
||||
{
|
||||
CurrentChipStateService currentChipStateService;
|
||||
if (context.TryGetData<CurrentChipStateService>(WorkflowContextKeys.CurrentChipStateService, out currentChipStateService) && currentChipStateService != null)
|
||||
{
|
||||
currentChipStateService.SetState(CurrentChipLifecycleState.InUse);
|
||||
}
|
||||
|
||||
autoProductionRuntimeStateService.SetCurrentChipRemainingCount(1);
|
||||
|
||||
context.SetData(WorkflowContextKeys.PendingChipLoad, false);
|
||||
context.SetData(WorkflowContextKeys.CurrentChipState, CurrentChipLifecycleState.InUse);
|
||||
context.SetData(WorkflowContextKeys.PreparationAreaStatus, preparationAreaService.CurrentStatus);
|
||||
context.SetData(WorkflowContextKeys.CurrentChipRemainingCount, autoProductionRuntimeStateService.CurrentChipRemainingCount);
|
||||
return Task.FromResult(ActivityResult.Success);
|
||||
}
|
||||
|
||||
return Task.FromResult(Fail(
|
||||
context,
|
||||
MessageKey.ProcessStepFailedWithReason,
|
||||
new object[]
|
||||
{
|
||||
Name,
|
||||
string.IsNullOrWhiteSpace(rejectReason) ? "оƬ<D0BE><C6AC>̨<CCA8><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD>ܡ<EFBFBD>" : rejectReason
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
using MainShell.Alarm;
|
||||
using MainShell.Common;
|
||||
using MainShell.Log;
|
||||
using MainShell.Vision;
|
||||
using MW.WorkFlow;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class ChipPreparationRequestHandler : IHandle<ChipPreparationRequestedEventArgs>, IHandle<ChipPreparationStateChangedEventArgs>, IDisposable
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly PreparationAreaService _preparationAreaService;
|
||||
private readonly AlarmOperate _alarmOperate;
|
||||
private readonly WorkflowRunner _workflowRunner;
|
||||
private readonly SemaphoreSlim _serialGate = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim _runnerStateGate = new SemaphoreSlim(1, 1);
|
||||
private readonly CancellationTokenSource _disposeCts = new CancellationTokenSource();
|
||||
private bool _disposed;
|
||||
|
||||
public ChipPreparationRequestHandler(IEventAggregator eventAggregator, PreparationAreaService preparationAreaService, AlarmOperate alarmOperate)
|
||||
{
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_preparationAreaService = preparationAreaService ?? throw new ArgumentNullException(nameof(preparationAreaService));
|
||||
_alarmOperate = alarmOperate ?? throw new ArgumentNullException(nameof(alarmOperate));
|
||||
_workflowRunner = new WorkflowRunner();
|
||||
|
||||
_eventAggregator.Subscribe(this);
|
||||
}
|
||||
|
||||
public void Handle(ChipPreparationRequestedEventArgs message)
|
||||
{
|
||||
if (message == null || message.Request == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ChipPreparationRequest request = CloneRequest(message.Request);
|
||||
_ = ProcessRequestAsync(request);
|
||||
}
|
||||
|
||||
public void Handle(ChipPreparationStateChangedEventArgs message)
|
||||
{
|
||||
if (message == null || message.Snapshot == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ChipPreparationStateSnapshot snapshot = CloneSnapshot(message.Snapshot);
|
||||
_ = SynchronizeRunnerStateAsync(snapshot);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
_disposeCts.Cancel();
|
||||
_eventAggregator.Unsubscribe(this);
|
||||
_workflowRunner.Dispose();
|
||||
_disposeCts.Dispose();
|
||||
_serialGate.Dispose();
|
||||
_runnerStateGate.Dispose();
|
||||
}
|
||||
|
||||
private async Task ProcessRequestAsync(ChipPreparationRequest request)
|
||||
{
|
||||
bool gateEntered = false;
|
||||
try
|
||||
{
|
||||
await _serialGate.WaitAsync(_disposeCts.Token);
|
||||
gateEntered = true;
|
||||
|
||||
string.Format("Chip preparation request started. {0}.", BuildRequestSummary(request)).LogProcessInfo();
|
||||
|
||||
ChipPreparationWaitResult executeResult = await ExecuteOnDemandActivityAsync(request);
|
||||
string rejectReason;
|
||||
|
||||
if (executeResult.Success)
|
||||
{
|
||||
_preparationAreaService.TryMarkReady(request.RequestId, out rejectReason);
|
||||
}
|
||||
else
|
||||
{
|
||||
string errorMessage = string.IsNullOrWhiteSpace(executeResult.ErrorMessage) ? "оƬ<C6AC><D7BC>ִ<EFBFBD><D6B4>ʧ<EFBFBD>ܡ<EFBFBD>" : executeResult.ErrorMessage;
|
||||
_preparationAreaService.TryMarkFault(errorMessage, out rejectReason);
|
||||
int? visionAlarmId = ResolveVisionAlarmId(executeResult);
|
||||
if (visionAlarmId.HasValue)
|
||||
{
|
||||
await _alarmOperate.AlertAsync(
|
||||
visionAlarmId.Value,
|
||||
nameof(ChipPreparationRequestHandler),
|
||||
"Vision alarm triggered",
|
||||
BuildAlarmTriggerSummary(request, executeResult)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string.Format("Chip preparation request failed with exception. {0}. {1}", BuildRequestSummary(request), ex).LogProcessError();
|
||||
string rejectReason;
|
||||
_preparationAreaService.TryMarkFault("оƬ<C6AC><D7BC>ִ<EFBFBD><D6B4><EFBFBD>쳣: " + ex.Message, out rejectReason);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (gateEntered)
|
||||
{
|
||||
_serialGate.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ChipPreparationWaitResult> ExecuteOnDemandActivityAsync(ChipPreparationRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
WorkflowContext context = new WorkflowContext();
|
||||
context[WorkflowContextKeys.WorkflowName] = ProcessFlowName.ChipPreparationOnDemandFlow;
|
||||
|
||||
string activityName = request != null && request.Action == ChipPreparationAction.Unload
|
||||
? ProcessFlowName.ChipUnloadExecuteActivity
|
||||
: ProcessFlowName.ChipLoadExecuteActivity;
|
||||
ChipPreparationExecuteActivity activity = new ChipPreparationExecuteActivity(activityName, request);
|
||||
|
||||
WorkflowRunCompletedEventArgs runResult = await _workflowRunner.RunActivityWithResultAsync(activity, context);
|
||||
if (runResult == null)
|
||||
{
|
||||
string.Format("Chip preparation request returned no workflow result. {0}.", BuildRequestSummary(request)).LogProcessError();
|
||||
return ChipPreparationWaitResult.CreateFailure("оƬ<C6AC><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>δ<EFBFBD><CEB4><EFBFBD><EFBFBD>ִ<EFBFBD>н<EFBFBD><D0BD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
}
|
||||
|
||||
switch (runResult.FinalState)
|
||||
{
|
||||
case WorkflowState.Completed:
|
||||
string.Format("Chip preparation request completed. {0}, FinalState={1}.", BuildRequestSummary(request), runResult.FinalState).LogProcessInfo();
|
||||
return ChipPreparationWaitResult.CreateSuccess();
|
||||
case WorkflowState.Canceled:
|
||||
string.Format("Chip preparation request canceled. {0}, FinalState={1}, ErrorMessage={2}.", BuildRequestSummary(request), runResult.FinalState, NormalizeLogValue(runResult.Error)).LogProcessError();
|
||||
return ChipPreparationWaitResult.CreateCanceled("оƬ<C6AC><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD>");
|
||||
case WorkflowState.Faulted:
|
||||
string.Format("Chip preparation request faulted. {0}, FinalState={1}, ErrorMessage={2}.", BuildRequestSummary(request), runResult.FinalState, NormalizeLogValue(runResult.Error)).LogProcessError();
|
||||
return ChipPreparationWaitResult.CreateFailure(string.IsNullOrWhiteSpace(runResult.Error)
|
||||
? "оƬ<C6AC><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ִ<EFBFBD><D6B4>ʧ<EFBFBD>ܡ<EFBFBD>"
|
||||
: runResult.Error);
|
||||
default:
|
||||
string.Format("Chip preparation request returned unexpected state. {0}, FinalState={1}, ErrorMessage={2}.", BuildRequestSummary(request), runResult.FinalState, NormalizeLogValue(runResult.Error)).LogProcessError();
|
||||
return ChipPreparationWaitResult.CreateFailure(string.Format("оƬ<C6AC><D7BC><EFBFBD><EFBFBD><EFBFBD>̷<EFBFBD><CCB7><EFBFBD><EFBFBD><EFBFBD>δԤ<CEB4><D4A4>״̬: {0}<7D><>", runResult.FinalState));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string.Format("Chip preparation workflow execution threw an exception. {0}. {1}", BuildRequestSummary(request), ex).LogProcessError();
|
||||
return ChipPreparationWaitResult.CreateFailure(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SynchronizeRunnerStateAsync(ChipPreparationStateSnapshot snapshot)
|
||||
{
|
||||
if (snapshot == null || _disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _runnerStateGate.WaitAsync(_disposeCts.Token).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (snapshot.Status == ChipPreparationStatus.Paused)
|
||||
{
|
||||
string.Format("Chip preparation runner paused. {0}.", BuildSnapshotSummary(snapshot)).LogProcessDebug();
|
||||
_workflowRunner.Pause();
|
||||
return;
|
||||
}
|
||||
|
||||
if (snapshot.Status == ChipPreparationStatus.Preparing ||
|
||||
snapshot.Status == ChipPreparationStatus.Loading ||
|
||||
snapshot.Status == ChipPreparationStatus.Unloading)
|
||||
{
|
||||
string.Format("Chip preparation runner resumed. {0}.", BuildSnapshotSummary(snapshot)).LogProcessDebug();
|
||||
_workflowRunner.Resume();
|
||||
return;
|
||||
}
|
||||
|
||||
if ((snapshot.Status == ChipPreparationStatus.Idle || snapshot.Status == ChipPreparationStatus.Faulted) &&
|
||||
_workflowRunner.IsRunning)
|
||||
{
|
||||
string.Format("Chip preparation runner stopping. {0}.", BuildSnapshotSummary(snapshot)).LogProcessDebug();
|
||||
await _workflowRunner.StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
_runnerStateGate.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static ChipPreparationRequest CloneRequest(ChipPreparationRequest request)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ChipPreparationRequest
|
||||
{
|
||||
RequestId = request.RequestId,
|
||||
RecipeName = request.RecipeName,
|
||||
Action = request.Action,
|
||||
SourceStepId = request.SourceStepId
|
||||
};
|
||||
}
|
||||
|
||||
private static ChipPreparationStateSnapshot CloneSnapshot(ChipPreparationStateSnapshot snapshot)
|
||||
{
|
||||
if (snapshot == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ChipPreparationStateSnapshot
|
||||
{
|
||||
Status = snapshot.Status,
|
||||
ActiveRequestId = snapshot.ActiveRequestId,
|
||||
RecipeName = snapshot.RecipeName,
|
||||
SourceStepId = snapshot.SourceStepId,
|
||||
CurrentStage = snapshot.CurrentStage,
|
||||
ErrorMessage = snapshot.ErrorMessage,
|
||||
LastUpdatedTime = snapshot.LastUpdatedTime,
|
||||
CurrentAction = snapshot.CurrentAction,
|
||||
IsChipLoaded = snapshot.IsChipLoaded
|
||||
};
|
||||
}
|
||||
private static int? ResolveVisionAlarmId(ChipPreparationWaitResult executeResult)
|
||||
{
|
||||
if (executeResult == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(executeResult.ErrorMessage) &&
|
||||
executeResult.ErrorMessage.IndexOf("Vision", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
return VisionAlarmIds.TemplateMatchFailed;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string BuildRequestSummary(ChipPreparationRequest request)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
return "RequestId=N/A, Action=None, RecipeName=N/A, SourceStepId=N/A";
|
||||
}
|
||||
|
||||
return string.Format(
|
||||
"RequestId={0}, Action={1}, RecipeName={2}, SourceStepId={3}",
|
||||
NormalizeLogValue(request.RequestId),
|
||||
request.Action,
|
||||
NormalizeLogValue(request.RecipeName),
|
||||
NormalizeLogValue(request.SourceStepId));
|
||||
}
|
||||
|
||||
private static string BuildSnapshotSummary(ChipPreparationStateSnapshot snapshot)
|
||||
{
|
||||
if (snapshot == null)
|
||||
{
|
||||
return "Status=N/A, ActiveRequestId=N/A, CurrentAction=None, SourceStepId=N/A, IsChipLoaded=False";
|
||||
}
|
||||
|
||||
return string.Format(
|
||||
"Status={0}, ActiveRequestId={1}, CurrentAction={2}, SourceStepId={3}, IsChipLoaded={4}",
|
||||
snapshot.Status,
|
||||
NormalizeLogValue(snapshot.ActiveRequestId),
|
||||
snapshot.CurrentAction,
|
||||
NormalizeLogValue(snapshot.SourceStepId),
|
||||
snapshot.IsChipLoaded);
|
||||
}
|
||||
|
||||
private static string BuildAlarmTriggerSummary(ChipPreparationRequest request, ChipPreparationWaitResult executeResult)
|
||||
{
|
||||
return string.Format(
|
||||
"{0}, ErrorMessage={1}",
|
||||
BuildRequestSummary(request),
|
||||
executeResult == null ? "N/A" : NormalizeLogValue(executeResult.ErrorMessage));
|
||||
}
|
||||
|
||||
private static string NormalizeLogValue(string value)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value) ? "N/A" : value;
|
||||
}
|
||||
}
|
||||
|
||||
public class ChipPreparationExecuteActivity : ActivityAbstractBase
|
||||
{
|
||||
private readonly ChipPreparationRequest _request;
|
||||
|
||||
public ChipPreparationExecuteActivity(string name, ChipPreparationRequest request)
|
||||
: base(name)
|
||||
{
|
||||
_request = request;
|
||||
}
|
||||
|
||||
protected override async Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
if (_request == null)
|
||||
{
|
||||
return ActivityResult.Failure;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
activityControl.ThrowIfCancellationRequested();
|
||||
await activityControl.CheckPauseAsync();
|
||||
await Task.Delay(300, activityControl.CancellationToken);
|
||||
}
|
||||
|
||||
return ActivityResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,925 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.ProcessResult;
|
||||
using MW.WorkFlow;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public enum ChipPreparationAction
|
||||
{
|
||||
None = 0,
|
||||
Load = 1,
|
||||
Unload = 2,
|
||||
Completed = 3,
|
||||
Prepare = 4
|
||||
}
|
||||
|
||||
public enum ChipPreparationStatus
|
||||
{
|
||||
Idle = 0,
|
||||
Preparing = 1,
|
||||
Prepared = 2,
|
||||
Paused = 3,
|
||||
Faulted = 4,
|
||||
Loading = 5,
|
||||
Loaded = 6,
|
||||
Unloading = 7
|
||||
}
|
||||
|
||||
public class ChipPreparationRequest
|
||||
{
|
||||
public string RequestId { get; set; }
|
||||
|
||||
public string RecipeName { get; set; }
|
||||
|
||||
public ChipPreparationAction Action { get; set; } = ChipPreparationAction.Prepare;
|
||||
|
||||
public string SourceStepId { get; set; }
|
||||
}
|
||||
|
||||
public class ChipPreparationWaitResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
|
||||
public bool IsTimeout { get; set; }
|
||||
|
||||
public bool IsCanceled { get; set; }
|
||||
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
public static ChipPreparationWaitResult CreateSuccess()
|
||||
{
|
||||
return new ChipPreparationWaitResult
|
||||
{
|
||||
Success = true
|
||||
};
|
||||
}
|
||||
|
||||
public static ChipPreparationWaitResult CreateTimeout(string errorMessage)
|
||||
{
|
||||
return new ChipPreparationWaitResult
|
||||
{
|
||||
IsTimeout = true,
|
||||
ErrorMessage = errorMessage
|
||||
};
|
||||
}
|
||||
|
||||
public static ChipPreparationWaitResult CreateCanceled(string errorMessage)
|
||||
{
|
||||
return new ChipPreparationWaitResult
|
||||
{
|
||||
IsCanceled = true,
|
||||
ErrorMessage = errorMessage
|
||||
};
|
||||
}
|
||||
|
||||
public static ChipPreparationWaitResult CreateFailure(string errorMessage)
|
||||
{
|
||||
return new ChipPreparationWaitResult
|
||||
{
|
||||
ErrorMessage = errorMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class ChipPreparationStateSnapshot
|
||||
{
|
||||
public ChipPreparationStatus Status { get; set; }
|
||||
|
||||
public string ActiveRequestId { get; set; }
|
||||
|
||||
public string RecipeName { get; set; }
|
||||
|
||||
public string SourceStepId { get; set; }
|
||||
|
||||
public string CurrentStage { get; set; }
|
||||
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
public DateTime LastUpdatedTime { get; set; }
|
||||
|
||||
public ChipPreparationAction CurrentAction { get; set; }
|
||||
|
||||
public bool IsChipLoaded { get; set; }
|
||||
}
|
||||
|
||||
public class ChipPreparationState : PropertyChangedBase
|
||||
{
|
||||
private ChipPreparationStatus _status;
|
||||
private string _activeRequestId;
|
||||
private string _recipeName;
|
||||
private string _sourceStepId;
|
||||
private string _currentStage;
|
||||
private string _errorMessage;
|
||||
private DateTime _lastUpdatedTime;
|
||||
private ChipPreparationAction _currentAction;
|
||||
private bool _isChipLoaded;
|
||||
|
||||
public ChipPreparationStatus Status
|
||||
{
|
||||
get { return _status; }
|
||||
set { SetAndNotify(ref _status, value); }
|
||||
}
|
||||
|
||||
public string ActiveRequestId
|
||||
{
|
||||
get { return _activeRequestId; }
|
||||
set { SetAndNotify(ref _activeRequestId, value); }
|
||||
}
|
||||
|
||||
public string RecipeName
|
||||
{
|
||||
get { return _recipeName; }
|
||||
set { SetAndNotify(ref _recipeName, value); }
|
||||
}
|
||||
|
||||
public string SourceStepId
|
||||
{
|
||||
get { return _sourceStepId; }
|
||||
set { SetAndNotify(ref _sourceStepId, value); }
|
||||
}
|
||||
|
||||
public string CurrentStage
|
||||
{
|
||||
get { return _currentStage; }
|
||||
set { SetAndNotify(ref _currentStage, value); }
|
||||
}
|
||||
|
||||
public string ErrorMessage
|
||||
{
|
||||
get { return _errorMessage; }
|
||||
set { SetAndNotify(ref _errorMessage, value); }
|
||||
}
|
||||
|
||||
public DateTime LastUpdatedTime
|
||||
{
|
||||
get { return _lastUpdatedTime; }
|
||||
set { SetAndNotify(ref _lastUpdatedTime, value); }
|
||||
}
|
||||
|
||||
public ChipPreparationAction CurrentAction
|
||||
{
|
||||
get { return _currentAction; }
|
||||
set { SetAndNotify(ref _currentAction, value); }
|
||||
}
|
||||
|
||||
public bool IsChipLoaded
|
||||
{
|
||||
get { return _isChipLoaded; }
|
||||
set { SetAndNotify(ref _isChipLoaded, value); }
|
||||
}
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get
|
||||
{
|
||||
return Status == ChipPreparationStatus.Preparing ||
|
||||
Status == ChipPreparationStatus.Loading ||
|
||||
Status == ChipPreparationStatus.Unloading;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReady
|
||||
{
|
||||
get
|
||||
{
|
||||
return Status == ChipPreparationStatus.Prepared ||
|
||||
Status == ChipPreparationStatus.Loaded ||
|
||||
(Status == ChipPreparationStatus.Idle && CurrentAction == ChipPreparationAction.Unload);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFaulted
|
||||
{
|
||||
get { return Status == ChipPreparationStatus.Faulted; }
|
||||
}
|
||||
|
||||
public void ApplySnapshot(ChipPreparationStateSnapshot snapshot)
|
||||
{
|
||||
if (snapshot == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Status = snapshot.Status;
|
||||
ActiveRequestId = snapshot.ActiveRequestId;
|
||||
RecipeName = snapshot.RecipeName;
|
||||
SourceStepId = snapshot.SourceStepId;
|
||||
CurrentStage = snapshot.CurrentStage;
|
||||
ErrorMessage = snapshot.ErrorMessage;
|
||||
LastUpdatedTime = snapshot.LastUpdatedTime;
|
||||
CurrentAction = snapshot.CurrentAction;
|
||||
IsChipLoaded = snapshot.IsChipLoaded;
|
||||
NotifyOfPropertyChange(nameof(IsBusy));
|
||||
NotifyOfPropertyChange(nameof(IsReady));
|
||||
NotifyOfPropertyChange(nameof(IsFaulted));
|
||||
}
|
||||
}
|
||||
|
||||
public class ChipPreparationRequestedEventArgs : EventArgs
|
||||
{
|
||||
public ChipPreparationRequestedEventArgs(ChipPreparationRequest request)
|
||||
{
|
||||
Request = request;
|
||||
}
|
||||
|
||||
public ChipPreparationRequest Request { get; private set; }
|
||||
}
|
||||
|
||||
public class ChipPreparationStateChangedEventArgs : EventArgs
|
||||
{
|
||||
public ChipPreparationStateChangedEventArgs(ChipPreparationStateSnapshot snapshot)
|
||||
{
|
||||
Snapshot = snapshot;
|
||||
}
|
||||
|
||||
public ChipPreparationStateSnapshot Snapshot { get; private set; }
|
||||
}
|
||||
|
||||
public interface IChipPreparationService
|
||||
{
|
||||
ChipPreparationState Current { get; }
|
||||
|
||||
bool TryRequestPrepare(ChipPreparationRequest request, out string rejectReason);
|
||||
|
||||
Task<ChipPreparationWaitResult> WaitUntilReadyAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
|
||||
Task<ChipPreparationWaitResult> WaitUntilLoadedAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
|
||||
Task<ChipPreparationWaitResult> EnsurePreparedAsync(ChipPreparationRequest request, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
|
||||
bool TryMarkReady(string requestId, out string rejectReason);
|
||||
|
||||
bool TryMarkFault(string errorMessage, out string rejectReason);
|
||||
|
||||
bool TryConsumeReady(out string rejectReason);
|
||||
|
||||
Task CancelAsync(CancellationToken cancellationToken = default(CancellationToken));
|
||||
|
||||
void Pause();
|
||||
|
||||
void Resume();
|
||||
|
||||
bool TryResetFault(out string failureReason);
|
||||
}
|
||||
|
||||
public class ChipPreparationService : IChipPreparationService
|
||||
{
|
||||
private readonly object _syncRoot = new object();
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ProcessResultManager _processResultManager;
|
||||
private ChipPreparationRequest _activeRequest;
|
||||
private ChipPreparationRequest _pendingLoadRequest;
|
||||
private ChipPreparationStateSnapshot _snapshot;
|
||||
|
||||
public ChipPreparationService(IEventAggregator eventAggregator, ProcessResultManager processResultManager)
|
||||
{
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_processResultManager = processResultManager ?? throw new ArgumentNullException(nameof(processResultManager));
|
||||
_processResultManager.ReloadAll();
|
||||
_snapshot = RestoreSnapshot(_processResultManager.PreparationAreaState);
|
||||
Current = new ChipPreparationState();
|
||||
Current.ApplySnapshot(_snapshot);
|
||||
}
|
||||
|
||||
public ChipPreparationState Current { get; private set; }
|
||||
|
||||
public bool TryRequestPrepare(ChipPreparationRequest request, out string rejectReason)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
if (!TryValidateActionRequest(request, out rejectReason))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.RequestId))
|
||||
{
|
||||
request.RequestId = Guid.NewGuid().ToString("N");
|
||||
}
|
||||
|
||||
ChipPreparationRequest requestToPublish = null;
|
||||
ChipPreparationStateSnapshot snapshotToPublish = null;
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_snapshot.Status == ChipPreparationStatus.Faulted)
|
||||
{
|
||||
rejectReason = string.IsNullOrWhiteSpace(_snapshot.ErrorMessage) ? "оƬ<C6AC><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD><C7B0><EFBFBD>ڹ<EFBFBD><DAB9><EFBFBD>״̬<D7B4><CCAC>" : _snapshot.ErrorMessage;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_snapshot.Status == ChipPreparationStatus.Paused)
|
||||
{
|
||||
rejectReason = "оƬ<C6AC><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͣ״̬<D7B4><CCAC>";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.Action == ChipPreparationAction.Prepare)
|
||||
{
|
||||
if (_snapshot.Status == ChipPreparationStatus.Preparing ||
|
||||
_snapshot.Status == ChipPreparationStatus.Prepared ||
|
||||
_snapshot.Status == ChipPreparationStatus.Loading ||
|
||||
_snapshot.Status == ChipPreparationStatus.Loaded)
|
||||
{
|
||||
rejectReason = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_snapshot.Status == ChipPreparationStatus.Unloading)
|
||||
{
|
||||
rejectReason = "оƬ<C6AC><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ִ<EFBFBD><D6B4><EFBFBD><EFBFBD><EFBFBD>ϣ<EFBFBD><CFA3><EFBFBD><EFBFBD>ܷ<EFBFBD><DCB7><EFBFBD><EFBFBD><D7BC><EFBFBD><EFBFBD>";
|
||||
return false;
|
||||
}
|
||||
|
||||
_activeRequest = CloneRequest(request);
|
||||
_pendingLoadRequest = null;
|
||||
_snapshot = CreateRunningSnapshot(_activeRequest, ChipPreparationStatus.Preparing, _snapshot.IsChipLoaded);
|
||||
snapshotToPublish = CloneSnapshot(_snapshot);
|
||||
requestToPublish = CloneRequest(_activeRequest);
|
||||
}
|
||||
else if (request.Action == ChipPreparationAction.Load)
|
||||
{
|
||||
if (_snapshot.Status == ChipPreparationStatus.Loaded)
|
||||
{
|
||||
rejectReason = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_snapshot.Status == ChipPreparationStatus.Loading)
|
||||
{
|
||||
rejectReason = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_snapshot.Status == ChipPreparationStatus.Preparing)
|
||||
{
|
||||
_pendingLoadRequest = CloneRequest(request);
|
||||
rejectReason = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_snapshot.Status == ChipPreparationStatus.Unloading)
|
||||
{
|
||||
rejectReason = "оƬ<C6AC><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ִ<EFBFBD><D6B4><EFBFBD><EFBFBD><EFBFBD>ϣ<EFBFBD><CFA3><EFBFBD><EFBFBD>ܷ<EFBFBD><DCB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϡ<EFBFBD>";
|
||||
return false;
|
||||
}
|
||||
|
||||
_activeRequest = CloneRequest(request);
|
||||
_snapshot = CreateRunningSnapshot(_activeRequest, ChipPreparationStatus.Loading, _snapshot.IsChipLoaded);
|
||||
snapshotToPublish = CloneSnapshot(_snapshot);
|
||||
requestToPublish = CloneRequest(_activeRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_snapshot.Status == ChipPreparationStatus.Unloading)
|
||||
{
|
||||
rejectReason = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_snapshot.IsChipLoaded && _snapshot.Status != ChipPreparationStatus.Loaded && _snapshot.Status != ChipPreparationStatus.Prepared)
|
||||
{
|
||||
rejectReason = "<22><>ǰû<C7B0>п<EFBFBD>ִ<EFBFBD><D6B4><EFBFBD><EFBFBD><EFBFBD>ϵ<EFBFBD>оƬ<D0BE><C6AC>";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_snapshot.Status == ChipPreparationStatus.Preparing || _snapshot.Status == ChipPreparationStatus.Loading)
|
||||
{
|
||||
rejectReason = "оƬ<C6AC><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ִ<EFBFBD><D6B4><EFBFBD><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϣ<EFBFBD><CFA3><EFBFBD><EFBFBD>ܷ<EFBFBD><DCB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϡ<EFBFBD>";
|
||||
return false;
|
||||
}
|
||||
|
||||
_activeRequest = CloneRequest(request);
|
||||
_pendingLoadRequest = null;
|
||||
_snapshot = CreateRunningSnapshot(_activeRequest, ChipPreparationStatus.Unloading, true);
|
||||
snapshotToPublish = CloneSnapshot(_snapshot);
|
||||
requestToPublish = CloneRequest(_activeRequest);
|
||||
}
|
||||
|
||||
rejectReason = string.Empty;
|
||||
}
|
||||
|
||||
PublishState(snapshotToPublish);
|
||||
if (requestToPublish != null)
|
||||
{
|
||||
_eventAggregator.Publish(new ChipPreparationRequestedEventArgs(requestToPublish));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<ChipPreparationWaitResult> WaitUntilReadyAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return await WaitForStateAsync(false, timeout, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<ChipPreparationWaitResult> WaitUntilLoadedAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return await WaitForStateAsync(true, timeout, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<ChipPreparationWaitResult> EnsurePreparedAsync(ChipPreparationRequest request, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
string rejectReason;
|
||||
if (!TryRequestPrepare(request, out rejectReason))
|
||||
{
|
||||
return ChipPreparationWaitResult.CreateFailure(rejectReason);
|
||||
}
|
||||
|
||||
return await WaitUntilReadyAsync(timeout, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public bool TryMarkReady(string requestId, out string rejectReason)
|
||||
{
|
||||
ChipPreparationStateSnapshot snapshotToPublish = null;
|
||||
ChipPreparationRequest requestToPublish = null;
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_activeRequest == null)
|
||||
{
|
||||
rejectReason = "<22><>ǰû<C7B0><C3BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ִ<EFBFBD>е<EFBFBD>оƬ<C6AC><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(requestId) &&
|
||||
!string.Equals(_activeRequest.RequestId, requestId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
rejectReason = "оƬ<C6AC><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʶ<EFBFBD><CAB6>ƥ<EFBFBD>䡣";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_activeRequest.Action == ChipPreparationAction.Prepare)
|
||||
{
|
||||
if (_pendingLoadRequest != null)
|
||||
{
|
||||
_activeRequest = CloneRequest(_pendingLoadRequest);
|
||||
_pendingLoadRequest = null;
|
||||
_snapshot = CreateRunningSnapshot(_activeRequest, ChipPreparationStatus.Loading, true);
|
||||
snapshotToPublish = CloneSnapshot(_snapshot);
|
||||
requestToPublish = CloneRequest(_activeRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
_snapshot = CreateStableSnapshot(ChipPreparationStatus.Prepared, _activeRequest, true, "Prepared");
|
||||
snapshotToPublish = CloneSnapshot(_snapshot);
|
||||
_activeRequest = null;
|
||||
}
|
||||
}
|
||||
else if (_activeRequest.Action == ChipPreparationAction.Load)
|
||||
{
|
||||
_snapshot = CreateStableSnapshot(ChipPreparationStatus.Loaded, _activeRequest, true, "LoadedPendingTransfer");
|
||||
snapshotToPublish = CloneSnapshot(_snapshot);
|
||||
_activeRequest = null;
|
||||
}
|
||||
else if (_activeRequest.Action == ChipPreparationAction.Unload)
|
||||
{
|
||||
_snapshot = CreateIdleSnapshot(false);
|
||||
_snapshot.CurrentAction = ChipPreparationAction.Unload;
|
||||
_snapshot.CurrentStage = "UnloadCompleted";
|
||||
snapshotToPublish = CloneSnapshot(_snapshot);
|
||||
_activeRequest = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
rejectReason = "оƬ<C6AC><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ч<EFBFBD><D0A7>";
|
||||
return false;
|
||||
}
|
||||
|
||||
rejectReason = string.Empty;
|
||||
}
|
||||
|
||||
PublishState(snapshotToPublish);
|
||||
if (requestToPublish != null)
|
||||
{
|
||||
_eventAggregator.Publish(new ChipPreparationRequestedEventArgs(requestToPublish));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryMarkFault(string errorMessage, out string rejectReason)
|
||||
{
|
||||
ChipPreparationStateSnapshot snapshotToPublish;
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_snapshot.Status == ChipPreparationStatus.Idle)
|
||||
{
|
||||
rejectReason = "<22><>ǰû<C7B0><C3BB>ִ<EFBFBD><D6B4><EFBFBD>е<EFBFBD>оƬ<D0BE><C6AC><EFBFBD>̡<EFBFBD>";
|
||||
return false;
|
||||
}
|
||||
|
||||
_snapshot = CreateFaultedSnapshot(_activeRequest, errorMessage, _snapshot.IsChipLoaded);
|
||||
_activeRequest = null;
|
||||
_pendingLoadRequest = null;
|
||||
snapshotToPublish = CloneSnapshot(_snapshot);
|
||||
rejectReason = string.Empty;
|
||||
}
|
||||
|
||||
PublishState(snapshotToPublish);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryConsumeReady(out string rejectReason)
|
||||
{
|
||||
ChipPreparationStateSnapshot snapshotToPublish;
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_snapshot.Status == ChipPreparationStatus.Loaded || _snapshot.Status == ChipPreparationStatus.Prepared)
|
||||
{
|
||||
ResetToIdle(false);
|
||||
snapshotToPublish = CloneSnapshot(_snapshot);
|
||||
rejectReason = string.Empty;
|
||||
}
|
||||
else if (_snapshot.Status == ChipPreparationStatus.Idle && _snapshot.CurrentAction == ChipPreparationAction.Unload)
|
||||
{
|
||||
_snapshot = CreateIdleSnapshot(false);
|
||||
snapshotToPublish = CloneSnapshot(_snapshot);
|
||||
rejectReason = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
rejectReason = "<22><>ǰû<C7B0>п<EFBFBD><D0BF><EFBFBD><EFBFBD>ѵ<EFBFBD><EFBFBD><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
PublishState(snapshotToPublish);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Task CancelAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
ChipPreparationStateSnapshot snapshotToPublish = null;
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (!IsTransientStatus(_snapshot.Status))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
bool keepLoadedChip = _snapshot.IsChipLoaded && _snapshot.Status != ChipPreparationStatus.Unloading;
|
||||
_activeRequest = null;
|
||||
_pendingLoadRequest = null;
|
||||
_snapshot = CreateIdleSnapshot(keepLoadedChip);
|
||||
snapshotToPublish = CloneSnapshot(_snapshot);
|
||||
}
|
||||
|
||||
PublishState(snapshotToPublish);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
ChipPreparationStateSnapshot snapshotToPublish = null;
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (!IsTransientStatus(_snapshot.Status))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_snapshot = CloneSnapshot(_snapshot);
|
||||
_snapshot.Status = ChipPreparationStatus.Paused;
|
||||
_snapshot.CurrentStage = "Paused";
|
||||
_snapshot.LastUpdatedTime = DateTime.Now;
|
||||
snapshotToPublish = CloneSnapshot(_snapshot);
|
||||
}
|
||||
|
||||
PublishState(snapshotToPublish);
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
ChipPreparationStateSnapshot snapshotToPublish = null;
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_snapshot.Status != ChipPreparationStatus.Paused || _activeRequest == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ChipPreparationStatus resumedStatus = ResolveRunningStatus(_activeRequest.Action);
|
||||
_snapshot = CloneSnapshot(_snapshot);
|
||||
_snapshot.Status = resumedStatus;
|
||||
_snapshot.CurrentStage = ResolveStageForStatus(resumedStatus);
|
||||
_snapshot.LastUpdatedTime = DateTime.Now;
|
||||
snapshotToPublish = CloneSnapshot(_snapshot);
|
||||
}
|
||||
|
||||
PublishState(snapshotToPublish);
|
||||
}
|
||||
|
||||
public bool TryResetFault(out string failureReason)
|
||||
{
|
||||
ChipPreparationStateSnapshot snapshotToPublish;
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_snapshot.Status != ChipPreparationStatus.Faulted)
|
||||
{
|
||||
failureReason = "<22><>ǰоƬ<C6AC><D7BC><EFBFBD><EFBFBD>δ<EFBFBD><CEB4><EFBFBD>ڹ<EFBFBD><DAB9><EFBFBD>״̬<D7B4><CCAC>";
|
||||
return false;
|
||||
}
|
||||
|
||||
ResetToIdle(_snapshot.IsChipLoaded);
|
||||
snapshotToPublish = CloneSnapshot(_snapshot);
|
||||
failureReason = string.Empty;
|
||||
}
|
||||
|
||||
PublishState(snapshotToPublish);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<ChipPreparationWaitResult> WaitForStateAsync(bool requireLoaded, TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
if (timeout <= TimeSpan.Zero)
|
||||
{
|
||||
timeout = TimeSpan.FromMilliseconds(100);
|
||||
}
|
||||
|
||||
DateTime deadline = DateTime.Now.Add(timeout);
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
ChipPreparationWaitResult immediateResult = EvaluateWaitResult(requireLoaded);
|
||||
if (immediateResult != null)
|
||||
{
|
||||
return immediateResult;
|
||||
}
|
||||
|
||||
if (DateTime.Now >= deadline)
|
||||
{
|
||||
return ChipPreparationWaitResult.CreateTimeout(requireLoaded
|
||||
? "<22>ȴ<EFBFBD>оƬ<D0BE><C6AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɳ<EFBFBD>ʱ<EFBFBD><CAB1>"
|
||||
: "<22>ȴ<EFBFBD>оƬ<C6AC><D7BC><EFBFBD><EFBFBD><EFBFBD>ɳ<EFBFBD>ʱ<EFBFBD><CAB1>");
|
||||
}
|
||||
|
||||
await Task.Delay(50, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return ChipPreparationWaitResult.CreateCanceled(requireLoaded
|
||||
? "<22>ȴ<EFBFBD>оƬ<D0BE><C6AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD>"
|
||||
: "<22>ȴ<EFBFBD>оƬ<C6AC><D7BC><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD>");
|
||||
}
|
||||
}
|
||||
|
||||
private ChipPreparationWaitResult EvaluateWaitResult(bool requireLoaded)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_snapshot.Status == ChipPreparationStatus.Faulted)
|
||||
{
|
||||
return ChipPreparationWaitResult.CreateFailure(string.IsNullOrWhiteSpace(_snapshot.ErrorMessage)
|
||||
? "оƬ<C6AC><D7BC><EFBFBD><EFBFBD>ִ<EFBFBD><D6B4>ʧ<EFBFBD>ܡ<EFBFBD>"
|
||||
: _snapshot.ErrorMessage);
|
||||
}
|
||||
|
||||
if (requireLoaded)
|
||||
{
|
||||
if (_snapshot.Status == ChipPreparationStatus.Loaded)
|
||||
{
|
||||
return ChipPreparationWaitResult.CreateSuccess();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_snapshot.Status == ChipPreparationStatus.Prepared ||
|
||||
_snapshot.Status == ChipPreparationStatus.Loaded ||
|
||||
(_snapshot.Status == ChipPreparationStatus.Idle && _snapshot.CurrentAction == ChipPreparationAction.Unload))
|
||||
{
|
||||
return ChipPreparationWaitResult.CreateSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
if (_snapshot.Status == ChipPreparationStatus.Idle && _activeRequest == null)
|
||||
{
|
||||
return ChipPreparationWaitResult.CreateFailure("<22><>δ<EFBFBD><CEB4>ʼоƬ<C6AC><D7BC><EFBFBD><EFBFBD><EFBFBD>̡<EFBFBD>") ;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetToIdle(bool keepLoadedChip)
|
||||
{
|
||||
_activeRequest = null;
|
||||
_pendingLoadRequest = null;
|
||||
_snapshot = CreateIdleSnapshot(keepLoadedChip);
|
||||
}
|
||||
|
||||
private void PublishState(ChipPreparationStateSnapshot snapshot)
|
||||
{
|
||||
if (snapshot == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Current.ApplySnapshot(snapshot);
|
||||
_processResultManager.SavePreparationAreaState(snapshot);
|
||||
_eventAggregator.Publish(new ChipPreparationStateChangedEventArgs(CloneSnapshot(snapshot)));
|
||||
}
|
||||
|
||||
private static bool IsTransientStatus(ChipPreparationStatus status)
|
||||
{
|
||||
return status == ChipPreparationStatus.Preparing ||
|
||||
status == ChipPreparationStatus.Loading ||
|
||||
status == ChipPreparationStatus.Unloading ||
|
||||
status == ChipPreparationStatus.Paused;
|
||||
}
|
||||
|
||||
private static bool TryValidateActionRequest(ChipPreparationRequest request, out string rejectReason)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
rejectReason = "<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD>ա<EFBFBD>";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.Action != ChipPreparationAction.Prepare &&
|
||||
request.Action != ChipPreparationAction.Load &&
|
||||
request.Action != ChipPreparationAction.Unload)
|
||||
{
|
||||
rejectReason = "оƬ<C6AC><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ч<EFBFBD><D0A7>";
|
||||
return false;
|
||||
}
|
||||
|
||||
rejectReason = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ChipPreparationStatus ResolveRunningStatus(ChipPreparationAction action)
|
||||
{
|
||||
if (action == ChipPreparationAction.Prepare)
|
||||
{
|
||||
return ChipPreparationStatus.Preparing;
|
||||
}
|
||||
|
||||
if (action == ChipPreparationAction.Load)
|
||||
{
|
||||
return ChipPreparationStatus.Loading;
|
||||
}
|
||||
|
||||
return ChipPreparationStatus.Unloading;
|
||||
}
|
||||
|
||||
private static string ResolveStageForStatus(ChipPreparationStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case ChipPreparationStatus.Preparing:
|
||||
return "Preparing";
|
||||
case ChipPreparationStatus.Prepared:
|
||||
return "Prepared";
|
||||
case ChipPreparationStatus.Loading:
|
||||
return "Loading";
|
||||
case ChipPreparationStatus.Loaded:
|
||||
return "LoadedPendingTransfer";
|
||||
case ChipPreparationStatus.Unloading:
|
||||
return "Unloading";
|
||||
case ChipPreparationStatus.Paused:
|
||||
return "Paused";
|
||||
case ChipPreparationStatus.Faulted:
|
||||
return "Faulted";
|
||||
default:
|
||||
return "Idle";
|
||||
}
|
||||
}
|
||||
|
||||
private static ChipPreparationStateSnapshot CreateIdleSnapshot(bool isChipLoaded)
|
||||
{
|
||||
return new ChipPreparationStateSnapshot
|
||||
{
|
||||
Status = ChipPreparationStatus.Idle,
|
||||
CurrentStage = isChipLoaded ? "LoadedPendingTransfer" : "Idle",
|
||||
LastUpdatedTime = DateTime.Now,
|
||||
CurrentAction = ChipPreparationAction.None,
|
||||
IsChipLoaded = isChipLoaded
|
||||
};
|
||||
}
|
||||
|
||||
private static ChipPreparationStateSnapshot CreateRunningSnapshot(ChipPreparationRequest request, ChipPreparationStatus status, bool isChipLoaded)
|
||||
{
|
||||
return new ChipPreparationStateSnapshot
|
||||
{
|
||||
Status = status,
|
||||
ActiveRequestId = request != null ? request.RequestId : null,
|
||||
RecipeName = request != null ? request.RecipeName : null,
|
||||
SourceStepId = request != null ? request.SourceStepId : null,
|
||||
CurrentStage = ResolveStageForStatus(status),
|
||||
LastUpdatedTime = DateTime.Now,
|
||||
CurrentAction = request != null ? request.Action : ChipPreparationAction.None,
|
||||
IsChipLoaded = isChipLoaded
|
||||
};
|
||||
}
|
||||
|
||||
private static ChipPreparationStateSnapshot CreateStableSnapshot(ChipPreparationStatus status, ChipPreparationRequest request, bool isChipLoaded, string stage)
|
||||
{
|
||||
return new ChipPreparationStateSnapshot
|
||||
{
|
||||
Status = status,
|
||||
ActiveRequestId = request != null ? request.RequestId : null,
|
||||
RecipeName = request != null ? request.RecipeName : null,
|
||||
SourceStepId = request != null ? request.SourceStepId : null,
|
||||
CurrentStage = stage,
|
||||
LastUpdatedTime = DateTime.Now,
|
||||
CurrentAction = request != null ? request.Action : ChipPreparationAction.None,
|
||||
IsChipLoaded = isChipLoaded
|
||||
};
|
||||
}
|
||||
|
||||
private static ChipPreparationStateSnapshot CreateFaultedSnapshot(ChipPreparationRequest request, string errorMessage, bool isChipLoaded)
|
||||
{
|
||||
return new ChipPreparationStateSnapshot
|
||||
{
|
||||
Status = ChipPreparationStatus.Faulted,
|
||||
ActiveRequestId = request != null ? request.RequestId : null,
|
||||
RecipeName = request != null ? request.RecipeName : null,
|
||||
SourceStepId = request != null ? request.SourceStepId : null,
|
||||
CurrentStage = "Faulted",
|
||||
ErrorMessage = string.IsNullOrWhiteSpace(errorMessage) ? "оƬ<C6AC><D7BC><EFBFBD><EFBFBD>ִ<EFBFBD><D6B4>ʧ<EFBFBD>ܡ<EFBFBD>" : errorMessage,
|
||||
LastUpdatedTime = DateTime.Now,
|
||||
CurrentAction = request != null ? request.Action : ChipPreparationAction.None,
|
||||
IsChipLoaded = isChipLoaded
|
||||
};
|
||||
}
|
||||
|
||||
private static ChipPreparationRequest CloneRequest(ChipPreparationRequest request)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ChipPreparationRequest
|
||||
{
|
||||
RequestId = request.RequestId,
|
||||
RecipeName = request.RecipeName,
|
||||
Action = request.Action,
|
||||
SourceStepId = request.SourceStepId
|
||||
};
|
||||
}
|
||||
|
||||
private static ChipPreparationStateSnapshot CloneSnapshot(ChipPreparationStateSnapshot snapshot)
|
||||
{
|
||||
if (snapshot == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ChipPreparationStateSnapshot
|
||||
{
|
||||
Status = snapshot.Status,
|
||||
ActiveRequestId = snapshot.ActiveRequestId,
|
||||
RecipeName = snapshot.RecipeName,
|
||||
SourceStepId = snapshot.SourceStepId,
|
||||
CurrentStage = snapshot.CurrentStage,
|
||||
ErrorMessage = snapshot.ErrorMessage,
|
||||
LastUpdatedTime = snapshot.LastUpdatedTime,
|
||||
CurrentAction = snapshot.CurrentAction,
|
||||
IsChipLoaded = snapshot.IsChipLoaded
|
||||
};
|
||||
}
|
||||
|
||||
private static ChipPreparationStateSnapshot RestoreSnapshot(PreparationAreaProcessState persistedState)
|
||||
{
|
||||
if (persistedState == null)
|
||||
{
|
||||
return CreateIdleSnapshot(false);
|
||||
}
|
||||
|
||||
return new ChipPreparationStateSnapshot
|
||||
{
|
||||
Status = persistedState.Status,
|
||||
ActiveRequestId = persistedState.ActiveRequestId,
|
||||
RecipeName = persistedState.RecipeName,
|
||||
SourceStepId = persistedState.SourceStepId,
|
||||
CurrentStage = string.IsNullOrWhiteSpace(persistedState.CurrentStage)
|
||||
? ResolveStageForStatus(persistedState.Status)
|
||||
: persistedState.CurrentStage,
|
||||
ErrorMessage = persistedState.ErrorMessage,
|
||||
LastUpdatedTime = persistedState.LastUpdatedTime == default(DateTime)
|
||||
? DateTime.Now
|
||||
: persistedState.LastUpdatedTime,
|
||||
CurrentAction = persistedState.CurrentAction,
|
||||
IsChipLoaded = persistedState.HasPreparedChip
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using MainShell.Common;
|
||||
using MW.WorkFlow;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class ChipPreparationSyncActivity : ActivityAbstractBase
|
||||
{
|
||||
private readonly TimeSpan _timeout;
|
||||
|
||||
public ChipPreparationSyncActivity(string name, TimeSpan? timeout = null)
|
||||
: base(name)
|
||||
{
|
||||
_timeout = timeout ?? TimeSpan.FromSeconds(30);
|
||||
}
|
||||
|
||||
protected override async Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
SubstrateLifecycleState substrateState;
|
||||
if (!context.TryGetData<SubstrateLifecycleState>(WorkflowContextKeys.SubstrateProcessState, out substrateState) ||
|
||||
substrateState != SubstrateLifecycleState.HeightMeasured)
|
||||
{
|
||||
return Fail(
|
||||
context,
|
||||
MessageKey.ProcessStepFailedWithReason,
|
||||
new object[]
|
||||
{
|
||||
Name,
|
||||
"<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>δ<EFBFBD><CEB4><EFBFBD>ɲ<EFBFBD><C9B2>ߣ<EFBFBD><DFA3><EFBFBD><EFBFBD>ܽ<EFBFBD><DCBD><EFBFBD>оƬͬ<C6AC><CDAC><EFBFBD>㡣"
|
||||
});
|
||||
}
|
||||
|
||||
bool pendingChipLoad;
|
||||
if (context.TryGetData<bool>(WorkflowContextKeys.PendingChipLoad, out pendingChipLoad) && !pendingChipLoad)
|
||||
{
|
||||
return ActivityResult.Success;
|
||||
}
|
||||
|
||||
PreparationAreaService preparationAreaService;
|
||||
if (!context.TryGetData<PreparationAreaService>(WorkflowContextKeys.PreparationAreaService, out preparationAreaService) || preparationAreaService == null)
|
||||
{
|
||||
throw new InvalidOperationException("δ<>ڹ<EFBFBD><DAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҵ<EFBFBD><EFBFBD><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
}
|
||||
|
||||
ChipPreparationWaitResult waitResult = await preparationAreaService.WaitUntilLoadedAsync(_timeout, activityControl.CancellationToken).ConfigureAwait(false);
|
||||
if (!waitResult.Success)
|
||||
{
|
||||
if (waitResult.IsCanceled)
|
||||
{
|
||||
SetWorkflowFailure(context, string.IsNullOrWhiteSpace(waitResult.ErrorMessage) ? "оƬͬ<C6AC><CDAC><EFBFBD>ȴ<EFBFBD><C8B4><EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD>" : waitResult.ErrorMessage);
|
||||
return ActivityResult.Canceled;
|
||||
}
|
||||
|
||||
return Fail(
|
||||
context,
|
||||
MessageKey.ProcessStepFailedWithReason,
|
||||
new object[]
|
||||
{
|
||||
Name,
|
||||
string.IsNullOrWhiteSpace(waitResult.ErrorMessage) ? "оƬͬ<C6AC><CDAC><EFBFBD>ȴ<EFBFBD>ʧ<EFBFBD>ܡ<EFBFBD>" : waitResult.ErrorMessage
|
||||
});
|
||||
}
|
||||
|
||||
CurrentChipStateService currentChipStateService;
|
||||
if (!context.TryGetData<CurrentChipStateService>(WorkflowContextKeys.CurrentChipStateService, out currentChipStateService) || currentChipStateService == null)
|
||||
{
|
||||
throw new InvalidOperationException("δ<>ڹ<EFBFBD><DAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҵ<EFBFBD><D2B5><EFBFBD>ǰоƬ״̬<D7B4><CCAC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
}
|
||||
|
||||
currentChipStateService.SetState(CurrentChipLifecycleState.LoadedPendingTransfer);
|
||||
context.SetData(WorkflowContextKeys.CurrentChipState, CurrentChipLifecycleState.LoadedPendingTransfer);
|
||||
context.SetData(WorkflowContextKeys.PendingChipLoad, false);
|
||||
context.SetData(WorkflowContextKeys.PreparationAreaStatus, preparationAreaService.CurrentStatus);
|
||||
return ActivityResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using MainShell.Common;
|
||||
using MW.WorkFlow;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class ChipPreparationWaitActivity : ActivityAbstractBase
|
||||
{
|
||||
private readonly TimeSpan _timeout;
|
||||
private readonly bool _consumeOnSuccess;
|
||||
|
||||
public ChipPreparationWaitActivity(string name, TimeSpan? timeout = null, bool consumeOnSuccess = false)
|
||||
: base(name)
|
||||
{
|
||||
_timeout = timeout ?? TimeSpan.FromSeconds(30);
|
||||
_consumeOnSuccess = consumeOnSuccess;
|
||||
}
|
||||
|
||||
protected override async Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
PreparationAreaService preparationAreaService;
|
||||
if (!context.TryGetData<PreparationAreaService>(WorkflowContextKeys.PreparationAreaService, out preparationAreaService) || preparationAreaService == null)
|
||||
{
|
||||
throw new InvalidOperationException("δ<>ڹ<EFBFBD><DAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҵ<EFBFBD><EFBFBD><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
}
|
||||
|
||||
ChipPreparationWaitResult waitResult = await preparationAreaService.WaitUntilReadyAsync(_timeout, activityControl.CancellationToken).ConfigureAwait(false);
|
||||
if (!waitResult.Success)
|
||||
{
|
||||
if (waitResult.IsCanceled)
|
||||
{
|
||||
SetWorkflowFailure(context, string.IsNullOrWhiteSpace(waitResult.ErrorMessage) ? "<22>ȴ<EFBFBD>оƬ<D0BE><C6AC>̨<CCA8><D7BC><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD>" : waitResult.ErrorMessage);
|
||||
return ActivityResult.Canceled;
|
||||
}
|
||||
|
||||
return Fail(
|
||||
context,
|
||||
MessageKey.ProcessStepFailedWithReason,
|
||||
new object[]
|
||||
{
|
||||
Name,
|
||||
string.IsNullOrWhiteSpace(waitResult.ErrorMessage) ? "<22>ȴ<EFBFBD>оƬ<D0BE><C6AC>̨<CCA8><D7BC>ʧ<EFBFBD>ܡ<EFBFBD>" : waitResult.ErrorMessage
|
||||
});
|
||||
}
|
||||
|
||||
if (!_consumeOnSuccess)
|
||||
{
|
||||
return ActivityResult.Success;
|
||||
}
|
||||
|
||||
string rejectReason;
|
||||
if (preparationAreaService.TryConsumeReady(out rejectReason))
|
||||
{
|
||||
context.SetData(WorkflowContextKeys.PreparationAreaStatus, preparationAreaService.CurrentStatus);
|
||||
return ActivityResult.Success;
|
||||
}
|
||||
|
||||
return Fail(
|
||||
context,
|
||||
MessageKey.ProcessStepFailedWithReason,
|
||||
new object[]
|
||||
{
|
||||
Name,
|
||||
string.IsNullOrWhiteSpace(rejectReason) ? "оƬ<D0BE><C6AC>̨<CCA8><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD>ܡ<EFBFBD>" : rejectReason
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Recipe.Models;
|
||||
using MW.WorkFlow;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class ChipUnloadActivity : ActivityAbstractBase
|
||||
{
|
||||
private readonly TimeSpan _timeout;
|
||||
|
||||
public ChipUnloadActivity(string name, TimeSpan? timeout = null)
|
||||
: base(name)
|
||||
{
|
||||
_timeout = timeout ?? TimeSpan.FromSeconds(30);
|
||||
}
|
||||
|
||||
protected override async Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
activityControl.ThrowIfCancellationRequested();
|
||||
|
||||
PreparationAreaService preparationAreaService;
|
||||
if (!context.TryGetData<PreparationAreaService>(WorkflowContextKeys.PreparationAreaService, out preparationAreaService) || preparationAreaService == null)
|
||||
{
|
||||
throw new InvalidOperationException("δ<>ڹ<EFBFBD><DAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҵ<EFBFBD><EFBFBD><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
}
|
||||
|
||||
CurrentChipStateService currentChipStateService;
|
||||
if (!context.TryGetData<CurrentChipStateService>(WorkflowContextKeys.CurrentChipStateService, out currentChipStateService) || currentChipStateService == null)
|
||||
{
|
||||
throw new InvalidOperationException("δ<>ڹ<EFBFBD><DAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҵ<EFBFBD><D2B5><EFBFBD>ǰоƬ״̬<D7B4><CCAC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
}
|
||||
|
||||
AutoProductionRuntimeStateService autoProductionRuntimeStateService;
|
||||
if (!context.TryGetData<AutoProductionRuntimeStateService>(WorkflowContextKeys.AutoProductionRuntimeStateService, out autoProductionRuntimeStateService) || autoProductionRuntimeStateService == null)
|
||||
{
|
||||
throw new InvalidOperationException("δ<>ڹ<EFBFBD><DAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҵ<EFBFBD><D2B5>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̬<EFBFBD><CCAC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
}
|
||||
|
||||
if (currentChipStateService.CurrentState == CurrentChipLifecycleState.Empty)
|
||||
{
|
||||
autoProductionRuntimeStateService.SetCurrentChipRemainingCount(0);
|
||||
context.SetData(WorkflowContextKeys.CurrentChipState, CurrentChipLifecycleState.Empty);
|
||||
context.SetData(WorkflowContextKeys.PendingChipLoad, true);
|
||||
context.SetData(WorkflowContextKeys.PreparationAreaStatus, preparationAreaService.CurrentStatus);
|
||||
context.SetData(WorkflowContextKeys.CurrentChipRemainingCount, autoProductionRuntimeStateService.CurrentChipRemainingCount);
|
||||
return ActivityResult.Success;
|
||||
}
|
||||
|
||||
currentChipStateService.SetState(CurrentChipLifecycleState.Unloading);
|
||||
context.SetData(WorkflowContextKeys.CurrentChipState, CurrentChipLifecycleState.Unloading);
|
||||
|
||||
ChipPreparationRequest request = new ChipPreparationRequest
|
||||
{
|
||||
RecipeName = ResolveRecipeName(context),
|
||||
Action = ChipPreparationAction.Unload,
|
||||
SourceStepId = ResolveSourceStepId(context)
|
||||
};
|
||||
|
||||
string rejectReason;
|
||||
if (!preparationAreaService.TryRequestPrepare(request, out rejectReason))
|
||||
{
|
||||
return Fail(
|
||||
context,
|
||||
MessageKey.ProcessStepFailedWithReason,
|
||||
new object[]
|
||||
{
|
||||
Name,
|
||||
string.IsNullOrWhiteSpace(rejectReason) ? "оƬ<D0BE><C6AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD>ܡ<EFBFBD>" : rejectReason
|
||||
});
|
||||
}
|
||||
|
||||
ChipPreparationWaitResult waitResult = await preparationAreaService.WaitUntilReadyAsync(_timeout, activityControl.CancellationToken).ConfigureAwait(false);
|
||||
if (!waitResult.Success)
|
||||
{
|
||||
if (waitResult.IsCanceled)
|
||||
{
|
||||
SetWorkflowFailure(context, string.IsNullOrWhiteSpace(waitResult.ErrorMessage) ? "оƬ<D0BE><C6AC><EFBFBD>ϵȴ<CFB5><C8B4><EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD>" : waitResult.ErrorMessage);
|
||||
return ActivityResult.Canceled;
|
||||
}
|
||||
|
||||
return Fail(
|
||||
context,
|
||||
MessageKey.ProcessStepFailedWithReason,
|
||||
new object[]
|
||||
{
|
||||
Name,
|
||||
string.IsNullOrWhiteSpace(waitResult.ErrorMessage) ? "оƬ<D0BE><C6AC><EFBFBD>ϵȴ<CFB5>ʧ<EFBFBD>ܡ<EFBFBD>" : waitResult.ErrorMessage
|
||||
});
|
||||
}
|
||||
|
||||
if (!preparationAreaService.TryConsumeReady(out rejectReason))
|
||||
{
|
||||
return Fail(
|
||||
context,
|
||||
MessageKey.ProcessStepFailedWithReason,
|
||||
new object[]
|
||||
{
|
||||
Name,
|
||||
string.IsNullOrWhiteSpace(rejectReason) ? "оƬ<D0BE><C6AC><EFBFBD>Ͻ<EFBFBD><CFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD>ܡ<EFBFBD>" : rejectReason
|
||||
});
|
||||
}
|
||||
|
||||
currentChipStateService.SetState(CurrentChipLifecycleState.Empty);
|
||||
autoProductionRuntimeStateService.SetCurrentChipRemainingCount(0);
|
||||
context.SetData(WorkflowContextKeys.PreparationAreaStatus, preparationAreaService.CurrentStatus);
|
||||
context.SetData(WorkflowContextKeys.CurrentChipState, CurrentChipLifecycleState.Empty);
|
||||
context.SetData(WorkflowContextKeys.PendingChipLoad, true);
|
||||
context.SetData(WorkflowContextKeys.CurrentChipRemainingCount, autoProductionRuntimeStateService.CurrentChipRemainingCount);
|
||||
return ActivityResult.Success;
|
||||
}
|
||||
|
||||
private static string ResolveRecipeName(WorkflowContext context)
|
||||
{
|
||||
RecipeManager recipeManager;
|
||||
if (context.TryGetData<RecipeManager>(WorkflowContextKeys.RecipeManager, out recipeManager) &&
|
||||
recipeManager != null &&
|
||||
recipeManager.CurrentWaferRecipe != null)
|
||||
{
|
||||
return recipeManager.CurrentWaferRecipe.RecipeName;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static string ResolveSourceStepId(WorkflowContext context)
|
||||
{
|
||||
string stepId;
|
||||
if (context.TryGetData<string>(WorkflowContextKeys.CurrentStepId, out stepId))
|
||||
{
|
||||
return stepId;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Recipe.Models;
|
||||
using MW.WorkFlow;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class ChipUnloadRequestActivity : ActivityAbstractBase
|
||||
{
|
||||
public ChipUnloadRequestActivity(string name, TimeSpan? timeout = null) : base(name)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
activityControl.ThrowIfCancellationRequested();
|
||||
|
||||
PreparationAreaService preparationAreaService;
|
||||
if (!context.TryGetData<PreparationAreaService>(WorkflowContextKeys.PreparationAreaService, out preparationAreaService) || preparationAreaService == null)
|
||||
{
|
||||
throw new InvalidOperationException("δ<>ڹ<EFBFBD><DAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҵ<EFBFBD><EFBFBD><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
}
|
||||
|
||||
ChipPreparationRequest request = new ChipPreparationRequest
|
||||
{
|
||||
RecipeName = ResolveRecipeName(context),
|
||||
Action = ChipPreparationAction.Unload,
|
||||
SourceStepId = ResolveSourceStepId(context)
|
||||
};
|
||||
|
||||
string rejectReason;
|
||||
if (preparationAreaService.TryRequestPrepare(request, out rejectReason))
|
||||
{
|
||||
context.SetData(WorkflowContextKeys.PreparationAreaStatus, preparationAreaService.CurrentStatus);
|
||||
return Task.FromResult(ActivityResult.Success);
|
||||
}
|
||||
|
||||
return Task.FromResult(Fail(
|
||||
context,
|
||||
MessageKey.ProcessStepFailedWithReason,
|
||||
new object[]
|
||||
{
|
||||
Name,
|
||||
string.IsNullOrWhiteSpace(rejectReason) ? "оƬ<D0BE><C6AC>̨<EFBFBD><CCA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܾ<F3B1BBBE><DCBE><EFBFBD>" : rejectReason
|
||||
}));
|
||||
}
|
||||
|
||||
private static string ResolveRecipeName(WorkflowContext context)
|
||||
{
|
||||
if (context.TryGetData<RecipeManager>(WorkflowContextKeys.RecipeManager, out var recipeManager) &&
|
||||
recipeManager != null &&
|
||||
recipeManager.CurrentWaferRecipe != null)
|
||||
{
|
||||
return recipeManager.CurrentWaferRecipe.RecipeName;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static string ResolveSourceStepId(WorkflowContext context)
|
||||
{
|
||||
if (context.TryGetData<string>(WorkflowContextKeys.CurrentStepId, out var stepId))
|
||||
{
|
||||
return stepId;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using MainShell.ProcessResult;
|
||||
using System;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class CurrentChipStateService
|
||||
{
|
||||
private readonly ProcessResultManager _processResultManager;
|
||||
private CurrentChipLifecycleState _currentState;
|
||||
|
||||
public CurrentChipStateService(ProcessResultManager processResultManager)
|
||||
{
|
||||
_processResultManager = processResultManager ?? throw new ArgumentNullException(nameof(processResultManager));
|
||||
_currentState = _processResultManager.CurrentChipState.State;
|
||||
}
|
||||
|
||||
public CurrentChipLifecycleState CurrentState
|
||||
{
|
||||
get { return _currentState; }
|
||||
}
|
||||
|
||||
public void SetState(CurrentChipLifecycleState chipState)
|
||||
{
|
||||
_currentState = chipState;
|
||||
_processResultManager.SaveCurrentChipState(chipState);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class PreparationAreaService
|
||||
{
|
||||
private readonly IChipPreparationService _innerService;
|
||||
|
||||
public PreparationAreaService(IChipPreparationService innerService)
|
||||
{
|
||||
_innerService = innerService ?? throw new ArgumentNullException(nameof(innerService));
|
||||
}
|
||||
|
||||
public IChipPreparationService InnerService
|
||||
{
|
||||
get { return _innerService; }
|
||||
}
|
||||
|
||||
public ChipPreparationStatus CurrentStatus
|
||||
{
|
||||
get { return _innerService.Current.Status; }
|
||||
}
|
||||
|
||||
public bool TryRequestPrepare(ChipPreparationRequest request, out string rejectReason)
|
||||
{
|
||||
return _innerService.TryRequestPrepare(request, out rejectReason);
|
||||
}
|
||||
|
||||
public Task<ChipPreparationWaitResult> WaitUntilReadyAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
return _innerService.WaitUntilReadyAsync(timeout, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<ChipPreparationWaitResult> WaitUntilLoadedAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
return _innerService.WaitUntilLoadedAsync(timeout, cancellationToken);
|
||||
}
|
||||
|
||||
public bool TryConsumeReady(out string rejectReason)
|
||||
{
|
||||
return _innerService.TryConsumeReady(out rejectReason);
|
||||
}
|
||||
|
||||
public Task CancelAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return _innerService.CancelAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
_innerService.Pause();
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
_innerService.Resume();
|
||||
}
|
||||
|
||||
public bool TryMarkReady(string requestId, out string rejectReason)
|
||||
{
|
||||
return _innerService.TryMarkReady(requestId, out rejectReason);
|
||||
}
|
||||
|
||||
public bool TryMarkFault(string errorMessage, out string rejectReason)
|
||||
{
|
||||
return _innerService.TryMarkFault(errorMessage, out rejectReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Recipe.Models;
|
||||
using MW.WorkFlow;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class PreparationSignalActivity : ActivityAbstractBase
|
||||
{
|
||||
public PreparationSignalActivity(string name)
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
activityControl.ThrowIfCancellationRequested();
|
||||
|
||||
bool pendingChipLoad;
|
||||
if (context.TryGetData<bool>(WorkflowContextKeys.PendingChipLoad, out pendingChipLoad) && !pendingChipLoad)
|
||||
{
|
||||
return Task.FromResult(ActivityResult.Success);
|
||||
}
|
||||
|
||||
PreparationAreaService preparationAreaService;
|
||||
if (!context.TryGetData<PreparationAreaService>(WorkflowContextKeys.PreparationAreaService, out preparationAreaService) || preparationAreaService == null)
|
||||
{
|
||||
throw new InvalidOperationException("δ<>ڹ<EFBFBD><DAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҵ<EFBFBD><EFBFBD><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
}
|
||||
|
||||
ChipPreparationRequest request = new ChipPreparationRequest
|
||||
{
|
||||
RecipeName = ResolveRecipeName(context),
|
||||
Action = ChipPreparationAction.Prepare,
|
||||
SourceStepId = ResolveSourceStepId(context)
|
||||
};
|
||||
|
||||
string rejectReason;
|
||||
if (preparationAreaService.TryRequestPrepare(request, out rejectReason) || CanIgnoreReject(preparationAreaService))
|
||||
{
|
||||
context.SetData(WorkflowContextKeys.PreparationAreaStatus, preparationAreaService.CurrentStatus);
|
||||
return Task.FromResult(ActivityResult.Success);
|
||||
}
|
||||
|
||||
return Task.FromResult(Fail(
|
||||
context,
|
||||
MessageKey.ProcessStepFailedWithReason,
|
||||
new object[]
|
||||
{
|
||||
Name,
|
||||
string.IsNullOrWhiteSpace(rejectReason) ? "оƬ<C6AC><D7BC><EFBFBD>źŴ<C5BA><C5B4><EFBFBD>ʧ<EFBFBD>ܡ<EFBFBD>" : rejectReason
|
||||
}));
|
||||
}
|
||||
|
||||
private static bool CanIgnoreReject(PreparationAreaService preparationAreaService)
|
||||
{
|
||||
return preparationAreaService.CurrentStatus == ChipPreparationStatus.Preparing ||
|
||||
preparationAreaService.CurrentStatus == ChipPreparationStatus.Prepared ||
|
||||
preparationAreaService.CurrentStatus == ChipPreparationStatus.Loading ||
|
||||
preparationAreaService.CurrentStatus == ChipPreparationStatus.Loaded;
|
||||
}
|
||||
|
||||
private static string ResolveRecipeName(WorkflowContext context)
|
||||
{
|
||||
RecipeManager recipeManager;
|
||||
if (context.TryGetData<RecipeManager>(WorkflowContextKeys.RecipeManager, out recipeManager) &&
|
||||
recipeManager != null &&
|
||||
recipeManager.CurrentWaferRecipe != null)
|
||||
{
|
||||
return recipeManager.CurrentWaferRecipe.RecipeName;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static string ResolveSourceStepId(WorkflowContext context)
|
||||
{
|
||||
string stepId;
|
||||
if (context.TryGetData<string>(WorkflowContextKeys.CurrentStepId, out stepId))
|
||||
{
|
||||
return stepId;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Filewritable;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class FlowStageMappingRule
|
||||
{
|
||||
public string WorkflowName { get; set; }
|
||||
public string ParentActivityName { get; set; }
|
||||
public string ActivityName { get; set; }
|
||||
public FlowDisplayStage Stage { get; set; }
|
||||
public string DisplayFlowName { get; set; }
|
||||
}
|
||||
|
||||
public class FlowStageMappingConfig
|
||||
{
|
||||
public List<FlowStageMappingRule> Rules { get; } = new List<FlowStageMappingRule>();
|
||||
|
||||
public static string DefaultConfigPath => Path.Combine(Paths.BasePath, "Configuration", "FlowStageMapping.json");
|
||||
|
||||
public FlowDisplayStage Resolve(string workflowName, string parentActivityName, string activityName)
|
||||
{
|
||||
var rule = ResolveRule(workflowName, parentActivityName, activityName);
|
||||
|
||||
return rule?.Stage ?? FlowDisplayStage.Align;
|
||||
}
|
||||
|
||||
public string ResolveDisplayFlowName(string workflowName, string parentActivityName, string activityName)
|
||||
{
|
||||
var rule = ResolveRule(workflowName, parentActivityName, activityName);
|
||||
if (rule != null && !string.IsNullOrWhiteSpace(rule.DisplayFlowName))
|
||||
{
|
||||
return rule.DisplayFlowName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(parentActivityName)) return parentActivityName;
|
||||
return workflowName;
|
||||
}
|
||||
|
||||
private FlowStageMappingRule ResolveRule(string workflowName, string parentActivityName, string activityName)
|
||||
{
|
||||
return Rules.FirstOrDefault(r =>
|
||||
Match(r.WorkflowName, workflowName) &&
|
||||
Match(r.ParentActivityName, parentActivityName) &&
|
||||
Match(r.ActivityName, activityName));
|
||||
}
|
||||
|
||||
private static bool Match(string expected, string actual)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(expected) || expected == "*") return true;
|
||||
if (expected.IndexOf('*') >= 0)
|
||||
{
|
||||
var source = actual ?? string.Empty;
|
||||
var parts = expected.Split(new[] { '*' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var index = 0;
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var found = source.IndexOf(part, index, StringComparison.OrdinalIgnoreCase);
|
||||
if (found < 0) return false;
|
||||
index = found + part.Length;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return string.Equals(expected, actual ?? string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static FlowStageMappingConfig LoadOrCreate(string filePath = null)
|
||||
{
|
||||
var path = string.IsNullOrWhiteSpace(filePath) ? DefaultConfigPath : filePath;
|
||||
var dir = Path.GetDirectoryName(path);
|
||||
if (!string.IsNullOrWhiteSpace(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
var def = CreateDefault();
|
||||
def.Save(path);
|
||||
return def;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var loaded = Load(path);
|
||||
if (loaded == null || loaded.Rules.Count == 0)
|
||||
{
|
||||
throw new InvalidDataException("FlowStageMapping config is empty.");
|
||||
}
|
||||
loaded.Normalize();
|
||||
return loaded;
|
||||
}
|
||||
catch
|
||||
{
|
||||
var def = CreateDefault();
|
||||
def.Save(path);
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(string filePath = null)
|
||||
{
|
||||
var path = string.IsNullOrWhiteSpace(filePath) ? DefaultConfigPath : filePath;
|
||||
var ext = Path.GetExtension(path)?.ToLowerInvariant();
|
||||
|
||||
Normalize();
|
||||
|
||||
var data = new FlowStageMappingFile { Rules = Rules.ToList() };
|
||||
|
||||
if (ext == ".xml")
|
||||
{
|
||||
var serializer = new XmlSerializer(typeof(FlowStageMappingFile));
|
||||
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
serializer.Serialize(fs, data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var json = JsonConvert.SerializeObject(data, Formatting.Indented);
|
||||
File.WriteAllText(path, json);
|
||||
}
|
||||
|
||||
public static FlowStageMappingConfig Load(string filePath)
|
||||
{
|
||||
var ext = Path.GetExtension(filePath)?.ToLowerInvariant();
|
||||
FlowStageMappingFile data;
|
||||
|
||||
if (ext == ".xml")
|
||||
{
|
||||
var serializer = new XmlSerializer(typeof(FlowStageMappingFile));
|
||||
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
data = serializer.Deserialize(fs) as FlowStageMappingFile;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var json = File.ReadAllText(filePath);
|
||||
data = JsonConvert.DeserializeObject<FlowStageMappingFile>(json);
|
||||
}
|
||||
|
||||
var cfg = new FlowStageMappingConfig();
|
||||
if (data != null && data.Rules != null)
|
||||
{
|
||||
cfg.Rules.AddRange(data.Rules);
|
||||
}
|
||||
|
||||
cfg.Normalize();
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
private void Normalize()
|
||||
{
|
||||
foreach (var rule in Rules)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rule.DisplayFlowName) &&
|
||||
!string.IsNullOrWhiteSpace(rule.WorkflowName) &&
|
||||
!string.Equals(rule.WorkflowName, ProcessFlowName.AutoProduction, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
rule.DisplayFlowName = rule.WorkflowName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static FlowStageMappingConfig CreateDefault()
|
||||
{
|
||||
var cfg = new FlowStageMappingConfig();
|
||||
|
||||
cfg.Rules.Add(new FlowStageMappingRule
|
||||
{
|
||||
WorkflowName = ProcessFlowName.SubstrateLoadFlow,
|
||||
ParentActivityName = "*",
|
||||
ActivityName = "*",
|
||||
Stage = FlowDisplayStage.Load,
|
||||
DisplayFlowName = ProcessFlowName.SubstrateLoadFlow
|
||||
});
|
||||
|
||||
cfg.Rules.Add(new FlowStageMappingRule
|
||||
{
|
||||
WorkflowName = ProcessFlowName.SubstratePositionFlow,
|
||||
ParentActivityName = "*",
|
||||
ActivityName = "*",
|
||||
Stage = FlowDisplayStage.Align,
|
||||
DisplayFlowName = ProcessFlowName.SubstratePositionFlow
|
||||
});
|
||||
|
||||
cfg.Rules.Add(new FlowStageMappingRule
|
||||
{
|
||||
WorkflowName = ProcessFlowName.ChipStraighteningFlow,
|
||||
ParentActivityName = "*",
|
||||
ActivityName = "*",
|
||||
Stage = FlowDisplayStage.Align,
|
||||
DisplayFlowName = ProcessFlowName.ChipStraighteningFlow
|
||||
});
|
||||
|
||||
cfg.Rules.Add(new FlowStageMappingRule
|
||||
{
|
||||
WorkflowName = ProcessFlowName.DiePositionFlow,
|
||||
ParentActivityName = "*",
|
||||
ActivityName = "*",
|
||||
Stage = FlowDisplayStage.Align,
|
||||
DisplayFlowName = ProcessFlowName.DiePositionFlow
|
||||
});
|
||||
|
||||
cfg.Rules.Add(new FlowStageMappingRule
|
||||
{
|
||||
WorkflowName = ProcessFlowName.DieTransferFlow,
|
||||
ParentActivityName = "*",
|
||||
ActivityName = "*",
|
||||
Stage = FlowDisplayStage.Bond,
|
||||
DisplayFlowName = ProcessFlowName.DieTransferFlow
|
||||
});
|
||||
|
||||
cfg.Rules.Add(new FlowStageMappingRule
|
||||
{
|
||||
WorkflowName = ProcessFlowName.DieRecheckFlow,
|
||||
ParentActivityName = "*",
|
||||
ActivityName = "*",
|
||||
Stage = FlowDisplayStage.Inspect,
|
||||
DisplayFlowName = ProcessFlowName.DieRecheckFlow
|
||||
});
|
||||
|
||||
cfg.Rules.Add(new FlowStageMappingRule
|
||||
{
|
||||
WorkflowName = ProcessFlowName.SubstrateUnloadFlow,
|
||||
ParentActivityName = "*",
|
||||
ActivityName = "*",
|
||||
Stage = FlowDisplayStage.Unload,
|
||||
DisplayFlowName = ProcessFlowName.SubstrateUnloadFlow
|
||||
});
|
||||
|
||||
cfg.Rules.Add(new FlowStageMappingRule
|
||||
{
|
||||
WorkflowName = ProcessFlowName.AutoProduction,
|
||||
ParentActivityName = ProcessFlowName.SubstrateLoadFlow,
|
||||
ActivityName = "*",
|
||||
Stage = FlowDisplayStage.Load,
|
||||
DisplayFlowName = ProcessFlowName.SubstrateLoadFlow
|
||||
});
|
||||
|
||||
cfg.Rules.Add(new FlowStageMappingRule
|
||||
{
|
||||
WorkflowName = ProcessFlowName.AutoProduction,
|
||||
ParentActivityName = ProcessFlowName.SubstratePositionFlow,
|
||||
ActivityName = "*",
|
||||
Stage = FlowDisplayStage.Align,
|
||||
DisplayFlowName = ProcessFlowName.SubstratePositionFlow
|
||||
});
|
||||
|
||||
cfg.Rules.Add(new FlowStageMappingRule
|
||||
{
|
||||
WorkflowName = ProcessFlowName.AutoProduction,
|
||||
ParentActivityName = ProcessFlowName.ChipStraighteningFlow,
|
||||
ActivityName = "*",
|
||||
Stage = FlowDisplayStage.Align,
|
||||
DisplayFlowName = ProcessFlowName.ChipStraighteningFlow
|
||||
});
|
||||
|
||||
cfg.Rules.Add(new FlowStageMappingRule
|
||||
{
|
||||
WorkflowName = ProcessFlowName.AutoProduction,
|
||||
ParentActivityName = ProcessFlowName.DiePositionFlow,
|
||||
ActivityName = "*",
|
||||
Stage = FlowDisplayStage.Align,
|
||||
DisplayFlowName = ProcessFlowName.DiePositionFlow
|
||||
});
|
||||
|
||||
cfg.Rules.Add(new FlowStageMappingRule
|
||||
{
|
||||
WorkflowName = ProcessFlowName.AutoProduction,
|
||||
ParentActivityName = ProcessFlowName.DieTransferFlow,
|
||||
ActivityName = "*",
|
||||
Stage = FlowDisplayStage.Bond,
|
||||
DisplayFlowName = ProcessFlowName.DieTransferFlow
|
||||
});
|
||||
|
||||
cfg.Rules.Add(new FlowStageMappingRule
|
||||
{
|
||||
WorkflowName = ProcessFlowName.AutoProduction,
|
||||
ParentActivityName = ProcessFlowName.DieRecheckFlow,
|
||||
ActivityName = "*",
|
||||
Stage = FlowDisplayStage.Inspect,
|
||||
DisplayFlowName = ProcessFlowName.DieRecheckFlow
|
||||
});
|
||||
|
||||
cfg.Rules.Add(new FlowStageMappingRule
|
||||
{
|
||||
WorkflowName = ProcessFlowName.AutoProduction,
|
||||
ParentActivityName = ProcessFlowName.SubstrateUnloadFlow,
|
||||
ActivityName = "*",
|
||||
Stage = FlowDisplayStage.Unload,
|
||||
DisplayFlowName = ProcessFlowName.SubstrateUnloadFlow
|
||||
});
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
[XmlRoot("FlowStageMapping")]
|
||||
public class FlowStageMappingFile
|
||||
{
|
||||
[XmlArray("Rules")]
|
||||
[XmlArrayItem("Rule")]
|
||||
public List<FlowStageMappingRule> Rules { get; set; } = new List<FlowStageMappingRule>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Filewritable;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class WaferHandlingFlowNodeRule
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
public List<string> ActivityKeywords { get; set; } = new List<string>();
|
||||
public List<string> WorkflowKeywords { get; set; } = new List<string>();
|
||||
public List<string> ParentKeywords { get; set; } = new List<string>();
|
||||
}
|
||||
|
||||
public class WaferHandlingFlowConfig
|
||||
{
|
||||
public List<WaferHandlingFlowNodeRule> Nodes { get; } = new List<WaferHandlingFlowNodeRule>();
|
||||
|
||||
public static string DefaultConfigPath => Path.Combine(Paths.BasePath, "Configuration", "WaferHandlingFlow.json");
|
||||
|
||||
public static WaferHandlingFlowConfig LoadOrCreate(string filePath = null)
|
||||
{
|
||||
var path = string.IsNullOrWhiteSpace(filePath) ? DefaultConfigPath : filePath;
|
||||
var dir = Path.GetDirectoryName(path);
|
||||
if (!string.IsNullOrWhiteSpace(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
var def = CreateDefault();
|
||||
def.Save(path);
|
||||
return def;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var loaded = Load(path);
|
||||
if (loaded == null || loaded.Nodes.Count == 0)
|
||||
{
|
||||
throw new InvalidDataException("WaferHandlingFlow config is empty.");
|
||||
}
|
||||
loaded.Normalize();
|
||||
return loaded;
|
||||
}
|
||||
catch
|
||||
{
|
||||
var def = CreateDefault();
|
||||
def.Save(path);
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
public static WaferHandlingFlowConfig Load(string filePath)
|
||||
{
|
||||
var json = File.ReadAllText(filePath);
|
||||
var data = JsonConvert.DeserializeObject<WaferHandlingFlowFile>(json);
|
||||
|
||||
var cfg = new WaferHandlingFlowConfig();
|
||||
if (data?.Nodes != null)
|
||||
{
|
||||
cfg.Nodes.AddRange(data.Nodes);
|
||||
}
|
||||
|
||||
cfg.Normalize();
|
||||
return cfg;
|
||||
}
|
||||
|
||||
public void Save(string filePath = null)
|
||||
{
|
||||
var path = string.IsNullOrWhiteSpace(filePath) ? DefaultConfigPath : filePath;
|
||||
Normalize();
|
||||
|
||||
var data = new WaferHandlingFlowFile
|
||||
{
|
||||
Nodes = Nodes.ToList()
|
||||
};
|
||||
|
||||
var json = JsonConvert.SerializeObject(data, Formatting.Indented);
|
||||
File.WriteAllText(path, json);
|
||||
}
|
||||
|
||||
public string ResolveNodeKey(string workflowName, string parentActivityName, string activityName)
|
||||
{
|
||||
var activity = activityName ?? string.Empty;
|
||||
var workflow = workflowName ?? string.Empty;
|
||||
var parent = parentActivityName ?? string.Empty;
|
||||
|
||||
var rule = Nodes.FirstOrDefault(n => IsMatch(n, activity, workflow, parent));
|
||||
return rule?.Key;
|
||||
}
|
||||
|
||||
private static bool IsMatch(WaferHandlingFlowNodeRule node, string activity, string workflow, string parent)
|
||||
{
|
||||
if (node == null) return false;
|
||||
|
||||
return ContainsAny(activity, node.ActivityKeywords) ||
|
||||
ContainsAny(workflow, node.WorkflowKeywords) ||
|
||||
ContainsAny(parent, node.ParentKeywords);
|
||||
}
|
||||
|
||||
private static bool ContainsAny(string source, List<string> keywords)
|
||||
{
|
||||
if (keywords == null || keywords.Count == 0) return false;
|
||||
|
||||
foreach (var keyword in keywords)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(keyword)) continue;
|
||||
if ((source ?? string.Empty).IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void Normalize()
|
||||
{
|
||||
Nodes.RemoveAll(n => n == null || string.IsNullOrWhiteSpace(n.Key));
|
||||
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(node.DisplayName))
|
||||
{
|
||||
node.DisplayName = node.Key;
|
||||
}
|
||||
|
||||
node.ActivityKeywords = node.ActivityKeywords ?? new List<string>();
|
||||
node.WorkflowKeywords = node.WorkflowKeywords ?? new List<string>();
|
||||
node.ParentKeywords = node.ParentKeywords ?? new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
public static WaferHandlingFlowConfig CreateDefault()
|
||||
{
|
||||
var cfg = new WaferHandlingFlowConfig();
|
||||
|
||||
cfg.Nodes.Add(new WaferHandlingFlowNodeRule
|
||||
{
|
||||
Key = "WaferLoad",
|
||||
DisplayName = "<22><>Բ<EFBFBD><D4B2><EFBFBD><EFBFBD>",
|
||||
ActivityKeywords = new List<string> { "ChipLoad", ProcessFlowName.ChipLoadExecuteActivity }
|
||||
});
|
||||
|
||||
cfg.Nodes.Add(new WaferHandlingFlowNodeRule
|
||||
{
|
||||
Key = "WaferSupply",
|
||||
DisplayName = "<22><>Ƭ<C6AC><D7BC>",
|
||||
WorkflowKeywords = new List<string>
|
||||
{
|
||||
ProcessFlowName.ChipStraighteningFlow,
|
||||
ProcessFlowName.DiePositionFlow,
|
||||
ProcessFlowName.DieTransferFlow,
|
||||
ProcessFlowName.DieRecheckFlow
|
||||
},
|
||||
ParentKeywords = new List<string> { "DieAlign", "DieBond", "Inspect" }
|
||||
});
|
||||
|
||||
cfg.Nodes.Add(new WaferHandlingFlowNodeRule
|
||||
{
|
||||
Key = "WaferUnload",
|
||||
DisplayName = "<22><>Բ<EFBFBD><D4B2><EFBFBD><EFBFBD>",
|
||||
ActivityKeywords = new List<string> { "ChipUnload", ProcessFlowName.ChipUnloadExecuteActivity }
|
||||
});
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
public class WaferHandlingFlowFile
|
||||
{
|
||||
public List<WaferHandlingFlowNodeRule> Nodes { get; set; } = new List<WaferHandlingFlowNodeRule>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public interface IWaferRecognitionResultCollector
|
||||
{
|
||||
void Add(WaferRecognitionFrameResult frameResult);
|
||||
|
||||
WaferRecognitionSnapshot CreateSnapshot();
|
||||
|
||||
void Reset();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public interface IWaferScanPlanner
|
||||
{
|
||||
WaferScanPlan CreatePlan(WaferDiePositionContext context);
|
||||
}
|
||||
}
|
||||
@@ -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 轴。");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public enum DieTransferRowDirection
|
||||
{
|
||||
Positive = 0,
|
||||
Negative = 1
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="2KCsfwtPIocPMLLmmFXo" name="Page-1">
|
||||
<mxGraphModel dx="844" dy="779" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="11" style="edgeStyle=none;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="2" target="3" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="12" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="2" target="4" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="2" value="<22><><EFBFBD>̿<EFBFBD>ʼ" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry y="340" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="14" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="3" target="5" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="3" value="<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="160" y="280" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="13" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="4" target="7" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="4" value="оƬ<C6AC><D7BC>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="160" y="400" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="15" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="5" target="6" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="5" value="<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="320" y="280" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="17" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="6" target="8" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="6" value="оƬ<D0BE><C6AC>ֱ" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="480" y="280" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="16" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="7" target="6" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="7" value="оƬ<D0BE><C6AC><EFBFBD><EFBFBD>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="320" y="400" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="18" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="8" target="9" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="8" value="оƬ<D0BE><C6AC>λ" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="640" y="280" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="21" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9" target="19" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="9" value="оƬ<D0BE><C6AC>λ" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="800" y="280" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="22" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="19" target="20" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="24" style="edgeStyle=none;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="19" target="23" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="19" value="оƬת<C6AC><D7AA>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="960" y="280" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="20" value="<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="1120" y="280" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="27" style="edgeStyle=none;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="23" target="26" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="23" value="оƬ<C6AC><D7BC>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="960" y="490" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="25" value="оƬ<D0BE><C6AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>С<EFBFBD>ڻ<EFBFBD><DABB><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD>" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" parent="1" vertex="1">
|
||||
<mxGeometry x="970" y="408" width="180" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="26" value="оƬ<D0BE><C6AC><EFBFBD><EFBFBD>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="960" y="630" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="28" value="<22>ȴ<EFBFBD>ת<EFBFBD><D7AA><EFBFBD><EFBFBD><EFBFBD>ɺ<EFBFBD>" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" parent="1" vertex="1">
|
||||
<mxGeometry x="1015" y="568" width="110" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="29" style="edgeStyle=none;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="26" target="7" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public static class ProcessAlarmIds
|
||||
{
|
||||
public const int WorkflowFaulted = 14000;
|
||||
|
||||
public const int RuntimeValidationFailed = 14001;
|
||||
|
||||
public const int Timeout = 14002;
|
||||
|
||||
public const int GenericRuntimeFailure = 14003;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Log;
|
||||
using MainShell.ProcessResult;
|
||||
using MW.WorkFlow;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class SubstrateHeightMeasureActivity : ActivityAbstractBase
|
||||
{
|
||||
private readonly SubstrateHeightMeasureService _substrateHeightMeasureService;
|
||||
|
||||
public SubstrateHeightMeasureActivity(string name, SubstrateHeightMeasureService substrateHeightMeasureService)
|
||||
: base(name)
|
||||
{
|
||||
_substrateHeightMeasureService = substrateHeightMeasureService ?? throw new ArgumentNullException(nameof(substrateHeightMeasureService));
|
||||
}
|
||||
|
||||
protected override async Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
await _substrateHeightMeasureService.ExecuteAsync(context, activityControl).ConfigureAwait(false);
|
||||
|
||||
SubstrateHeightMeasureProcessResult result = context.GetData<SubstrateHeightMeasureProcessResult>(WorkflowContextKeys.SubstrateHeightMeasureResult);
|
||||
if (result == null || !result.IsSuccess)
|
||||
{
|
||||
MessageKey failureMessageKey = result != null && result.ErrorMessageKey != MessageKey.None
|
||||
? result.ErrorMessageKey
|
||||
: MessageKey.ProcessSubstrateHeightMeasureFailedWithReason;
|
||||
|
||||
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($"SubstrateHeightMeasure activity failed: {errorMessage}");
|
||||
return Fail(context, failureMessageKey, failureMessageArguments);
|
||||
}
|
||||
|
||||
context.SetData(WorkflowContextKeys.SubstrateProcessState, SubstrateLifecycleState.HeightMeasured);
|
||||
return ActivityResult.Success;
|
||||
}
|
||||
|
||||
private static object[] ConvertToObjectArray(string[] arguments)
|
||||
{
|
||||
return arguments == null ? Array.Empty<object>() : arguments.Cast<object>().ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.HeightMeasure.Model;
|
||||
using MainShell.HeightMeasure.Service;
|
||||
using MainShell.Log;
|
||||
using MainShell.Models;
|
||||
using MainShell.ProcessResult;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Recipe.Models.SubstrateParameter;
|
||||
using MW.WorkFlow;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class SubstrateHeightMeasureService
|
||||
{
|
||||
private readonly IHeightMeasure _heightMeasure;
|
||||
private readonly HeightMeasureMotionService _heightMeasureMotionService;
|
||||
|
||||
public SubstrateHeightMeasureService(IHeightMeasure heightMeasure, HeightMeasureMotionService heightMeasureMotionService)
|
||||
{
|
||||
_heightMeasure = heightMeasure ?? throw new ArgumentNullException(nameof(heightMeasure));
|
||||
_heightMeasureMotionService = heightMeasureMotionService ?? throw new ArgumentNullException(nameof(heightMeasureMotionService));
|
||||
}
|
||||
|
||||
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);
|
||||
SubstrateRecipe substrateRecipe = GetCurrentRecipe(recipeManager);
|
||||
IList<SubstrateHeightMeasurePoint> points = GetAvailablePoints(substrateRecipe);
|
||||
SubstrateHeightMeasureProcessResult processResult = InitializeProcessResult(processResultManager, substrateRecipe.RecipeName);
|
||||
|
||||
try
|
||||
{
|
||||
LogManager.LogProcessInfo($"SubstrateHeightMeasure: start. Recipe={substrateRecipe.RecipeName}, PointCount={points.Count}");
|
||||
|
||||
for (int index = 0; index < points.Count; index++)
|
||||
{
|
||||
SubstrateHeightMeasurePoint point = points[index];
|
||||
string pointName = GetPointName(point, index);
|
||||
|
||||
await CheckCancellationAndPauseAsync(activityControl).ConfigureAwait(false);
|
||||
|
||||
double positionX;
|
||||
double positionY;
|
||||
ResolveMeasurePosition(substrateRecipe, point, pointName, out positionX, out positionY);
|
||||
|
||||
LogManager.LogProcessDebug($"SubstrateHeightMeasure: moving to point[{index}] '{pointName}', X={positionX:F4}, Y={positionY:F4}");
|
||||
_heightMeasureMotionService.MoveWaferMeasurePose(positionX, positionY);
|
||||
|
||||
double heightValue = _heightMeasure.GetDistance();
|
||||
processResult.PointResults.Add(new SubstrateHeightMeasurePointResult
|
||||
{
|
||||
PointName = pointName,
|
||||
RowIndex = point.RowIndex,
|
||||
ColumnIndex = point.ColumnIndex,
|
||||
PositionX = positionX,
|
||||
PositionY = positionY,
|
||||
HeightValue = heightValue
|
||||
});
|
||||
|
||||
LogManager.LogProcessInfo($"SubstrateHeightMeasure: point[{index}] '{pointName}' measured height={heightValue:F4}");
|
||||
}
|
||||
|
||||
processResult.IsSuccess = true;
|
||||
processResult.ErrorMessage = null;
|
||||
processResult.ErrorMessageKey = MessageKey.None;
|
||||
processResult.ErrorMessageArguments = Array.Empty<string>();
|
||||
LogManager.LogProcessInfo($"SubstrateHeightMeasure: completed. Recipe={substrateRecipe.RecipeName}, PointCount={processResult.PointResults.Count}");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
processResult.IsSuccess = false;
|
||||
processResult.ErrorMessage = null;
|
||||
processResult.ErrorMessageKey = MessageKey.None;
|
||||
processResult.ErrorMessageArguments = Array.Empty<string>();
|
||||
LogManager.LogProcessInfo("SubstrateHeightMeasure: operation canceled.");
|
||||
throw;
|
||||
}
|
||||
catch (LocalizedProcessException ex)
|
||||
{
|
||||
processResult.IsSuccess = false;
|
||||
processResult.ErrorMessageKey = ex.FailureMessageKey;
|
||||
processResult.ErrorMessageArguments = ex.FailureMessageArguments;
|
||||
processResult.ErrorMessage = ex.Message;
|
||||
LogManager.LogProcessError($"SubstrateHeightMeasure: {ex.Message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
processResult.IsSuccess = false;
|
||||
processResult.ErrorMessageKey = MessageKey.ProcessSubstrateHeightMeasureFailedWithReason;
|
||||
processResult.ErrorMessageArguments = new string[] { ex.Message ?? string.Empty };
|
||||
processResult.ErrorMessage = LanguageResourceHelper.Format(
|
||||
processResult.ErrorMessageKey,
|
||||
LocalizedProcessException.ConvertArguments(processResult.ErrorMessageArguments));
|
||||
LogManager.LogProcessError($"SubstrateHeightMeasure: {processResult.ErrorMessage}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
WriteResultToContext(context, processResultManager, processResult);
|
||||
}
|
||||
}
|
||||
|
||||
private static SubstrateRecipe GetCurrentRecipe(RecipeManager recipeManager)
|
||||
{
|
||||
if (recipeManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(recipeManager));
|
||||
}
|
||||
|
||||
if (recipeManager.CurrentSubstrateRecipe == null)
|
||||
{
|
||||
throw new LocalizedProcessException(
|
||||
MessageKey.ProcessSubstrateHeightMeasureRecipeNotLoaded,
|
||||
Array.Empty<string>());
|
||||
}
|
||||
|
||||
return recipeManager.CurrentSubstrateRecipe;
|
||||
}
|
||||
|
||||
private static IList<SubstrateHeightMeasurePoint> GetAvailablePoints(SubstrateRecipe substrateRecipe)
|
||||
{
|
||||
if (substrateRecipe == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(substrateRecipe));
|
||||
}
|
||||
|
||||
if (substrateRecipe.HeightMeasureSetting == null || substrateRecipe.HeightMeasureSetting.Points == null)
|
||||
{
|
||||
throw new LocalizedProcessException(
|
||||
MessageKey.ProcessSubstrateHeightMeasureSettingMissing,
|
||||
Array.Empty<string>());
|
||||
}
|
||||
|
||||
List<SubstrateHeightMeasurePoint> points = new List<SubstrateHeightMeasurePoint>();
|
||||
foreach (SubstrateHeightMeasurePoint point in substrateRecipe.HeightMeasureSetting.Points)
|
||||
{
|
||||
if (point != null)
|
||||
{
|
||||
points.Add(point);
|
||||
}
|
||||
}
|
||||
|
||||
if (points.Count == 0)
|
||||
{
|
||||
throw new LocalizedProcessException(
|
||||
MessageKey.ProcessSubstrateHeightMeasureNoPoints,
|
||||
Array.Empty<string>());
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
private static SubstrateHeightMeasureProcessResult InitializeProcessResult(ProcessResultManager processResultManager, string recipeName)
|
||||
{
|
||||
if (processResultManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(processResultManager));
|
||||
}
|
||||
|
||||
SubstrateHeightMeasureProcessResult processResult = processResultManager.SubstrateHeightMeasureResult;
|
||||
processResult.IsSuccess = false;
|
||||
processResult.RecipeName = recipeName;
|
||||
processResult.ErrorMessage = null;
|
||||
processResult.ErrorMessageKey = MessageKey.None;
|
||||
processResult.ErrorMessageArguments = Array.Empty<string>();
|
||||
processResult.PointResults.Clear();
|
||||
return processResult;
|
||||
}
|
||||
|
||||
private static async Task CheckCancellationAndPauseAsync(ActivityControl activityControl)
|
||||
{
|
||||
activityControl.ThrowIfCancellationRequested();
|
||||
await activityControl.CheckPauseAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static void ResolveMeasurePosition(SubstrateRecipe substrateRecipe, SubstrateHeightMeasurePoint point, string pointName, out double positionX, out double positionY)
|
||||
{
|
||||
if (substrateRecipe == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(substrateRecipe));
|
||||
}
|
||||
|
||||
if (point == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(point));
|
||||
}
|
||||
|
||||
if (substrateRecipe.HeightMeasureSetting != null && substrateRecipe.HeightMeasureSetting.Mode == SubstrateHeightMeasureMode.RowColumnOffset)
|
||||
{
|
||||
if (substrateRecipe.SubstrateInfo == null || substrateRecipe.SubstrateInfo.PitchX <= 0 || substrateRecipe.SubstrateInfo.PitchY <= 0)
|
||||
{
|
||||
throw new LocalizedProcessException(
|
||||
MessageKey.ProcessSubstrateHeightMeasurePointPositionInvalid,
|
||||
pointName);
|
||||
}
|
||||
|
||||
if (point.RowIndex <= 0 || point.ColumnIndex <= 0)
|
||||
{
|
||||
throw new LocalizedProcessException(
|
||||
MessageKey.ProcessSubstrateHeightMeasurePointPositionInvalid,
|
||||
pointName);
|
||||
}
|
||||
|
||||
MPoint commonOffset = substrateRecipe.HeightMeasureSetting.CommonOffsetCompensation ?? new MPoint();
|
||||
MPoint pointOffset = point.OffsetCompensation ?? new MPoint();
|
||||
positionX = (point.ColumnIndex - 1) * substrateRecipe.SubstrateInfo.PitchX + commonOffset.X + pointOffset.X;
|
||||
positionY = (point.RowIndex - 1) * substrateRecipe.SubstrateInfo.PitchY + commonOffset.Y + pointOffset.Y;
|
||||
return;
|
||||
}
|
||||
|
||||
if (point.TeachPosition == null)
|
||||
{
|
||||
throw new LocalizedProcessException(
|
||||
MessageKey.ProcessSubstrateHeightMeasurePointPositionInvalid,
|
||||
pointName);
|
||||
}
|
||||
|
||||
positionX = point.TeachPosition.X;
|
||||
positionY = point.TeachPosition.Y;
|
||||
}
|
||||
|
||||
private static string GetPointName(SubstrateHeightMeasurePoint point, int index)
|
||||
{
|
||||
if (point == null || string.IsNullOrWhiteSpace(point.PointName))
|
||||
{
|
||||
return $"Point{index + 1}";
|
||||
}
|
||||
|
||||
return point.PointName;
|
||||
}
|
||||
|
||||
private static void WriteResultToContext(
|
||||
WorkflowContext context,
|
||||
ProcessResultManager processResultManager,
|
||||
SubstrateHeightMeasureProcessResult processResult)
|
||||
{
|
||||
context.SetData(WorkflowContextKeys.SubstrateHeightMeasureResult, processResult);
|
||||
processResultManager.SaveSubstrateHeightMeasureResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MW.WorkFlow;
|
||||
using MainShell.Common;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class SubstrateLoadActivity : ActivityAbstractBase
|
||||
{
|
||||
public SubstrateLoadActivity(string name) : base(name)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override async Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
activityControl.ThrowIfCancellationRequested();
|
||||
await activityControl.CheckPauseAsync();
|
||||
await Task.Delay(100, activityControl.CancellationToken);
|
||||
}
|
||||
|
||||
context.SetData(WorkflowContextKeys.SubstrateProcessState, SubstrateLifecycleState.Loaded);
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Log;
|
||||
using MainShell.ProcessResult;
|
||||
using MW.WorkFlow;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class SubstratePositionActivity : ActivityAbstractBase
|
||||
{
|
||||
private readonly SubstratePositionMotionService _motionService;
|
||||
|
||||
public SubstratePositionActivity(string name, SubstratePositionMotionService 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);
|
||||
|
||||
SubstratePositionProcessResult result = context.GetData<SubstratePositionProcessResult>(WorkflowContextKeys.SubstratePositionResult);
|
||||
if (result == null || !result.IsSuccess)
|
||||
{
|
||||
MessageKey failureMessageKey = result != null && result.ErrorMessageKey != MessageKey.None
|
||||
? result.ErrorMessageKey
|
||||
: MessageKey.ProcessSubstratePositionFailedWithReason;
|
||||
|
||||
object[] failureMessageArguments = result != null
|
||||
? ConvertToObjectArray(result.ErrorMessageArguments)
|
||||
: new object[] { LanguageResourceHelper.GetString(MessageKey.CommonUnknownError) };
|
||||
|
||||
string errorMsg = result != null && !string.IsNullOrWhiteSpace(result.ErrorMessage)
|
||||
? result.ErrorMessage
|
||||
: LanguageResourceHelper.Format(failureMessageKey, failureMessageArguments);
|
||||
|
||||
LogManager.LogSysError($"SubstratePosition activity failed: {errorMsg}");
|
||||
return Fail(context, failureMessageKey, failureMessageArguments);
|
||||
}
|
||||
|
||||
context.SetData(WorkflowContextKeys.SubstrateProcessState, SubstrateLifecycleState.Positioned);
|
||||
|
||||
return ActivityResult.Success;
|
||||
}
|
||||
|
||||
private static object[] ConvertToObjectArray(string[] arguments)
|
||||
{
|
||||
return arguments == null ? Array.Empty<object>() : arguments.Cast<object>().ToArray();
|
||||
}
|
||||
|
||||
protected override void PrepareExecute(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void AfterExecute(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Log;
|
||||
using MainShell.Motion;
|
||||
using MainShell.ProcessResult;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Recipe.Models.SubstrateParameter;
|
||||
using MW.WorkFlow;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class SubstratePositionMotionService
|
||||
{
|
||||
private readonly ApproachAlignmentService _approachAlignmentService;
|
||||
|
||||
public SubstratePositionMotionService(ApproachAlignmentService approachAlignmentService)
|
||||
{
|
||||
_approachAlignmentService = approachAlignmentService ?? throw new ArgumentNullException(nameof(approachAlignmentService));
|
||||
}
|
||||
|
||||
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 = GetRecipeManager(context);
|
||||
ProcessResultManager processResultManager = GetProcessResultManager(context);
|
||||
SubstrateRecipe substrateRecipe = GetCurrentRecipe(recipeManager);
|
||||
List<MarkData> enabledMarks = GetEnabledMarks(substrateRecipe);
|
||||
SubstratePositionProcessResult processResult = InitializeProcessResult(processResultManager, enabledMarks.Count);
|
||||
|
||||
try
|
||||
{
|
||||
LogManager.LogInfo($"SubstratePosition: start mark positioning. Total enabled marks: {enabledMarks.Count}");
|
||||
|
||||
for (int i = 0; i < enabledMarks.Count; i++)
|
||||
{
|
||||
MarkData markData = enabledMarks[i];
|
||||
string markName = string.IsNullOrWhiteSpace(markData?.TemplateName) ? $"Mark_{i}" : markData.TemplateName;
|
||||
|
||||
await CheckCancellationAndPauseAsync(activityControl).ConfigureAwait(false);
|
||||
|
||||
LogManager.LogInfo($"SubstratePosition: executing approach alignment for mark[{i}] '{markName}'");
|
||||
ApproachAlignmentResult alignmentResult = await ExecuteApproachAlignmentAsync(markData, activityControl).ConfigureAwait(false);
|
||||
|
||||
if (!alignmentResult.Succeeded)
|
||||
{
|
||||
string reason = alignmentResult.Message ?? string.Empty;
|
||||
string errorMsg = $"SubstratePosition: approach alignment failed for mark[{i}] '{markName}'. Reason: {reason}";
|
||||
LogManager.LogSysError(errorMsg);
|
||||
throw new LocalizedProcessException(
|
||||
MessageKey.ProcessSubstratePositionAlignmentFailedWithReason,
|
||||
new[] { markName, reason });
|
||||
}
|
||||
|
||||
Point recognizedPos = ExtractRecognizedPosition(alignmentResult, markData);
|
||||
processResult.MarkResults.Add(recognizedPos);
|
||||
processResult.RecognizedMarkCount++;
|
||||
|
||||
LogManager.LogInfo(
|
||||
string.Format(
|
||||
"SubstratePosition: mark[{0}] aligned successfully. Iterations: {1}. Position: ({2:F4}, {3:F4})",
|
||||
i,
|
||||
alignmentResult.CompletedIterations,
|
||||
recognizedPos.X,
|
||||
recognizedPos.Y));
|
||||
}
|
||||
|
||||
processResult.SubstrateAngle = CalculateSubstrateAngle(processResult.MarkResults);
|
||||
processResult.IsSuccess = true;
|
||||
processResult.ErrorMessage = null;
|
||||
processResult.ErrorMessageKey = MessageKey.None;
|
||||
processResult.ErrorMessageArguments = Array.Empty<string>();
|
||||
|
||||
LogManager.LogInfo(
|
||||
string.Format(
|
||||
"SubstratePosition: completed successfully. Recognized: {0}/{1}, Angle: {2:F6}",
|
||||
processResult.RecognizedMarkCount,
|
||||
processResult.AvailableMarkCount,
|
||||
processResult.SubstrateAngle));
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
processResult.IsSuccess = false;
|
||||
processResult.ErrorMessage = null;
|
||||
processResult.ErrorMessageKey = MessageKey.None;
|
||||
processResult.ErrorMessageArguments = Array.Empty<string>();
|
||||
LogManager.LogInfo("SubstratePosition: operation canceled.");
|
||||
throw;
|
||||
}
|
||||
catch (LocalizedProcessException ex)
|
||||
{
|
||||
processResult.IsSuccess = false;
|
||||
processResult.ErrorMessageKey = ex.FailureMessageKey;
|
||||
processResult.ErrorMessageArguments = ex.FailureMessageArguments;
|
||||
processResult.ErrorMessage = ex.Message;
|
||||
LogManager.LogSysError(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
processResult.IsSuccess = false;
|
||||
processResult.ErrorMessageKey = MessageKey.ProcessStepFailedWithReason;
|
||||
processResult.ErrorMessageArguments = new[] { ProcessFlowName.SubstratePositionFlow, ex.Message ?? string.Empty };
|
||||
processResult.ErrorMessage = LanguageResourceHelper.Format(
|
||||
processResult.ErrorMessageKey,
|
||||
LocalizedProcessException.ConvertArguments(processResult.ErrorMessageArguments));
|
||||
LogManager.LogSysError(processResult.ErrorMessage);
|
||||
}
|
||||
finally
|
||||
{
|
||||
WriteResultToContext(context, processResultManager, processResult);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ApproachAlignmentResult> ExecuteApproachAlignmentAsync(MarkData markData, ActivityControl activityControl)
|
||||
{
|
||||
if (markData == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(markData));
|
||||
}
|
||||
|
||||
List<ApproachAlignmentAxis> axes = new List<ApproachAlignmentAxis>
|
||||
{
|
||||
new ApproachAlignmentAxis("X", 0.1),
|
||||
new ApproachAlignmentAxis("Y", 0.1)
|
||||
};
|
||||
|
||||
ApproachAlignmentRequest request = new ApproachAlignmentRequest(axes)
|
||||
{
|
||||
MaxIterations = 5,
|
||||
MoveTimeoutMilliseconds = 30000,
|
||||
RecognitionTimeoutMilliseconds = 10000
|
||||
};
|
||||
|
||||
return await _approachAlignmentService
|
||||
.ApproachAlignmentAsync(request, activityControl.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static Point ExtractRecognizedPosition(ApproachAlignmentResult alignmentResult, MarkData markData)
|
||||
{
|
||||
if (alignmentResult == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(alignmentResult));
|
||||
}
|
||||
|
||||
if (alignmentResult.FinalAxisPositions == null ||
|
||||
!alignmentResult.FinalAxisPositions.TryGetValue("X", out var x) ||
|
||||
!alignmentResult.FinalAxisPositions.TryGetValue("Y", out var y))
|
||||
{
|
||||
throw new LocalizedProcessException(
|
||||
MessageKey.ProcessSubstratePositionAlignmentFailedWithReason,
|
||||
new[]
|
||||
{
|
||||
string.IsNullOrWhiteSpace(markData?.TemplateName) ? string.Empty : markData.TemplateName,
|
||||
"Recognized position is missing X or Y axis data."
|
||||
});
|
||||
}
|
||||
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
private static async Task CheckCancellationAndPauseAsync(ActivityControl activityControl)
|
||||
{
|
||||
activityControl.ThrowIfCancellationRequested();
|
||||
await activityControl.CheckPauseAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static RecipeManager GetRecipeManager(WorkflowContext context)
|
||||
{
|
||||
return context.GetData<RecipeManager>(WorkflowContextKeys.RecipeManager);
|
||||
}
|
||||
|
||||
private static ProcessResultManager GetProcessResultManager(WorkflowContext context)
|
||||
{
|
||||
return context.GetData<ProcessResultManager>(WorkflowContextKeys.ProcessResultManager);
|
||||
}
|
||||
|
||||
private static SubstrateRecipe GetCurrentRecipe(RecipeManager recipeManager)
|
||||
{
|
||||
if (recipeManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(recipeManager));
|
||||
}
|
||||
|
||||
if (recipeManager.CurrentSubstrateRecipe == null)
|
||||
{
|
||||
throw new LocalizedProcessException(
|
||||
MessageKey.ProcessSubstratePositionRecipeNotLoaded,
|
||||
Array.Empty<string>());
|
||||
}
|
||||
|
||||
return recipeManager.CurrentSubstrateRecipe;
|
||||
}
|
||||
|
||||
private static List<MarkData> GetEnabledMarks(SubstrateRecipe substrateRecipe)
|
||||
{
|
||||
if (substrateRecipe?.SubtrateMarkParameterInfo?.MarkDatas == null)
|
||||
{
|
||||
throw new LocalizedProcessException(
|
||||
MessageKey.ProcessSubstratePositionMarkParameterMissing,
|
||||
Array.Empty<string>());
|
||||
}
|
||||
|
||||
List<MarkData> enabledMarks = substrateRecipe.SubtrateMarkParameterInfo.MarkDatas
|
||||
.Where(mark => mark != null && mark.IsEnabled)
|
||||
.ToList();
|
||||
|
||||
if (enabledMarks.Count == 0)
|
||||
{
|
||||
throw new LocalizedProcessException(
|
||||
MessageKey.ProcessSubstratePositionNoEnabledMarks,
|
||||
Array.Empty<string>());
|
||||
}
|
||||
|
||||
return enabledMarks;
|
||||
}
|
||||
|
||||
private static SubstratePositionProcessResult InitializeProcessResult(ProcessResultManager processResultManager, int availableMarkCount)
|
||||
{
|
||||
if (processResultManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(processResultManager));
|
||||
}
|
||||
|
||||
SubstratePositionProcessResult processResult = processResultManager.SubstratePositionResult;
|
||||
processResult.IsSuccess = false;
|
||||
processResult.AvailableMarkCount = availableMarkCount;
|
||||
processResult.RecognizedMarkCount = 0;
|
||||
processResult.ErrorMessage = null;
|
||||
processResult.ErrorMessageKey = MessageKey.None;
|
||||
processResult.ErrorMessageArguments = Array.Empty<string>();
|
||||
processResult.SubstrateAngle = 0;
|
||||
processResult.MarkResults.Clear();
|
||||
return processResult;
|
||||
}
|
||||
|
||||
private static double CalculateSubstrateAngle(List<Point> markResults)
|
||||
{
|
||||
if (markResults == null || markResults.Count < 2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
double centroidX = markResults.Average(point => point.X);
|
||||
double centroidY = markResults.Average(point => point.Y);
|
||||
|
||||
double sumXX = 0;
|
||||
double sumYY = 0;
|
||||
double sumXY = 0;
|
||||
|
||||
foreach (Point point in markResults)
|
||||
{
|
||||
double dx = point.X - centroidX;
|
||||
double dy = point.Y - centroidY;
|
||||
sumXX += dx * dx;
|
||||
sumYY += dy * dy;
|
||||
sumXY += dx * dy;
|
||||
}
|
||||
|
||||
if (Math.Abs(sumXX) < 1e-9 && Math.Abs(sumYY) < 1e-9 && Math.Abs(sumXY) < 1e-9)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
double angleRadians = 0.5 * Math.Atan2(2 * sumXY, sumXX - sumYY);
|
||||
return angleRadians * 180.0 / Math.PI;
|
||||
}
|
||||
|
||||
private static void WriteResultToContext(
|
||||
WorkflowContext context,
|
||||
ProcessResultManager processResultManager,
|
||||
SubstratePositionProcessResult processResult)
|
||||
{
|
||||
context.SetData(WorkflowContextKeys.SubstratePositionResult, processResult);
|
||||
processResultManager.SaveSubstratePositionResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using MainShell.Common;
|
||||
using MW.WorkFlow;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class SubstrateUnloadActivity : ActivityAbstractBase
|
||||
{
|
||||
public SubstrateUnloadActivity(string name)
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
|
||||
protected override async Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
context.SetData(WorkflowContextKeys.SubstrateProcessState, SubstrateLifecycleState.Unloading);
|
||||
|
||||
for (int index = 0; index < 10; index++)
|
||||
{
|
||||
activityControl.ThrowIfCancellationRequested();
|
||||
await activityControl.CheckPauseAsync().ConfigureAwait(false);
|
||||
await Task.Delay(100, activityControl.CancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
context.SetData(WorkflowContextKeys.SubstrateProcessState, SubstrateLifecycleState.NotLoaded);
|
||||
return ActivityResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.ProcessResult;
|
||||
using MW.WorkFlow;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public abstract class ActivityAbstractBase : IActivity
|
||||
{
|
||||
public string Name { get; protected set; }
|
||||
|
||||
protected ProcessResultManager ResultManager { get; private set; }
|
||||
|
||||
protected string CurrentWorkflowName { get; private set; } = "UnknownFlow";
|
||||
|
||||
protected string ParentActivityName { get; private set; } = string.Empty;
|
||||
|
||||
protected ActivityAbstractBase(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentException("Activity name cannot be null or whitespace.", nameof(name));
|
||||
}
|
||||
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public async Task<ActivityResult> ExecuteAsync(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (activityControl == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(activityControl));
|
||||
}
|
||||
|
||||
var result = ActivityResult.Failure;
|
||||
var hasFaulted = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (context.TryGetData<string>(WorkflowContextKeys.WorkflowName, out var flowName))
|
||||
{
|
||||
CurrentWorkflowName = flowName;
|
||||
}
|
||||
|
||||
if (context.TryGetData<string>(WorkflowContextKeys.ParentActivityName, out var parentName))
|
||||
{
|
||||
ParentActivityName = parentName;
|
||||
}
|
||||
else
|
||||
{
|
||||
ParentActivityName = string.Empty;
|
||||
}
|
||||
|
||||
if (context.TryGetData<ProcessResultManager>(WorkflowContextKeys.ProcessResultManager, out var manager))
|
||||
{
|
||||
ResultManager = manager;
|
||||
ResultManager.UpdateFlowState(CurrentWorkflowName, ParentActivityName, Name, ProcessExecutionStatus.Running, null);
|
||||
}
|
||||
|
||||
PrepareExecute(context, activityControl);
|
||||
result = await OnExecuteAsync(context, activityControl);
|
||||
return result;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
ResultManager?.UpdateFlowState(CurrentWorkflowName, ParentActivityName, Name, ProcessExecutionStatus.Canceled, null);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!HasWorkflowFailure(context))
|
||||
{
|
||||
SetWorkflowFailure(
|
||||
context,
|
||||
MessageKey.ProcessStepFailedWithReason,
|
||||
new object[] { Name, ex.Message ?? string.Empty });
|
||||
}
|
||||
|
||||
var errorMsg = ResolveWorkflowFailureMessage(context);
|
||||
ResultManager?.UpdateFlowState(CurrentWorkflowName, ParentActivityName, Name, ProcessExecutionStatus.Faulted, errorMsg);
|
||||
hasFaulted = true;
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
AfterExecute(context, activityControl);
|
||||
|
||||
if (ResultManager != null && !hasFaulted)
|
||||
{
|
||||
if (result == ActivityResult.Success)
|
||||
{
|
||||
ResultManager.UpdateFlowState(CurrentWorkflowName, ParentActivityName, Name, ProcessExecutionStatus.Completed, null);
|
||||
}
|
||||
else if (result == ActivityResult.Canceled)
|
||||
{
|
||||
ResultManager.UpdateFlowState(CurrentWorkflowName, ParentActivityName, Name, ProcessExecutionStatus.Canceled, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResultManager.UpdateFlowState(
|
||||
CurrentWorkflowName,
|
||||
ParentActivityName,
|
||||
Name,
|
||||
ProcessExecutionStatus.Faulted,
|
||||
ResolveWorkflowFailureMessage(context));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected ActivityResult Fail(WorkflowContext context, MessageKey failureMessageKey, object[] failureMessageArguments)
|
||||
{
|
||||
SetWorkflowFailure(context, failureMessageKey, failureMessageArguments);
|
||||
return ActivityResult.Failure;
|
||||
}
|
||||
|
||||
protected void SetWorkflowFailure(WorkflowContext context, MessageKey failureMessageKey, object[] failureMessageArguments)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var normalized = NormalizeFailureMessageArguments(failureMessageArguments);
|
||||
context.SetData(WorkflowContextKeys.WorkflowFailureMessageKey, failureMessageKey);
|
||||
context.SetData(WorkflowContextKeys.WorkflowFailureMessageArguments, normalized);
|
||||
context.SetData(WorkflowContextKeys.WorkflowFailureMessage, LanguageResourceHelper.Format(failureMessageKey, normalized));
|
||||
}
|
||||
|
||||
protected void SetWorkflowFailure(WorkflowContext context, string failureMessage)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
context.SetData(WorkflowContextKeys.WorkflowFailureMessageKey, MessageKey.None);
|
||||
context.SetData<object[]>(WorkflowContextKeys.WorkflowFailureMessageArguments, null);
|
||||
context.SetData(WorkflowContextKeys.WorkflowFailureMessage, failureMessage ?? string.Empty);
|
||||
}
|
||||
|
||||
protected bool HasWorkflowFailure(WorkflowContext context)
|
||||
{
|
||||
if (context.TryGetData<MessageKey>(WorkflowContextKeys.WorkflowFailureMessageKey, out var failureMessageKey) &&
|
||||
failureMessageKey > MessageKey.None)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return context.TryGetData<string>(WorkflowContextKeys.WorkflowFailureMessage, out var failureMessage) &&
|
||||
!string.IsNullOrWhiteSpace(failureMessage);
|
||||
}
|
||||
|
||||
protected string ResolveWorkflowFailureMessage(WorkflowContext context)
|
||||
{
|
||||
if (context.TryGetData<MessageKey>(WorkflowContextKeys.WorkflowFailureMessageKey, out var failureMessageKey) &&
|
||||
failureMessageKey > MessageKey.None)
|
||||
{
|
||||
return LanguageResourceHelper.Format(failureMessageKey, GetWorkflowFailureMessageArguments(context));
|
||||
}
|
||||
|
||||
if (context.TryGetData<string>(WorkflowContextKeys.WorkflowFailureMessage, out var failureMessage) &&
|
||||
!string.IsNullOrWhiteSpace(failureMessage))
|
||||
{
|
||||
return failureMessage;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
protected object[] GetWorkflowFailureMessageArguments(WorkflowContext context)
|
||||
{
|
||||
if (context.TryGetData<object[]>(WorkflowContextKeys.WorkflowFailureMessageArguments, out var failureMessageArguments) &&
|
||||
failureMessageArguments != null)
|
||||
{
|
||||
return failureMessageArguments;
|
||||
}
|
||||
|
||||
return Array.Empty<object>();
|
||||
}
|
||||
|
||||
protected object[] NormalizeFailureMessageArguments(object[] failureMessageArguments)
|
||||
{
|
||||
return failureMessageArguments ?? Array.Empty<object>();
|
||||
}
|
||||
|
||||
protected abstract Task<ActivityResult> OnExecuteAsync(WorkflowContext context, ActivityControl activityControl);
|
||||
|
||||
protected virtual void PrepareExecute(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void AfterExecute(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using MainShell.Common;
|
||||
using MW.WorkFlow;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class CompositeActivity : IActivity
|
||||
{
|
||||
private readonly IEnumerable<IActivity> _subSteps;
|
||||
private readonly IEnumerable<IActivity> _finallySteps;
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public CompositeActivity(string name, IEnumerable<IActivity> subSteps)
|
||||
: this(name, subSteps, Array.Empty<IActivity>())
|
||||
{
|
||||
}
|
||||
|
||||
public CompositeActivity(string name, IEnumerable<IActivity> subSteps, IEnumerable<IActivity> finallySteps)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentException("Composite activity name cannot be null or whitespace.", nameof(name));
|
||||
}
|
||||
|
||||
Name = name;
|
||||
_subSteps = subSteps ?? throw new ArgumentNullException(nameof(subSteps));
|
||||
_finallySteps = finallySteps ?? Array.Empty<IActivity>();
|
||||
}
|
||||
|
||||
public async Task<ActivityResult> ExecuteAsync(WorkflowContext context, ActivityControl activityControl)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (activityControl == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(activityControl));
|
||||
}
|
||||
|
||||
activityControl.ThrowIfCancellationRequested();
|
||||
|
||||
context.TryGetData<string>(WorkflowContextKeys.ParentActivityName, out var previousParent);
|
||||
context.SetData(WorkflowContextKeys.ParentActivityName, Name);
|
||||
|
||||
ActivityResult result = ActivityResult.Success;
|
||||
Exception mainException = null;
|
||||
Exception cleanupException = null;
|
||||
|
||||
try
|
||||
{
|
||||
foreach (IActivity step in _subSteps)
|
||||
{
|
||||
activityControl.ThrowIfCancellationRequested();
|
||||
|
||||
result = await step.ExecuteAsync(context, activityControl);
|
||||
if (result != ActivityResult.Success)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
mainException = ex;
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (IActivity cleanupStep in _finallySteps)
|
||||
{
|
||||
try
|
||||
{
|
||||
ActivityResult cleanupResult = await cleanupStep.ExecuteAsync(context, activityControl);
|
||||
if (mainException == null && result == ActivityResult.Success && cleanupResult != ActivityResult.Success)
|
||||
{
|
||||
result = cleanupResult;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (mainException == null && result == ActivityResult.Success)
|
||||
{
|
||||
cleanupException = ex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.SetData(WorkflowContextKeys.ParentActivityName, previousParent ?? string.Empty);
|
||||
}
|
||||
|
||||
if (mainException != null)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(mainException).Throw();
|
||||
}
|
||||
|
||||
if (cleanupException != null)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(cleanupException).Throw();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class ProcessFlowName
|
||||
{
|
||||
public const string AutoProduction = "AutoProduction";
|
||||
/// <summary>
|
||||
/// 基板上料
|
||||
/// </summary>
|
||||
public const string SubstrateLoadFlow = "SubstrateLoadFlow";
|
||||
/// <summary>
|
||||
/// 基板定位
|
||||
/// </summary>
|
||||
public const string SubstratePositionFlow = "SubstratePositionFlow";
|
||||
/// <summary>
|
||||
/// 芯片拉直
|
||||
/// </summary>
|
||||
public const string ChipStraighteningFlow = "WaferAngleAdjustmentFlow";
|
||||
/// <summary>
|
||||
/// 芯片定位
|
||||
/// </summary>
|
||||
public const string DiePositionFlow = "DiePositionFlow";
|
||||
/// <summary>
|
||||
/// 芯片转移
|
||||
/// </summary>
|
||||
public const string DieTransferFlow = "DieTransferFlow";
|
||||
/// <summary>
|
||||
/// 基板测高
|
||||
/// </summary>
|
||||
public const string SubstrateHeightMeasureFlow = "SubstrateHeightMeasureFlow";
|
||||
/// <summary>
|
||||
/// 精度复检
|
||||
/// </summary>
|
||||
public const string DieRecheckFlow = "DieRecheckFlow";
|
||||
/// <summary>
|
||||
/// 基板下料
|
||||
/// </summary>
|
||||
public const string SubstrateUnloadFlow = "SubstrateUnloadFlow";
|
||||
|
||||
public const string PreparationSignalFlow = "PreparationSignalFlow";
|
||||
public const string ChipPreparationAutoLoadStartFlow = "ChipPreparationAutoLoadStartFlow";
|
||||
public const string ChipPreparationSyncFlow = "ChipPreparationSyncFlow";
|
||||
public const string PreTransferValidationFlow = "PreTransferValidationFlow";
|
||||
public const string ChipUnloadFlow = "ChipUnloadFlow";
|
||||
|
||||
public const string ChipPreparationOnDemandFlow = "ChipPreparationOnDemand";
|
||||
public const string ChipLoadExecuteActivity = "ChipLoadExecute";
|
||||
public const string ChipUnloadExecuteActivity = "ChipUnloadExecute";
|
||||
}
|
||||
|
||||
public enum DieTransferRoute
|
||||
{
|
||||
None = 0,
|
||||
Recheck = 1
|
||||
}
|
||||
|
||||
public enum AutoProductionRoute
|
||||
{
|
||||
None = 0,
|
||||
ContinueCurrentSubstrate = 1,
|
||||
Recheck = 2,
|
||||
ChipExhausted = 3,
|
||||
SubstrateComplete = 4,
|
||||
BothExhausted = 5
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.ProcessResult;
|
||||
using MW.WorkFlow;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class WorkflowRunCompletedEventArgs : EventArgs
|
||||
{
|
||||
public WorkflowState FinalState { get; }
|
||||
public string Error { get; }
|
||||
public string FailureMessage { get; }
|
||||
public MessageKey FailureMessageKey { get; }
|
||||
public object[] FailureMessageArguments { get; }
|
||||
|
||||
public WorkflowRunCompletedEventArgs(WorkflowState finalState, string error, string failureMessage, MessageKey failureMessageKey, object[] failureMessageArguments)
|
||||
{
|
||||
FinalState = finalState;
|
||||
Error = error;
|
||||
FailureMessage = failureMessage;
|
||||
FailureMessageKey = failureMessageKey;
|
||||
FailureMessageArguments = failureMessageArguments ?? Array.Empty<object>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 封装工作流的启动、停止和状态管理,用于手动流程的复用。
|
||||
/// </summary>
|
||||
public class
|
||||
WorkflowRunner : PropertyChangedBase, IDisposable
|
||||
{
|
||||
private WorkflowEngine _workflowEngine;
|
||||
private bool _isRunning;
|
||||
|
||||
/// <summary>
|
||||
/// 流程是否正在运行
|
||||
/// </summary>
|
||||
public bool IsRunning
|
||||
{
|
||||
get { return _isRunning; }
|
||||
private set { SetAndNotify(ref _isRunning, value); }
|
||||
}
|
||||
|
||||
public event EventHandler<WorkflowRunCompletedEventArgs> RunCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// 运行单个 Activity(自动包装为单步工作流)
|
||||
/// </summary>
|
||||
public Task RunActivityAsync(IActivity activity, WorkflowContext context)
|
||||
{
|
||||
if (activity == null) throw new ArgumentNullException(nameof(activity));
|
||||
|
||||
// 自动构建单步工作流定义
|
||||
var stepId = "SingleStep_" + Guid.NewGuid().ToString("N");
|
||||
var step = new WorkflowStep(stepId, activity);
|
||||
var definition = new WorkflowDefinition(stepId);
|
||||
definition.AddStep(step);
|
||||
|
||||
return RunAsync(definition, context);
|
||||
}
|
||||
|
||||
public Task<WorkflowRunCompletedEventArgs> RunActivityWithResultAsync(IActivity activity, WorkflowContext context)
|
||||
{
|
||||
if (activity == null) throw new ArgumentNullException(nameof(activity));
|
||||
|
||||
var stepId = "SingleStep_" + Guid.NewGuid().ToString("N");
|
||||
var step = new WorkflowStep(stepId, activity);
|
||||
var definition = new WorkflowDefinition(stepId);
|
||||
definition.AddStep(step);
|
||||
|
||||
return RunWithResultAsync(definition, context, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行完整的工作流定义
|
||||
/// </summary>
|
||||
public async Task RunAsync(WorkflowDefinition definition, WorkflowContext context)
|
||||
{
|
||||
await RunWithResultAsync(definition, context, null);
|
||||
}
|
||||
|
||||
public Task<WorkflowRunCompletedEventArgs> RunWithResultAsync(WorkflowDefinition definition, WorkflowContext context)
|
||||
{
|
||||
return RunWithResultAsync(definition, context, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行完整的工作流定义,并支持从指定步骤启动。
|
||||
/// </summary>
|
||||
/// <param name="definition">工作流定义</param>
|
||||
/// <param name="context">工作流上下文</param>
|
||||
/// <param name="startStepId">可选:指定起始步骤ID;为 null/空时从初始步骤启动</param>
|
||||
public async Task RunAsync(WorkflowDefinition definition, WorkflowContext context, string startStepId)
|
||||
{
|
||||
await RunWithResultAsync(definition, context, startStepId);
|
||||
}
|
||||
|
||||
public async Task<WorkflowRunCompletedEventArgs> RunWithResultAsync(WorkflowDefinition definition, WorkflowContext context, string startStepId)
|
||||
{
|
||||
if (definition == null) throw new ArgumentNullException(nameof(definition));
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
|
||||
if (IsRunning)
|
||||
{
|
||||
throw new InvalidOperationException("当前已有流程正在运行,无法重复启动。");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(startStepId) && !definition.GetAllStepIds().Contains(startStepId))
|
||||
{
|
||||
throw new ArgumentException($"指定的起始步骤ID '{startStepId}' 不存在于工作流定义中。", nameof(startStepId));
|
||||
}
|
||||
|
||||
var resolvedStartStepId = ResolveStartStepId(definition, context, startStepId);
|
||||
|
||||
var errorStr = string.Empty;
|
||||
WorkflowState finalState = WorkflowState.Created;
|
||||
MessageKey failureMessageKey = MessageKey.None;
|
||||
object[] failureMessageArguments = Array.Empty<object>();
|
||||
string failureMessage = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
IsRunning = true;
|
||||
_workflowEngine = new WorkflowEngine(definition, context);
|
||||
_workflowEngine.Start(resolvedStartStepId);
|
||||
|
||||
// 异步轮询等待工作流结束
|
||||
// 这样可以不阻塞 UI 线程,同时保持 IsRunning 状态正确
|
||||
while (_workflowEngine != null && _workflowEngine.IsWorkflowRunning)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
// 捕获最终状态(Dispose 之前)
|
||||
if (_workflowEngine != null)
|
||||
{
|
||||
finalState = _workflowEngine.CurrentState;
|
||||
}
|
||||
|
||||
failureMessageKey = ResolveFailureMessageKey(context);
|
||||
failureMessageArguments = ResolveFailureMessageArguments(context);
|
||||
failureMessage = ResolveFailureMessage(context, failureMessageKey, failureMessageArguments);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 无论正常结束、异常还是被取消,都确保清理资源和复位状态
|
||||
errorStr = _workflowEngine?.LastException?.ToString();
|
||||
IsRunning = false;
|
||||
_workflowEngine?.Dispose();
|
||||
_workflowEngine = null;
|
||||
}
|
||||
|
||||
var completedArgs = new WorkflowRunCompletedEventArgs(finalState, errorStr, failureMessage, failureMessageKey, failureMessageArguments);
|
||||
RunCompleted?.Invoke(this, completedArgs);
|
||||
|
||||
return completedArgs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定步骤启动工作流(语义化封装)。
|
||||
/// </summary>
|
||||
public Task RunFromStepAsync(WorkflowDefinition definition, WorkflowContext context, string startStepId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(startStepId))
|
||||
throw new ArgumentNullException(nameof(startStepId));
|
||||
|
||||
return RunAsync(definition, context, startStepId);
|
||||
}
|
||||
|
||||
public Task<WorkflowRunCompletedEventArgs> RunFromStepWithResultAsync(WorkflowDefinition definition, WorkflowContext context, string startStepId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(startStepId))
|
||||
throw new ArgumentNullException(nameof(startStepId));
|
||||
|
||||
return RunWithResultAsync(definition, context, startStepId);
|
||||
}
|
||||
|
||||
private string ResolveStartStepId(WorkflowDefinition definition, WorkflowContext context, string startStepId)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(startStepId))
|
||||
{
|
||||
return startStepId;
|
||||
}
|
||||
|
||||
if (!context.TryGetData<string>(WorkflowContextKeys.WorkflowName, out var workflowName) ||
|
||||
string.IsNullOrWhiteSpace(workflowName) ||
|
||||
!context.TryGetData<ProcessResultManager>(WorkflowContextKeys.ProcessResultManager, out var resultManager) ||
|
||||
resultManager == null ||
|
||||
!resultManager.TryGetResumeStepId(workflowName, out var resumeStepId) ||
|
||||
!definition.ContainsStep(resumeStepId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return resumeStepId;
|
||||
}
|
||||
|
||||
private MessageKey ResolveFailureMessageKey(WorkflowContext context)
|
||||
{
|
||||
MessageKey failureMessageKey;
|
||||
if (context.TryGetData<MessageKey>(WorkflowContextKeys.WorkflowFailureMessageKey, out failureMessageKey))
|
||||
{
|
||||
return failureMessageKey;
|
||||
}
|
||||
|
||||
return MessageKey.None;
|
||||
}
|
||||
|
||||
private object[] ResolveFailureMessageArguments(WorkflowContext context)
|
||||
{
|
||||
object[] failureMessageArguments;
|
||||
if (context.TryGetData<object[]>(WorkflowContextKeys.WorkflowFailureMessageArguments, out failureMessageArguments) &&
|
||||
failureMessageArguments != null)
|
||||
{
|
||||
return failureMessageArguments;
|
||||
}
|
||||
|
||||
return Array.Empty<object>();
|
||||
}
|
||||
|
||||
private string ResolveFailureMessage(WorkflowContext context, MessageKey failureMessageKey, object[] failureMessageArguments)
|
||||
{
|
||||
if (failureMessageKey != MessageKey.None)
|
||||
{
|
||||
return LanguageResourceHelper.Format(failureMessageKey, failureMessageArguments);
|
||||
}
|
||||
|
||||
string failureMessage;
|
||||
if (context.TryGetData<string>(WorkflowContextKeys.WorkflowFailureMessage, out failureMessage) &&
|
||||
!string.IsNullOrWhiteSpace(failureMessage))
|
||||
{
|
||||
return failureMessage;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求停止当前流程
|
||||
/// </summary>
|
||||
public async Task StopAsync()
|
||||
{
|
||||
if (_workflowEngine != null && _workflowEngine.IsWorkflowRunning)
|
||||
{
|
||||
await _workflowEngine.StopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
if (_workflowEngine != null && _workflowEngine.IsWorkflowRunning)
|
||||
{
|
||||
_workflowEngine.Pause();
|
||||
}
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
if (_workflowEngine != null && _workflowEngine.IsWorkflowRunning)
|
||||
{
|
||||
_workflowEngine.Resume();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_workflowEngine?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using MainShell.Log;
|
||||
using MainShell.ProcessResult;
|
||||
using System;
|
||||
using MW.WorkFlow;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class WorkflowRuntimeTracker : IWorkflowRuntimeTracker
|
||||
{
|
||||
private readonly ProcessResultManager _processResultManager;
|
||||
|
||||
public WorkflowRuntimeTracker(ProcessResultManager processResultManager)
|
||||
{
|
||||
_processResultManager = processResultManager ?? throw new ArgumentNullException(nameof(processResultManager));
|
||||
}
|
||||
|
||||
public void UpdateExecutionPointer(WorkflowExecutionPointer executionPointer)
|
||||
{
|
||||
if (executionPointer != null)
|
||||
{
|
||||
string.Format(
|
||||
"Workflow pointer updated. WorkflowName={0}, FlowName={1}, ActivityName={2}, CurrentStepId={3}, NextStepId={4}, UpdatedAt={5:yyyy-MM-dd HH:mm:ss.fff}.",
|
||||
NormalizeValue(executionPointer.WorkflowName),
|
||||
NormalizeValue(executionPointer.FlowName),
|
||||
NormalizeValue(executionPointer.ActivityName),
|
||||
NormalizeValue(executionPointer.CurrentStepId),
|
||||
NormalizeValue(executionPointer.NextStepId),
|
||||
executionPointer.UpdatedAt).LogProcessDebug();
|
||||
}
|
||||
|
||||
_processResultManager.UpdateExecutionPointer(executionPointer);
|
||||
}
|
||||
|
||||
public void ReportWorkflowFault(WorkflowFaultInfo faultInfo)
|
||||
{
|
||||
if (faultInfo != null)
|
||||
{
|
||||
string.Format(
|
||||
"Workflow fault reported. WorkflowName={0}, FlowName={1}, ActivityName={2}, CurrentStepId={3}, OccurredAt={4:yyyy-MM-dd HH:mm:ss.fff}, ErrorMessage={5}.",
|
||||
NormalizeValue(faultInfo.WorkflowName),
|
||||
NormalizeValue(faultInfo.FlowName),
|
||||
NormalizeValue(faultInfo.ActivityName),
|
||||
NormalizeValue(faultInfo.CurrentStepId),
|
||||
faultInfo.OccurredAt,
|
||||
NormalizeValue(faultInfo.ErrorMessage)).LogProcessError();
|
||||
}
|
||||
|
||||
_processResultManager.ReportWorkflowFault(faultInfo);
|
||||
}
|
||||
|
||||
private static string NormalizeValue(string value)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value) ? "N/A" : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using MW.WorkFlow;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public class WorkflowStartRequest
|
||||
{
|
||||
public WorkflowDefinition Definition { get; }
|
||||
public WorkflowContext Context { get; }
|
||||
public string StartStepId { get; }
|
||||
|
||||
public WorkflowStartRequest(WorkflowDefinition definition, WorkflowContext context, string startStepId)
|
||||
{
|
||||
Definition = definition ?? throw new ArgumentNullException(nameof(definition));
|
||||
Context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
StartStepId = startStepId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public static class WorkflowStepIdResolver
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<string, string> AutoProductionEntryStepIdMap =
|
||||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ ProcessFlowName.SubstrateLoadFlow, WorkflowStepIds.AutoProduction.SubstrateLoadEntry },
|
||||
{ ProcessFlowName.SubstratePositionFlow, WorkflowStepIds.AutoProduction.SubstratePositionEntry },
|
||||
{ ProcessFlowName.SubstrateHeightMeasureFlow, WorkflowStepIds.AutoProduction.SubstrateHeightMeasureEntry },
|
||||
{ ProcessFlowName.PreparationSignalFlow, WorkflowStepIds.AutoProduction.PreparationSignalEntry },
|
||||
{ ProcessFlowName.ChipPreparationSyncFlow, WorkflowStepIds.AutoProduction.ChipPreparationSyncEntry },
|
||||
{ ProcessFlowName.PreTransferValidationFlow, WorkflowStepIds.AutoProduction.PreTransferValidationEntry },
|
||||
{ ProcessFlowName.ChipStraighteningFlow, WorkflowStepIds.AutoProduction.ChipStraighteningEntry },
|
||||
{ ProcessFlowName.DiePositionFlow, WorkflowStepIds.AutoProduction.DiePositionEntry },
|
||||
{ ProcessFlowName.DieTransferFlow, WorkflowStepIds.AutoProduction.DieTransferEntry },
|
||||
{ ProcessFlowName.DieRecheckFlow, WorkflowStepIds.AutoProduction.DieRecheckEntry },
|
||||
{ ProcessFlowName.ChipUnloadFlow, WorkflowStepIds.AutoProduction.ChipUnloadEntry },
|
||||
{ ProcessFlowName.SubstrateUnloadFlow, WorkflowStepIds.AutoProduction.SubstrateUnloadEntry }
|
||||
};
|
||||
|
||||
public static string GetAutoProductionEntryStepId(string flowName)
|
||||
{
|
||||
if (TryGetAutoProductionEntryStepId(flowName, out var stepId))
|
||||
{
|
||||
return stepId;
|
||||
}
|
||||
|
||||
return WorkflowStepIds.AutoProduction.SubstrateLoadEntry;
|
||||
}
|
||||
|
||||
public static bool TryGetAutoProductionEntryStepId(string flowName, out string stepId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(flowName))
|
||||
{
|
||||
stepId = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return AutoProductionEntryStepIdMap.TryGetValue(flowName, out stepId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace MainShell.Process
|
||||
{
|
||||
public static class WorkflowStepIds
|
||||
{
|
||||
public static class AutoProduction
|
||||
{
|
||||
public const string SubstrateLoadEntry = "Auto.SubstrateLoad.Entry";
|
||||
public const string SubstratePositionEntry = "Auto.SubstratePosition.Entry";
|
||||
public const string SubstrateHeightMeasureEntry = "Auto.SubstrateHeightMeasure.Entry";
|
||||
public const string PreparationSignalEntry = "Auto.PreparationSignal.Entry";
|
||||
public const string ChipPreparationSyncEntry = "Auto.ChipPreparationSync.Entry";
|
||||
public const string PreTransferValidationEntry = "Auto.PreTransferValidation.Entry";
|
||||
public const string ChipStraighteningEntry = "Auto.ChipStraightening.Entry";
|
||||
public const string DiePositionEntry = "Auto.DiePosition.Entry";
|
||||
public const string DieTransferEntry = "Auto.DieTransfer.Entry";
|
||||
public const string DieRecheckEntry = "Auto.DieRecheck.Entry";
|
||||
public const string ChipUnloadEntry = "Auto.ChipUnload.Entry";
|
||||
public const string SubstrateUnloadEntry = "Auto.SubstrateUnload.Entry";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user