添加 MX-PD-盘古 项目文件
将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.EventArgsFolder;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Manual.Model;
|
||||
using MainShell.Recipe.Models;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Manual.ViewModel
|
||||
{
|
||||
public class ChipStraighteningViewModel : OperateViewModelBase,IHandle<ChipAngleEventArgs>
|
||||
{
|
||||
private double _chipAngle;
|
||||
public double ChipAngle
|
||||
{
|
||||
get { return _chipAngle; }
|
||||
set { SetAndNotify(ref _chipAngle, value); }
|
||||
}
|
||||
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly RecipeManager _recipeManager;
|
||||
public ChipStraighteningViewModel(HardwareManager hardwareManager, IEventAggregator eventAggregator, RecipeManager recipeManager)
|
||||
{
|
||||
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
|
||||
_recipeManager = recipeManager ?? throw new ArgumentNullException(nameof(recipeManager));
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
eventAggregator.Subscribe(this);
|
||||
_cameraAxisViewModel = IoC.Get<Common.Display.ViewModel.CameraAxisViewModel>();
|
||||
_cameraAxisViewModel.CameraAxisDevices.HardwareDeviceList = _hardwareManager.CameraAxisManager.TopCameraAxisDevices;
|
||||
}
|
||||
|
||||
// 手动启动流程
|
||||
public async Task StartProcess()
|
||||
{
|
||||
var context = new MW.WorkFlow.WorkflowContext();
|
||||
context[WorkflowContextKeys.EventAggregator] = _eventAggregator;
|
||||
context[WorkflowContextKeys.RecipeManager] = _recipeManager;
|
||||
|
||||
await RunManualActivityAsync(new MainShell.Process.ChipStraighteningActivity("ChipStraightening"), context);
|
||||
}
|
||||
|
||||
public override async Task StopProcess()
|
||||
{
|
||||
await base.StopProcess();
|
||||
}
|
||||
|
||||
|
||||
public void Handle(ChipAngleEventArgs message)
|
||||
{
|
||||
Stylet.Execute.OnUIThread(() =>
|
||||
{
|
||||
ChipAngle = message.Angle;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Manual.Model;
|
||||
using MainShell.Models;
|
||||
using MainShell.Models.Wafer;
|
||||
using MainShell.ParaSetting.Model;
|
||||
using MainShell.Process;
|
||||
using MainShell.ProcessResult;
|
||||
using MainShell.Recipe.BaseBoard.Model;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Resources.CustomControl;
|
||||
using MaxwellFramework.Core.Interfaces;
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Manual.ViewModel
|
||||
{
|
||||
public class DieBondingViewModel : OperateViewModelBase
|
||||
{
|
||||
private readonly RecipeManager _recipeManager;
|
||||
private readonly IEventAggregator _eventAggregator; // 引用 EventAggregator
|
||||
private readonly ProcessResultManager _processResultManager;
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly IDieTransferPathGenerator _dieTransferPathGenerator;
|
||||
|
||||
|
||||
private List<DieTransferPathItem> _allDieTransferPathItems = new List<DieTransferPathItem>();
|
||||
|
||||
private ObservableCollection<DieTransferPathItem> _dieTransferPathItems = new ObservableCollection<DieTransferPathItem>();
|
||||
/// <summary>
|
||||
/// 界面上显示的传输路径集合
|
||||
/// </summary>
|
||||
public ObservableCollection<DieTransferPathItem> DieTransferPathItems
|
||||
{
|
||||
get { return _dieTransferPathItems; }
|
||||
set { SetAndNotify(ref _dieTransferPathItems, value); }
|
||||
}
|
||||
|
||||
private DieTransferPathItem _selectedDieTransferPathItem;
|
||||
public DieTransferPathItem SelectedDieTransferPathItem
|
||||
{
|
||||
get => _selectedDieTransferPathItem;
|
||||
set => SetAndNotify(ref _selectedDieTransferPathItem, value);
|
||||
}
|
||||
|
||||
private readonly DieBondingManualSetting _dieBondingManualSetting;
|
||||
public DieBondingManualSysItem DieBondingManualSysItem => _dieBondingManualSetting.DieBondingManualSysItem;
|
||||
public PaginationViewModel Pagination { get; } = new PaginationViewModel();
|
||||
public DieBondingViewModel(IEventAggregator eventAggregator, RecipeManager recipeManager,
|
||||
ProcessResultManager processResultManager, HardwareManager hardwareManager, IDieTransferPathGenerator dieTransferPathGenerator, IParameterManager parameterManager)
|
||||
{
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_recipeManager = recipeManager ?? throw new ArgumentNullException(nameof(recipeManager));
|
||||
_processResultManager = processResultManager ?? throw new ArgumentNullException(nameof(processResultManager));
|
||||
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
|
||||
_dieTransferPathGenerator = dieTransferPathGenerator ?? throw new ArgumentNullException(nameof(dieTransferPathGenerator));
|
||||
|
||||
_cameraAxisViewModel = IoC.Get<Common.Display.ViewModel.CameraAxisViewModel>();
|
||||
_cameraAxisViewModel.CameraAxisDevices.HardwareDeviceList = hardwareManager.CameraAxisManager.TopCameraAxisDevices;
|
||||
|
||||
var _paramlist = parameterManager as IParamList;
|
||||
|
||||
_dieBondingManualSetting = _paramlist.GetParameter<DieBondingManualSetting>();
|
||||
|
||||
Pagination.PageChanged += (s, e) => LoadData();
|
||||
|
||||
LoadData();
|
||||
}
|
||||
|
||||
private void LoadData()
|
||||
{
|
||||
// 如果数据源为空,清空并返回
|
||||
if (_allDieTransferPathItems == null || !_allDieTransferPathItems.Any())
|
||||
{
|
||||
DieTransferPathItems = new ObservableCollection<DieTransferPathItem>();
|
||||
Pagination.TotalItems = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 先更新总数(这很重要,因为TotalPages依赖它)
|
||||
Pagination.TotalItems = _allDieTransferPathItems.Count();
|
||||
|
||||
// 2. 自动修正页码:如果当前页超过了新的总页数,强制跳到最后一页
|
||||
// 注意:修改 Pagination.CurrentPage 会再次触发 PageChanged -> LoadData,
|
||||
// 所以我们需要防止无限递归,或者只在必要时修正。
|
||||
// 在这里,简单的方式是计算 offset 时进行钳制,而不强行修改 CurrentPage 属性(取决于你的需求)
|
||||
// 或者更好的是:
|
||||
if (Pagination.CurrentPage > Pagination.TotalItems && Pagination.TotalItems > 0)
|
||||
{
|
||||
// 如果这里修改 CurrentPage 触发 LoadData,则本次 LoadData 可以直接中断
|
||||
Pagination.CurrentPage = Pagination.TotalPages;
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 安全计算 Offset
|
||||
int safePage = Math.Min(Pagination.CurrentPage, Pagination.TotalPages);
|
||||
safePage = Math.Max(1, safePage); // 确保至少为1
|
||||
|
||||
int offset = (safePage - 1) * Pagination.PageSize;
|
||||
int limit = Pagination.PageSize;
|
||||
|
||||
// 4. 重置集合
|
||||
DieTransferPathItems = new ObservableCollection<DieTransferPathItem>(_allDieTransferPathItems.Skip(offset).Take(limit));
|
||||
}
|
||||
|
||||
public async Task SaveDieBondingSetAsync()
|
||||
{
|
||||
bool success = await Loading.RunAsync(async (token, progress) =>
|
||||
{
|
||||
_dieBondingManualSetting.Write();
|
||||
|
||||
}, "保存参数...",isIndeterminate:true,canCancel:false);
|
||||
}
|
||||
|
||||
public Task GenerateTransPathAsync()
|
||||
{
|
||||
DieTransferPathRequest pathRequest = CreatePathRequest();
|
||||
DieTransferPathPlan pathPlan = _dieTransferPathGenerator.Generate(pathRequest);
|
||||
_allDieTransferPathItems = ConvertToPathItems(pathPlan);
|
||||
Pagination.CurrentPage = 1;
|
||||
LoadData();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private DieTransferPathRequest CreatePathRequest()
|
||||
{
|
||||
DieTransferPathRequest pathRequest = new DieTransferPathRequest();
|
||||
pathRequest.DieRegion = DieBondingManualSysItem.DieRegion;
|
||||
pathRequest.SubstrateRegion = DieBondingManualSysItem.SubstrateRegion;
|
||||
pathRequest.TransPathType = DieBondingManualSysItem.TransPathType;
|
||||
pathRequest.PadRowDirectionStrategy = DieBondingManualSysItem.PadRowDirectionStrategy;
|
||||
pathRequest.DieRowDirectionStrategy = DieBondingManualSysItem.DieRowDirectionStrategy;
|
||||
pathRequest.SkipNgDie = true;
|
||||
pathRequest.DieCandidates = CreateDieCandidates(DieBondingManualSysItem.DieRegion);
|
||||
pathRequest.PadCandidates = CreatePadCandidates(DieBondingManualSysItem.SubstrateRegion);
|
||||
return pathRequest;
|
||||
}
|
||||
|
||||
private List<Die> CreateDieCandidates(RegionModel region)
|
||||
{
|
||||
List<Die> dieCandidates = new List<Die>();
|
||||
if (region == null)
|
||||
{
|
||||
return dieCandidates;
|
||||
}
|
||||
|
||||
for (int row = region.StartRow; row <= region.EndRow; row++)
|
||||
{
|
||||
for (int column = region.StartCol; column <= region.EndCol; column++)
|
||||
{
|
||||
Die die = new Die();
|
||||
die.Row = row;
|
||||
die.Column = column;
|
||||
die.X = column;
|
||||
die.Y = row;
|
||||
die.Status = DieStatus.Normal;
|
||||
dieCandidates.Add(die);
|
||||
}
|
||||
}
|
||||
|
||||
return dieCandidates;
|
||||
}
|
||||
|
||||
private List<Pad> CreatePadCandidates(RegionModel region)
|
||||
{
|
||||
List<Pad> padCandidates = new List<Pad>();
|
||||
if (region == null)
|
||||
{
|
||||
return padCandidates;
|
||||
}
|
||||
|
||||
for (int row = region.StartRow; row <= region.EndRow; row++)
|
||||
{
|
||||
for (int column = region.StartCol; column <= region.EndCol; column++)
|
||||
{
|
||||
Pad pad = new Pad();
|
||||
pad.Row = row;
|
||||
pad.Column = column;
|
||||
pad.X = column;
|
||||
pad.Y = row;
|
||||
|
||||
SubstratePoint substratePoint;
|
||||
if (TryGetSubstratePoint(row, column, out substratePoint))
|
||||
{
|
||||
pad.X = substratePoint.X;
|
||||
pad.Y = substratePoint.Y;
|
||||
}
|
||||
|
||||
padCandidates.Add(pad);
|
||||
}
|
||||
}
|
||||
|
||||
return padCandidates;
|
||||
}
|
||||
|
||||
private bool TryGetSubstratePoint(int row, int column, out SubstratePoint substratePoint)
|
||||
{
|
||||
substratePoint = default(SubstratePoint);
|
||||
|
||||
SubstratePoint[,] substratePoints = _recipeManager.CurrentSubstrateRecipe != null
|
||||
? _recipeManager.CurrentSubstrateRecipe.SubstratePoints
|
||||
: null;
|
||||
if (substratePoints == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int rowIndex = row - 1;
|
||||
int columnIndex = column - 1;
|
||||
if (rowIndex < 0 || rowIndex >= substratePoints.GetLength(0) || columnIndex < 0 || columnIndex >= substratePoints.GetLength(1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
substratePoint = substratePoints[rowIndex, columnIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<DieTransferPathItem> ConvertToPathItems(DieTransferPathPlan pathPlan)
|
||||
{
|
||||
List<DieTransferPathItem> pathItems = new List<DieTransferPathItem>();
|
||||
if (pathPlan == null || pathPlan.Steps == null)
|
||||
{
|
||||
return pathItems;
|
||||
}
|
||||
|
||||
foreach (DieTransferPathStep pathStep in pathPlan.Steps)
|
||||
{
|
||||
DieTransferPathItem pathItem = DieTransferPathItem.FromPathStep(pathStep);
|
||||
if (pathItem != null)
|
||||
{
|
||||
pathItems.Add(pathItem);
|
||||
}
|
||||
}
|
||||
|
||||
return pathItems;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Manual.Model;
|
||||
using MainShell.Models;
|
||||
using MainShell.Process;
|
||||
using MainShell.ProcessResult;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Resources.CustomControl;
|
||||
using MaxwellFramework.Core.Interfaces;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Manual.ViewModel
|
||||
{
|
||||
public class DieStatisticsModel : PropertyChangedBase
|
||||
{
|
||||
private int _totalDieCount;
|
||||
public int TotalDieCount
|
||||
{
|
||||
get => _totalDieCount;
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _totalDieCount, value))
|
||||
{
|
||||
UpdatePassRate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _okDieCount;
|
||||
public int OkDieCount
|
||||
{
|
||||
get => _okDieCount;
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _okDieCount, value))
|
||||
{
|
||||
UpdatePassRate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _ngDieCount;
|
||||
public int NgDieCount
|
||||
{
|
||||
get => _ngDieCount;
|
||||
set => SetAndNotify(ref _ngDieCount, value);
|
||||
}
|
||||
|
||||
private double _averageSpacingX;
|
||||
public double AverageSpacingX
|
||||
{
|
||||
get => _averageSpacingX;
|
||||
set => SetAndNotify(ref _averageSpacingX, value);
|
||||
}
|
||||
|
||||
private double _averageSpacingY;
|
||||
public double AverageSpacingY
|
||||
{
|
||||
get => _averageSpacingY;
|
||||
set => SetAndNotify(ref _averageSpacingY, value);
|
||||
}
|
||||
|
||||
private double _passRate;
|
||||
public double PassRate
|
||||
{
|
||||
get => _passRate;
|
||||
private set => SetAndNotify(ref _passRate, value);
|
||||
}
|
||||
|
||||
private void UpdatePassRate()
|
||||
{
|
||||
PassRate = TotalDieCount > 0 ? (double)OkDieCount / TotalDieCount * 100 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public class DiePositionViewModel : OperateViewModelBase
|
||||
{
|
||||
private readonly RecipeManager _recipeManager;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ProcessResultManager _processResultManager;
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly DiePositionService _diePositionService;
|
||||
|
||||
private DieStatisticsModel _statistics = new DieStatisticsModel();
|
||||
public DieStatisticsModel Statistics
|
||||
{
|
||||
get => _statistics;
|
||||
set => SetAndNotify(ref _statistics, value);
|
||||
}
|
||||
|
||||
private DieMapModel _dieMapModel = new DieMapModel();
|
||||
public DieMapModel DieMapModel
|
||||
{
|
||||
get => _dieMapModel;
|
||||
set => SetAndNotify(ref _dieMapModel, value);
|
||||
}
|
||||
|
||||
public DiePositionViewModel(
|
||||
IEventAggregator eventAggregator,
|
||||
RecipeManager recipeManager,
|
||||
ProcessResultManager processResultManager,
|
||||
HardwareManager hardwareManager,
|
||||
IParameterManager parameterManager,
|
||||
DiePositionService diePositionService)
|
||||
{
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_recipeManager = recipeManager ?? throw new ArgumentNullException(nameof(recipeManager));
|
||||
_processResultManager = processResultManager ?? throw new ArgumentNullException(nameof(processResultManager));
|
||||
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
|
||||
_diePositionService = diePositionService ?? throw new ArgumentNullException(nameof(diePositionService));
|
||||
|
||||
_cameraAxisViewModel = IoC.Get<Common.Display.ViewModel.CameraAxisViewModel>();
|
||||
_cameraAxisViewModel.CameraAxisDevices.HardwareDeviceList = hardwareManager.CameraAxisManager.TopCameraAxisDevices;
|
||||
|
||||
InitTestMap();
|
||||
}
|
||||
|
||||
private void InitTestMap()
|
||||
{
|
||||
DieMapModel.Initialize(3, 3);
|
||||
|
||||
for (int r = 0; r < 3; r++)
|
||||
{
|
||||
for (int c = 0; c < 3; c++)
|
||||
{
|
||||
DieMapModel.SetDieState(r, c, DieState.Available);
|
||||
}
|
||||
}
|
||||
|
||||
DieMapModel.SetDieState(1, 1, DieState.Current);
|
||||
}
|
||||
|
||||
public async Task StartProcess()
|
||||
{
|
||||
MW.WorkFlow.WorkflowContext context = new MW.WorkFlow.WorkflowContext();
|
||||
context[WorkflowContextKeys.EventAggregator] = _eventAggregator;
|
||||
context[WorkflowContextKeys.RecipeManager] = _recipeManager;
|
||||
context[WorkflowContextKeys.ProcessResultManager] = _processResultManager;
|
||||
context[WorkflowContextKeys.WorkflowName] = ProcessFlowName.DiePositionFlow;
|
||||
|
||||
var result = await RunManualActivityAsync(
|
||||
new DiePositionActivity(ProcessFlowName.DiePositionFlow, _diePositionService),
|
||||
context);
|
||||
|
||||
if (!IsWorkflowCompleted(result))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DiePositionProcessResult processResult = context.GetData<DiePositionProcessResult>(WorkflowContextKeys.DiePositionResult);
|
||||
if (processResult == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateStatistics(processResult);
|
||||
UpdateMap(processResult);
|
||||
}
|
||||
|
||||
private void UpdateStatistics(DiePositionProcessResult processResult)
|
||||
{
|
||||
Statistics.TotalDieCount = processResult.TheoryDieCount;
|
||||
Statistics.OkDieCount = processResult.RecognizedDieCount;
|
||||
Statistics.NgDieCount = processResult.NgDieCount;
|
||||
Statistics.AverageSpacingX = processResult.AverageSpacingX;
|
||||
Statistics.AverageSpacingY = processResult.AverageSpacingY;
|
||||
}
|
||||
|
||||
private void UpdateMap(DiePositionProcessResult processResult)
|
||||
{
|
||||
if (processResult.RowCount <= 0 || processResult.ColumnCount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DieMapModel.Initialize(processResult.RowCount, processResult.ColumnCount);
|
||||
DieMapModel.SetDieState(0, 0, DieState.Current);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,581 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Log;
|
||||
using MainShell.Manual.Model;
|
||||
using MainShell.Models;
|
||||
using MainShell.Motion;
|
||||
using MainShell.Process;
|
||||
using MainShell.ProcessResult;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Resources.CustomControl;
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using StyletIoC;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using MW.WorkFlow;
|
||||
|
||||
namespace MainShell.Manual.ViewModel
|
||||
{
|
||||
public class DieRecheckDisplayItem : PropertyChangedBase
|
||||
{
|
||||
private int _stepIndex;
|
||||
public int StepIndex
|
||||
{
|
||||
get { return _stepIndex; }
|
||||
set { SetAndNotify(ref _stepIndex, value); }
|
||||
}
|
||||
|
||||
private int _padRow;
|
||||
public int PadRow
|
||||
{
|
||||
get { return _padRow; }
|
||||
set { SetAndNotify(ref _padRow, value); }
|
||||
}
|
||||
|
||||
private int _padColumn;
|
||||
public int PadColumn
|
||||
{
|
||||
get { return _padColumn; }
|
||||
set { SetAndNotify(ref _padColumn, value); }
|
||||
}
|
||||
|
||||
private int _dieRow;
|
||||
public int DieRow
|
||||
{
|
||||
get { return _dieRow; }
|
||||
set { SetAndNotify(ref _dieRow, value); }
|
||||
}
|
||||
|
||||
private int _dieColumn;
|
||||
public int DieColumn
|
||||
{
|
||||
get { return _dieColumn; }
|
||||
set { SetAndNotify(ref _dieColumn, value); }
|
||||
}
|
||||
|
||||
private double _padX;
|
||||
public double PadX
|
||||
{
|
||||
get { return _padX; }
|
||||
set { SetAndNotify(ref _padX, value); }
|
||||
}
|
||||
|
||||
private double _padY;
|
||||
public double PadY
|
||||
{
|
||||
get { return _padY; }
|
||||
set { SetAndNotify(ref _padY, value); }
|
||||
}
|
||||
|
||||
private double _dieX;
|
||||
public double DieX
|
||||
{
|
||||
get { return _dieX; }
|
||||
set { SetAndNotify(ref _dieX, value); }
|
||||
}
|
||||
|
||||
private double _dieY;
|
||||
public double DieY
|
||||
{
|
||||
get { return _dieY; }
|
||||
set { SetAndNotify(ref _dieY, value); }
|
||||
}
|
||||
|
||||
private string _transPathType;
|
||||
public string TransPathType
|
||||
{
|
||||
get { return _transPathType; }
|
||||
set { SetAndNotify(ref _transPathType, value); }
|
||||
}
|
||||
|
||||
private bool _isMissingBond;
|
||||
public bool IsMissingBond
|
||||
{
|
||||
get { return _isMissingBond; }
|
||||
set { SetAndNotify(ref _isMissingBond, value); }
|
||||
}
|
||||
|
||||
private double _xError;
|
||||
public double XError
|
||||
{
|
||||
get { return _xError; }
|
||||
set { SetAndNotify(ref _xError, value); }
|
||||
}
|
||||
|
||||
private double _yError;
|
||||
public double YError
|
||||
{
|
||||
get { return _yError; }
|
||||
set { SetAndNotify(ref _yError, value); }
|
||||
}
|
||||
|
||||
private bool _isXThresholdExceeded;
|
||||
public bool IsXThresholdExceeded
|
||||
{
|
||||
get { return _isXThresholdExceeded; }
|
||||
set { SetAndNotify(ref _isXThresholdExceeded, value); }
|
||||
}
|
||||
|
||||
private bool _isYThresholdExceeded;
|
||||
public bool IsYThresholdExceeded
|
||||
{
|
||||
get { return _isYThresholdExceeded; }
|
||||
set { SetAndNotify(ref _isYThresholdExceeded, value); }
|
||||
}
|
||||
|
||||
private string _statusSummary;
|
||||
public string StatusSummary
|
||||
{
|
||||
get { return _statusSummary; }
|
||||
set { SetAndNotify(ref _statusSummary, value); }
|
||||
}
|
||||
}
|
||||
|
||||
public class DieRecheckFilterState : PropertyChangedBase
|
||||
{
|
||||
private enum DieRecheckFilterRule
|
||||
{
|
||||
None = 0,
|
||||
MissingBondOnly,
|
||||
XThresholdExceededOnly,
|
||||
YThresholdExceededOnly,
|
||||
XYThresholdExceededOnly
|
||||
}
|
||||
|
||||
private DieRecheckFilterRule _selectedRule;
|
||||
private bool _isUpdatingSelection;
|
||||
|
||||
public bool IsMissingBondRuleSelected
|
||||
{
|
||||
get { return _selectedRule == DieRecheckFilterRule.MissingBondOnly; }
|
||||
set { SetSelectedRule(value, DieRecheckFilterRule.MissingBondOnly, nameof(IsMissingBondRuleSelected)); }
|
||||
}
|
||||
|
||||
public bool IsXThresholdRuleSelected
|
||||
{
|
||||
get { return _selectedRule == DieRecheckFilterRule.XThresholdExceededOnly; }
|
||||
set { SetSelectedRule(value, DieRecheckFilterRule.XThresholdExceededOnly, nameof(IsXThresholdRuleSelected)); }
|
||||
}
|
||||
|
||||
public bool IsYThresholdRuleSelected
|
||||
{
|
||||
get { return _selectedRule == DieRecheckFilterRule.YThresholdExceededOnly; }
|
||||
set { SetSelectedRule(value, DieRecheckFilterRule.YThresholdExceededOnly, nameof(IsYThresholdRuleSelected)); }
|
||||
}
|
||||
|
||||
public bool IsXYThresholdRuleSelected
|
||||
{
|
||||
get { return _selectedRule == DieRecheckFilterRule.XYThresholdExceededOnly; }
|
||||
set { SetSelectedRule(value, DieRecheckFilterRule.XYThresholdExceededOnly, nameof(IsXYThresholdRuleSelected)); }
|
||||
}
|
||||
|
||||
private double _xThreshold = 0.01d;
|
||||
public double XThreshold
|
||||
{
|
||||
get { return _xThreshold; }
|
||||
set { SetAndNotify(ref _xThreshold, value); }
|
||||
}
|
||||
|
||||
private double _yThreshold = 0.01d;
|
||||
public double YThreshold
|
||||
{
|
||||
get { return _yThreshold; }
|
||||
set { SetAndNotify(ref _yThreshold, value); }
|
||||
}
|
||||
|
||||
public void ClearSelectedRule()
|
||||
{
|
||||
UpdateSelectedRule(DieRecheckFilterRule.None, null);
|
||||
}
|
||||
|
||||
private void SetSelectedRule(bool isSelected, DieRecheckFilterRule targetRule, string propertyName)
|
||||
{
|
||||
if (_isUpdatingSelection)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DieRecheckFilterRule newRule = isSelected ? targetRule : DieRecheckFilterRule.None;
|
||||
UpdateSelectedRule(newRule, propertyName);
|
||||
}
|
||||
|
||||
private void UpdateSelectedRule(DieRecheckFilterRule newRule, string propertyName)
|
||||
{
|
||||
if (_selectedRule == newRule)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(propertyName))
|
||||
{
|
||||
OnPropertyChanged(propertyName);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedRule = newRule;
|
||||
_isUpdatingSelection = true;
|
||||
try
|
||||
{
|
||||
OnPropertyChanged(nameof(IsMissingBondRuleSelected));
|
||||
OnPropertyChanged(nameof(IsXThresholdRuleSelected));
|
||||
OnPropertyChanged(nameof(IsYThresholdRuleSelected));
|
||||
OnPropertyChanged(nameof(IsXYThresholdRuleSelected));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUpdatingSelection = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DieRecheckViewModel : OperateViewModelBase
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly RecipeManager _recipeManager;
|
||||
private readonly ProcessResultManager _processResultManager;
|
||||
private readonly DieRecheckService _dieRecheckService;
|
||||
private readonly SafeAxisMotion _safeAxisMotion;
|
||||
|
||||
private ObservableCollection<DieRecheckDisplayItem> _allPointResults = new ObservableCollection<DieRecheckDisplayItem>();
|
||||
private List<DieRecheckDisplayItem> _filteredPointResults = new List<DieRecheckDisplayItem>();
|
||||
|
||||
private ObservableCollection<DieRecheckDisplayItem> _pointResults = new ObservableCollection<DieRecheckDisplayItem>();
|
||||
public ObservableCollection<DieRecheckDisplayItem> PointResults
|
||||
{
|
||||
get { return _pointResults; }
|
||||
set { SetAndNotify(ref _pointResults, value); }
|
||||
}
|
||||
|
||||
public PaginationViewModel Pagination { get; } = new PaginationViewModel();
|
||||
|
||||
private RegionModel _recheckRegion = new RegionModel();
|
||||
public RegionModel RecheckRegion
|
||||
{
|
||||
get { return _recheckRegion; }
|
||||
set { SetAndNotify(ref _recheckRegion, value); }
|
||||
}
|
||||
|
||||
private DieRecheckFilterState _filterState = new DieRecheckFilterState();
|
||||
public DieRecheckFilterState FilterState
|
||||
{
|
||||
get { return _filterState; }
|
||||
set { SetAndNotify(ref _filterState, value); }
|
||||
}
|
||||
|
||||
private int _pointCount;
|
||||
public int PointCount
|
||||
{
|
||||
get { return _pointCount; }
|
||||
set { SetAndNotify(ref _pointCount, value); }
|
||||
}
|
||||
|
||||
private int _filteredPointCount;
|
||||
public int FilteredPointCount
|
||||
{
|
||||
get { return _filteredPointCount; }
|
||||
set { SetAndNotify(ref _filteredPointCount, value); }
|
||||
}
|
||||
|
||||
private DieRecheckDisplayItem _selectedPointResult;
|
||||
public DieRecheckDisplayItem SelectedPointResult
|
||||
{
|
||||
get { return _selectedPointResult; }
|
||||
set { SetAndNotify(ref _selectedPointResult, value); }
|
||||
}
|
||||
|
||||
public DieRecheckViewModel(
|
||||
IEventAggregator eventAggregator,
|
||||
RecipeManager recipeManager,
|
||||
ProcessResultManager processResultManager,
|
||||
HardwareManager hardwareManager,
|
||||
DieRecheckService dieRecheckService,
|
||||
SafeAxisMotion safeAxisMotion)
|
||||
{
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_recipeManager = recipeManager ?? throw new ArgumentNullException(nameof(recipeManager));
|
||||
_processResultManager = processResultManager ?? throw new ArgumentNullException(nameof(processResultManager));
|
||||
if (hardwareManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(hardwareManager));
|
||||
}
|
||||
_dieRecheckService = dieRecheckService ?? throw new ArgumentNullException(nameof(dieRecheckService));
|
||||
_safeAxisMotion = safeAxisMotion ?? throw new ArgumentNullException(nameof(safeAxisMotion));
|
||||
|
||||
_cameraAxisViewModel = IoC.Get<Common.Display.ViewModel.CameraAxisViewModel>();
|
||||
_cameraAxisViewModel.CameraAxisDevices.HardwareDeviceList = hardwareManager.CameraAxisManager.TopCameraAxisDevices;
|
||||
|
||||
FilterState.PropertyChanged += FilterState_PropertyChanged;
|
||||
RecheckRegion.PropertyChanged += RecheckRegion_PropertyChanged;
|
||||
Pagination.PageChanged += Pagination_PageChanged;
|
||||
|
||||
LoadCurrentPoints();
|
||||
}
|
||||
|
||||
protected override void OnViewLoaded()
|
||||
{
|
||||
base.OnViewLoaded();
|
||||
LoadCurrentPoints();
|
||||
}
|
||||
|
||||
public async Task StartProcess()
|
||||
{
|
||||
WorkflowContext context = new WorkflowContext();
|
||||
context[WorkflowContextKeys.EventAggregator] = _eventAggregator;
|
||||
context[WorkflowContextKeys.RecipeManager] = _recipeManager;
|
||||
context[WorkflowContextKeys.ProcessResultManager] = _processResultManager;
|
||||
context[WorkflowContextKeys.WorkflowName] = ProcessFlowName.DieRecheckFlow;
|
||||
|
||||
WorkflowRunCompletedEventArgs result = await RunManualActivityAsync(
|
||||
new DieRecheckActivity(ProcessFlowName.DieRecheckFlow, _dieRecheckService),
|
||||
context,
|
||||
false);
|
||||
|
||||
if (!IsWorkflowCompleted(result))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LoadCurrentPoints();
|
||||
}
|
||||
|
||||
public async Task MoveToSelectedPoint()
|
||||
{
|
||||
if (SelectedPointResult == null)
|
||||
{
|
||||
LocalizedMessageBox.Show(MessageKey.DieRecheckSelectedPointRequired, MessageKey.TitleWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
HardwareDevice selectedDevice = _cameraAxisViewModel.CameraAxisDevices != null
|
||||
? _cameraAxisViewModel.CameraAxisDevices.SelectedHardwareDevice
|
||||
: null;
|
||||
if (selectedDevice == null || selectedDevice.AxisX == null || selectedDevice.AxisY == null)
|
||||
{
|
||||
LocalizedMessageBox.Show(MessageKey.DieRecheckMoveDeviceUnavailable, MessageKey.TitleWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
string moveMessage = $"DieRecheck move to point. StepIndex={SelectedPointResult.StepIndex}, PadX={SelectedPointResult.PadX:F4}, PadY={SelectedPointResult.PadY:F4}";
|
||||
moveMessage.LogProcessInfo();
|
||||
|
||||
await Task.Run(() =>
|
||||
_safeAxisMotion.SafeMove(
|
||||
MotionMoveRequest.ForAxis(selectedDevice.AxisX, SelectedPointResult.PadX, source: nameof(DieRecheckViewModel), tags: new[] { "DieRecheck", "MoveToPoint", "AxisX" }),
|
||||
MotionMoveRequest.ForAxis(selectedDevice.AxisY, SelectedPointResult.PadY, source: nameof(DieRecheckViewModel), tags: new[] { "DieRecheck", "MoveToPoint", "AxisY" })));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
LogManager.LogSysError(exception);
|
||||
MwMessageBox.Show(exception.Message, LanguageResourceHelper.GetString(MessageKey.TitleError), MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetFilters()
|
||||
{
|
||||
FilterState.ClearSelectedRule();
|
||||
FilterState.XThreshold = 0.01d;
|
||||
FilterState.YThreshold = 0.01d;
|
||||
ApplyFilters();
|
||||
}
|
||||
|
||||
private void FilterState_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
UpdateThresholdFlags();
|
||||
Pagination.CurrentPage = 1;
|
||||
ApplyFilters();
|
||||
}
|
||||
|
||||
private void RecheckRegion_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
Pagination.CurrentPage = 1;
|
||||
ApplyFilters();
|
||||
}
|
||||
|
||||
private void Pagination_PageChanged(object sender, EventArgs e)
|
||||
{
|
||||
LoadPagedData();
|
||||
}
|
||||
|
||||
private void LoadCurrentPoints()
|
||||
{
|
||||
_allPointResults.Clear();
|
||||
_filteredPointResults.Clear();
|
||||
PointResults.Clear();
|
||||
|
||||
DieRecheckProcessResult processResult = _processResultManager.DieRecheckResult;
|
||||
if (processResult == null || processResult.PointResults == null)
|
||||
{
|
||||
PointCount = 0;
|
||||
FilteredPointCount = 0;
|
||||
Pagination.TotalItems = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (DieRecheckPointResult pointResult in processResult.PointResults)
|
||||
{
|
||||
_allPointResults.Add(new DieRecheckDisplayItem
|
||||
{
|
||||
StepIndex = pointResult.StepIndex,
|
||||
PadRow = pointResult.PadRow,
|
||||
PadColumn = pointResult.PadColumn,
|
||||
DieRow = pointResult.DieRow,
|
||||
DieColumn = pointResult.DieColumn,
|
||||
PadX = pointResult.PadX,
|
||||
PadY = pointResult.PadY,
|
||||
DieX = pointResult.DieX,
|
||||
DieY = pointResult.DieY,
|
||||
TransPathType = pointResult.TransPathType,
|
||||
IsMissingBond = pointResult.IsMissingBond,
|
||||
XError = pointResult.XError,
|
||||
YError = pointResult.YError
|
||||
});
|
||||
}
|
||||
|
||||
PointCount = _allPointResults.Count;
|
||||
InitializeRegion();
|
||||
UpdateThresholdFlags();
|
||||
Pagination.CurrentPage = 1;
|
||||
ApplyFilters();
|
||||
}
|
||||
|
||||
private void InitializeRegion()
|
||||
{
|
||||
if (_allPointResults.Count == 0)
|
||||
{
|
||||
RecheckRegion.StartRow = 1;
|
||||
RecheckRegion.StartCol = 1;
|
||||
RecheckRegion.EndRow = 1;
|
||||
RecheckRegion.EndCol = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
RecheckRegion.StartRow = _allPointResults.Min(item => item.PadRow);
|
||||
RecheckRegion.StartCol = _allPointResults.Min(item => item.PadColumn);
|
||||
RecheckRegion.EndRow = Math.Max(RecheckRegion.StartRow, _allPointResults.Max(item => item.PadRow));
|
||||
RecheckRegion.EndCol = Math.Max(RecheckRegion.StartCol, _allPointResults.Max(item => item.PadColumn));
|
||||
}
|
||||
|
||||
private void UpdateThresholdFlags()
|
||||
{
|
||||
foreach (DieRecheckDisplayItem item in _allPointResults)
|
||||
{
|
||||
item.IsXThresholdExceeded = Math.Abs(item.XError) >= FilterState.XThreshold;
|
||||
item.IsYThresholdExceeded = Math.Abs(item.YError) >= FilterState.YThreshold;
|
||||
item.StatusSummary = BuildStatusSummary(item);
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildStatusSummary(DieRecheckDisplayItem item)
|
||||
{
|
||||
Collection<string> tags = new Collection<string>();
|
||||
if (item.IsMissingBond)
|
||||
{
|
||||
tags.Add(LanguageResourceHelper.GetString(MessageKey.DieRecheckStatusMissingBond));
|
||||
}
|
||||
|
||||
if (item.IsXThresholdExceeded)
|
||||
{
|
||||
tags.Add(LanguageResourceHelper.Format(MessageKey.DieRecheckStatusXExceeded, item.XError));
|
||||
}
|
||||
|
||||
if (item.IsYThresholdExceeded)
|
||||
{
|
||||
tags.Add(LanguageResourceHelper.Format(MessageKey.DieRecheckStatusYExceeded, item.YError));
|
||||
}
|
||||
|
||||
if (tags.Count == 0)
|
||||
{
|
||||
tags.Add(LanguageResourceHelper.GetString(MessageKey.DieRecheckStatusOk));
|
||||
}
|
||||
|
||||
return string.Join(" / ", tags);
|
||||
}
|
||||
|
||||
private void ApplyFilters()
|
||||
{
|
||||
_filteredPointResults = _allPointResults.Where(MatchesFilters).ToList();
|
||||
FilteredPointCount = _filteredPointResults.Count;
|
||||
Pagination.TotalItems = FilteredPointCount;
|
||||
|
||||
if (Pagination.TotalItems == 0)
|
||||
{
|
||||
SelectedPointResult = null;
|
||||
PointResults = new ObservableCollection<DieRecheckDisplayItem>();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Pagination.CurrentPage > Pagination.TotalPages)
|
||||
{
|
||||
Pagination.CurrentPage = Pagination.TotalPages;
|
||||
return;
|
||||
}
|
||||
|
||||
LoadPagedData();
|
||||
}
|
||||
|
||||
private void LoadPagedData()
|
||||
{
|
||||
if (_filteredPointResults == null || _filteredPointResults.Count == 0)
|
||||
{
|
||||
PointResults = new ObservableCollection<DieRecheckDisplayItem>();
|
||||
SelectedPointResult = null;
|
||||
return;
|
||||
}
|
||||
|
||||
int safePage = Math.Max(1, Math.Min(Pagination.CurrentPage, Pagination.TotalPages));
|
||||
int offset = (safePage - 1) * Pagination.PageSize;
|
||||
int limit = Pagination.PageSize;
|
||||
|
||||
PointResults = new ObservableCollection<DieRecheckDisplayItem>(_filteredPointResults.Skip(offset).Take(limit));
|
||||
if (SelectedPointResult != null && !PointResults.Contains(SelectedPointResult))
|
||||
{
|
||||
SelectedPointResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool MatchesFilters(DieRecheckDisplayItem item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isInRegion = item.PadRow >= RecheckRegion.StartRow
|
||||
&& item.PadRow <= RecheckRegion.EndRow
|
||||
&& item.PadColumn >= RecheckRegion.StartCol
|
||||
&& item.PadColumn <= RecheckRegion.EndCol;
|
||||
if (!isInRegion)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FilterState.IsMissingBondRuleSelected && !item.IsMissingBond)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FilterState.IsXYThresholdRuleSelected && !(item.IsXThresholdExceeded && item.IsYThresholdExceeded))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FilterState.IsXThresholdRuleSelected && !item.IsXThresholdExceeded)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FilterState.IsYThresholdRuleSelected && !item.IsYThresholdExceeded)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
using MainShell.EventArgsFolder;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Manual.Model;
|
||||
using MainShell.Manual.ViewModel.State;
|
||||
using MainShell.Resources.CustomControl;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Manual.ViewModel
|
||||
{
|
||||
public class LogisticsOperationViewModel : OperateViewModelBase
|
||||
{
|
||||
private readonly IDeviceIoMonitorService _ioMonitorService;
|
||||
private readonly Dictionary<string, Action<bool>> _ioNameBindings = new Dictionary<string, Action<bool>>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<int, Action<bool>> _ioIdBindings = new Dictionary<int, Action<bool>>();
|
||||
|
||||
private LogisticsOperationState _state;
|
||||
|
||||
public new LogisticsOperationState State
|
||||
{
|
||||
get { return _state; }
|
||||
set { SetAndNotify(ref _state, value); }
|
||||
}
|
||||
|
||||
public LogisticsOperationViewModel(IDeviceIoMonitorService ioMonitorService)
|
||||
{
|
||||
_ioMonitorService = ioMonitorService;
|
||||
|
||||
State = BuildDefaultState();
|
||||
State.PropertyChanged += OnStatePropertyChanged;
|
||||
|
||||
InitializeIoBindings();
|
||||
|
||||
_ioMonitorService.IoChanged += OnIoSnapshotChanged;
|
||||
_ioMonitorService.Start();
|
||||
|
||||
ApplyEfemMode(State.IsManualWithoutEfem);
|
||||
RequestIoRefresh();
|
||||
}
|
||||
|
||||
private LogisticsOperationState BuildDefaultState()
|
||||
{
|
||||
var state = new LogisticsOperationState
|
||||
{
|
||||
SubstrateLoad = BuildSectionState("SUBSTRATE LOAD / 基板上料", "READY", "开始上料流程", "停止流程"),
|
||||
ChipLoad = BuildSectionState("CHIP LOAD / 芯片上料", "READY", "发送取料指令", "取消流程"),
|
||||
SubstrateOut = BuildSectionState("SUBSTRATE OUT / 基板下料", "IDLE", "开始下料出片", "停止流程"),
|
||||
ChipUnload = BuildSectionState("CHIP UNLOAD / 芯片下料", "AWAITING", "执行内部下料", "取消流程"),
|
||||
MagazineMap = MagazineMapViewModel.CreateDefault(25),
|
||||
FeedbackTitle = "FEEDBACK"
|
||||
};
|
||||
|
||||
state.ChipLoad.PhaseSteps.Add(new PhaseStepState
|
||||
{
|
||||
Title = "Phase 1: EFEM 取料",
|
||||
Status = "READY",
|
||||
ActionText = "发送取料指令",
|
||||
IsEnabled = true,
|
||||
ControlTag = "EFEM HANDOFF",
|
||||
RequiresEfemCommunication = true,
|
||||
TargetLayer = 1
|
||||
});
|
||||
state.ChipLoad.PhaseSteps.Add(new PhaseStepState
|
||||
{
|
||||
Title = "Phase 2: 设备内部吸取",
|
||||
Status = "内部手动",
|
||||
ActionText = "执行内部动作",
|
||||
IsEnabled = false,
|
||||
ControlTag = "INTERNAL",
|
||||
RequiresEfemCommunication = false
|
||||
});
|
||||
|
||||
state.ChipUnload.PhaseSteps.Add(new PhaseStepState
|
||||
{
|
||||
Title = "Phase 1: 内部移出至对接位",
|
||||
Status = "内部手动",
|
||||
ActionText = "执行内部下料",
|
||||
IsEnabled = true,
|
||||
ControlTag = "INTERNAL",
|
||||
RequiresEfemCommunication = false
|
||||
});
|
||||
state.ChipUnload.PhaseSteps.Add(new PhaseStepState
|
||||
{
|
||||
Title = "Phase 2: EFEM 返料到料盒",
|
||||
Status = "AWAITING",
|
||||
ActionText = "发送取料指令",
|
||||
IsEnabled = false,
|
||||
ControlTag = "EFEM HANDOFF",
|
||||
RequiresEfemCommunication = true,
|
||||
TargetLayer = 1
|
||||
});
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private SectionState BuildSectionState(string title, string status, string primaryAction, string secondaryAction)
|
||||
{
|
||||
return new SectionState
|
||||
{
|
||||
Title = title,
|
||||
Status = status,
|
||||
PrimaryActionText = primaryAction,
|
||||
SecondaryActionText = secondaryAction,
|
||||
Indicators =
|
||||
{
|
||||
new IndicatorState { Name = "进片感应器触发", IsOn = false },
|
||||
new IndicatorState { Name = "轨道电机运行中", IsOn = false },
|
||||
new IndicatorState { Name = "平移机原点检测", IsOn = false },
|
||||
new IndicatorState { Name = "真空吸附确认", IsOn = false }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public async Task ScanAsync()
|
||||
{
|
||||
if (State.IsScanning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
State.IsScanning = true;
|
||||
State.ScanProgress = 0;
|
||||
State.AddFeedback("自动扫码开始。", "INFO");
|
||||
RequestIoRefresh();
|
||||
|
||||
for (var i = 1; i <= 100; i++)
|
||||
{
|
||||
State.ScanProgress = i;
|
||||
await Task.Delay(20);
|
||||
}
|
||||
|
||||
State.IsScanning = false;
|
||||
State.AddFeedback("自动扫码完成,共扫描 25 个槽位。", "INFO");
|
||||
RequestIoRefresh();
|
||||
}
|
||||
|
||||
private void OnStatePropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(LogisticsOperationState.IsManualWithoutEfem))
|
||||
{
|
||||
ApplyEfemMode(State.IsManualWithoutEfem);
|
||||
}
|
||||
|
||||
RequestIoRefresh();
|
||||
}
|
||||
|
||||
private void ApplyEfemMode(bool manualWithoutEfem)
|
||||
{
|
||||
if (State.ChipLoad.PhaseSteps.Count > 1)
|
||||
{
|
||||
State.ChipLoad.PhaseSteps[0].IsEnabled = !manualWithoutEfem;
|
||||
State.ChipLoad.PhaseSteps[1].IsEnabled = manualWithoutEfem;
|
||||
}
|
||||
|
||||
if (State.ChipUnload.PhaseSteps.Count > 1)
|
||||
{
|
||||
State.ChipUnload.PhaseSteps[1].IsEnabled = !manualWithoutEfem;
|
||||
}
|
||||
|
||||
RequestIoRefresh();
|
||||
}
|
||||
|
||||
public Task ChipLoadPhase1Async()
|
||||
{
|
||||
if (State.IsManualWithoutEfem)
|
||||
{
|
||||
State.AddFeedback("CHIP LOAD: 无EFEM调试模式,直接执行设备内部芯片上料流程。", "INFO");
|
||||
return ChipLoadPhase2Async();
|
||||
}
|
||||
|
||||
var layer = State.ChipLoad.PhaseSteps[0].TargetLayer;
|
||||
State.AddFeedback(string.Format("CHIP LOAD Phase1: 发送EFEM取料指令,目标料盒层 L{0:D2}(预留通讯接口)。", layer), "INFO");
|
||||
if (State.ChipLoad.PhaseSteps.Count > 1)
|
||||
{
|
||||
State.ChipLoad.PhaseSteps[1].IsEnabled = true;
|
||||
}
|
||||
|
||||
RequestIoRefresh();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ChipLoadPhase2Async()
|
||||
{
|
||||
State.AddFeedback("CHIP LOAD Phase2: 执行设备内部吸取动作。", "INFO");
|
||||
RequestIoRefresh();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ChipUnloadPhase1Async()
|
||||
{
|
||||
State.AddFeedback("CHIP UNLOAD Phase1: 内部移出至对接位。", "INFO");
|
||||
RequestIoRefresh();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ChipUnloadPhase2Async()
|
||||
{
|
||||
var layer = State.ChipUnload.PhaseSteps[1].TargetLayer;
|
||||
State.AddFeedback(string.Format("CHIP UNLOAD Phase2: 发送EFEM返料指令,目标料盒层 L{0:D2}(预留通讯接口)。", layer), "INFO");
|
||||
RequestIoRefresh();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected override void OnDeactivate()
|
||||
{
|
||||
_ioMonitorService.IoChanged -= OnIoSnapshotChanged;
|
||||
base.OnDeactivate();
|
||||
}
|
||||
|
||||
private void InitializeIoBindings()
|
||||
{
|
||||
BindIoName("LG.SubstrateLoad.SensorIn", value => SetIndicator(State.SubstrateLoad, 0, value));
|
||||
BindIoName("LG.SubstrateLoad.TrackMotorRun", value => SetIndicator(State.SubstrateLoad, 1, value));
|
||||
BindIoName("LG.SubstrateLoad.HomeChecked", value => SetIndicator(State.SubstrateLoad, 2, value));
|
||||
BindIoName("LG.SubstrateLoad.VacuumOk", value => SetIndicator(State.SubstrateLoad, 3, value));
|
||||
|
||||
BindIoName("LG.ChipLoad.SensorIn", value => SetIndicator(State.ChipLoad, 0, value));
|
||||
BindIoName("LG.ChipLoad.TrackMotorRun", value => SetIndicator(State.ChipLoad, 1, value));
|
||||
BindIoName("LG.ChipLoad.HomeChecked", value => SetIndicator(State.ChipLoad, 2, value));
|
||||
BindIoName("LG.ChipLoad.VacuumOk", value => SetIndicator(State.ChipLoad, 3, value));
|
||||
|
||||
BindIoName("LG.SubstrateOut.SensorIn", value => SetIndicator(State.SubstrateOut, 0, value));
|
||||
BindIoName("LG.SubstrateOut.TrackMotorRun", value => SetIndicator(State.SubstrateOut, 1, value));
|
||||
BindIoName("LG.SubstrateOut.HomeChecked", value => SetIndicator(State.SubstrateOut, 2, value));
|
||||
BindIoName("LG.SubstrateOut.VacuumOk", value => SetIndicator(State.SubstrateOut, 3, value));
|
||||
|
||||
BindIoName("LG.ChipUnload.SensorIn", value => SetIndicator(State.ChipUnload, 0, value));
|
||||
BindIoName("LG.ChipUnload.TrackMotorRun", value => SetIndicator(State.ChipUnload, 1, value));
|
||||
BindIoName("LG.ChipUnload.HomeChecked", value => SetIndicator(State.ChipUnload, 2, value));
|
||||
BindIoName("LG.ChipUnload.VacuumOk", value => SetIndicator(State.ChipUnload, 3, value));
|
||||
|
||||
BindIoId(1001, value => SetIndicator(State.SubstrateLoad, 0, value));
|
||||
BindIoId(1002, value => SetIndicator(State.SubstrateLoad, 1, value));
|
||||
BindIoId(1003, value => SetIndicator(State.SubstrateLoad, 2, value));
|
||||
BindIoId(1004, value => SetIndicator(State.SubstrateLoad, 3, value));
|
||||
}
|
||||
|
||||
private void BindIoName(string name, Action<bool> setter)
|
||||
{
|
||||
_ioNameBindings[name] = setter;
|
||||
}
|
||||
|
||||
private void BindIoId(int id, Action<bool> setter)
|
||||
{
|
||||
_ioIdBindings[id] = setter;
|
||||
}
|
||||
|
||||
private void OnIoSnapshotChanged(object sender, DeviceIoChangedEventArgs e)
|
||||
{
|
||||
var changedPoints = e.ChangedPoints;
|
||||
for (var i = 0; i < changedPoints.Count; i++)
|
||||
{
|
||||
ApplyIoPoint(changedPoints[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyIoPoint(DeviceIoPointState point)
|
||||
{
|
||||
if (point == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Action<bool> setter;
|
||||
if (!string.IsNullOrWhiteSpace(point.Name) && _ioNameBindings.TryGetValue(point.Name, out setter))
|
||||
{
|
||||
setter(point.Value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_ioIdBindings.TryGetValue(point.Id, out setter))
|
||||
{
|
||||
setter(point.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void RequestIoRefresh()
|
||||
{
|
||||
_ioMonitorService.RequestRefresh();
|
||||
}
|
||||
|
||||
private static void SetIndicator(SectionState section, int index, bool isOn)
|
||||
{
|
||||
if (section == null || section.Indicators.Count <= index)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
section.Indicators[index].IsOn = isOn;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.ViewModel;
|
||||
using MaxwellFramework.Core.Interfaces;
|
||||
using Stylet;
|
||||
using StyletIoC;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Manual.ViewModel
|
||||
{
|
||||
public class ManualOperateViewModel : Stylet.Screen,IPage
|
||||
{
|
||||
public string Name => "ManualOperate";
|
||||
private const string LOGISTICS_OPERATION = "物流操作";
|
||||
private const string SUBSTRATEPOSITION = "基板定位";
|
||||
//基板测高
|
||||
private const string SUBSTRATEHEIGHTMEASUREMENT = "基板测高";
|
||||
//芯片拉直
|
||||
private const string CHIPSTRAIGHTENING = "芯片拉直";
|
||||
private const string WAFERPOSITION = "芯片定位";
|
||||
//Die转移
|
||||
private const string DIETRANSFER = "芯片转移";
|
||||
//精度复检
|
||||
private const string DIERECHECK = "精度复检";
|
||||
private readonly Dictionary<string, Screen> _viewModelDict = new Dictionary<string, Screen>();
|
||||
public ObservableCollection<MenuItemWrap> MenuItemWraps { get; private set; }
|
||||
|
||||
private MenuItemWrap _selectedMenuItem;
|
||||
|
||||
public MenuItemWrap SelectedMenuItem
|
||||
{
|
||||
get { return _selectedMenuItem; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _selectedMenuItem, value))
|
||||
{
|
||||
if (value != null && _viewModelDict.TryGetValue(value.Header, out var screen))
|
||||
{
|
||||
CurrentScreen = screen;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private Screen _currentScreen;
|
||||
|
||||
public Screen CurrentScreen
|
||||
{
|
||||
get { return _currentScreen; }
|
||||
set { SetAndNotify(ref _currentScreen, value); }
|
||||
}
|
||||
#region //界面ViewModel注入
|
||||
private LogisticsOperationViewModel _logisticsOperationViewModel;
|
||||
[Inject]
|
||||
public LogisticsOperationViewModel LogisticsOperationViewModel
|
||||
{
|
||||
get { return _logisticsOperationViewModel; }
|
||||
set { SetAndNotify(ref _logisticsOperationViewModel, value); }
|
||||
}
|
||||
private SubstratePositionViewModel _substratePositionViewModel;
|
||||
[Inject]
|
||||
public SubstratePositionViewModel SubstratePositionViewModel
|
||||
{
|
||||
get { return _substratePositionViewModel; }
|
||||
set { SetAndNotify(ref _substratePositionViewModel, value); }
|
||||
}
|
||||
|
||||
private WaferAngleAdjustmentViewModel _waferAngleAdjustmentViewModel;
|
||||
[Inject]
|
||||
public WaferAngleAdjustmentViewModel WaferAngleAdjustmentViewModel
|
||||
{
|
||||
get { return _waferAngleAdjustmentViewModel; }
|
||||
set { SetAndNotify(ref _waferAngleAdjustmentViewModel, value); }
|
||||
}
|
||||
private DieBondingViewModel _dieBondingViewModel;
|
||||
[Inject]
|
||||
public DieBondingViewModel DieBondingViewModel
|
||||
{
|
||||
get { return _dieBondingViewModel; }
|
||||
set { SetAndNotify(ref _dieBondingViewModel, value); }
|
||||
}
|
||||
private DiePositionViewModel _diePositionViewModel;
|
||||
[Inject]
|
||||
public DiePositionViewModel DiePositionViewModel
|
||||
{
|
||||
get { return _diePositionViewModel; }
|
||||
set { SetAndNotify(ref _diePositionViewModel, value); }
|
||||
}
|
||||
|
||||
|
||||
private ChipStraighteningViewModel _chipStraighteningViewModel;
|
||||
[Inject]
|
||||
public ChipStraighteningViewModel ChipStraighteningViewModel
|
||||
{
|
||||
get { return _chipStraighteningViewModel; }
|
||||
set { SetAndNotify(ref _chipStraighteningViewModel, value); }
|
||||
}
|
||||
|
||||
private SubstrateHeightMeasureViewModel _substrateHeightMeasureViewModel;
|
||||
[Inject]
|
||||
public SubstrateHeightMeasureViewModel SubstrateHeightMeasureViewModel
|
||||
{
|
||||
get { return _substrateHeightMeasureViewModel; }
|
||||
set { SetAndNotify(ref _substrateHeightMeasureViewModel, value); }
|
||||
}
|
||||
|
||||
private DieRecheckViewModel _dieRecheckViewModel;
|
||||
[Inject]
|
||||
public DieRecheckViewModel DieRecheckViewModel
|
||||
{
|
||||
get { return _dieRecheckViewModel; }
|
||||
set { SetAndNotify(ref _dieRecheckViewModel, value); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
public ManualOperateViewModel()
|
||||
{
|
||||
MenuItemWraps = new ObservableCollection<MenuItemWrap>()
|
||||
{
|
||||
new MenuItemWrap
|
||||
{
|
||||
Header=LOGISTICS_OPERATION,Tag=LOGISTICS_OPERATION
|
||||
},
|
||||
new MenuItemWrap
|
||||
{
|
||||
Header= SUBSTRATEPOSITION, Tag=SUBSTRATEPOSITION
|
||||
},
|
||||
new MenuItemWrap
|
||||
{
|
||||
Header=SUBSTRATEHEIGHTMEASUREMENT, Tag=SUBSTRATEHEIGHTMEASUREMENT
|
||||
},
|
||||
new MenuItemWrap
|
||||
{
|
||||
Header=CHIPSTRAIGHTENING, Tag=CHIPSTRAIGHTENING
|
||||
},
|
||||
new MenuItemWrap
|
||||
{
|
||||
Header=WAFERPOSITION, Tag=WAFERPOSITION
|
||||
},
|
||||
new MenuItemWrap
|
||||
{
|
||||
Header=DIETRANSFER, Tag=DIETRANSFER
|
||||
},
|
||||
new MenuItemWrap
|
||||
{
|
||||
Header=DIERECHECK, Tag=DIERECHECK
|
||||
}
|
||||
};
|
||||
}
|
||||
protected override void OnViewLoaded()
|
||||
{
|
||||
base.OnViewLoaded();
|
||||
if (_viewModelDict.Count == 0)
|
||||
{
|
||||
InitViewModelDict();
|
||||
}
|
||||
if (SelectedMenuItem == null)
|
||||
SelectedMenuItem = MenuItemWraps[0];
|
||||
}
|
||||
private void InitViewModelDict()
|
||||
{
|
||||
_viewModelDict.Clear();
|
||||
_viewModelDict.Add(LOGISTICS_OPERATION, LogisticsOperationViewModel);
|
||||
_viewModelDict.Add(SUBSTRATEPOSITION, SubstratePositionViewModel);
|
||||
_viewModelDict.Add(SUBSTRATEHEIGHTMEASUREMENT, SubstrateHeightMeasureViewModel);
|
||||
_viewModelDict.Add(CHIPSTRAIGHTENING, WaferAngleAdjustmentViewModel);
|
||||
|
||||
_viewModelDict.Add(DIETRANSFER, DieBondingViewModel);
|
||||
_viewModelDict.Add(WAFERPOSITION, DiePositionViewModel);
|
||||
_viewModelDict.Add(DIERECHECK, DieRecheckViewModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
using MainShell.Resources.CustomControl;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
||||
namespace MainShell.Manual.ViewModel.State
|
||||
{
|
||||
public class LogisticsOperationState : PropertyChangedBase
|
||||
{
|
||||
private SectionState _substrateLoad;
|
||||
public SectionState SubstrateLoad
|
||||
{
|
||||
get { return _substrateLoad; }
|
||||
set { SetAndNotify(ref _substrateLoad, value); }
|
||||
}
|
||||
|
||||
private SectionState _chipLoad;
|
||||
public SectionState ChipLoad
|
||||
{
|
||||
get { return _chipLoad; }
|
||||
set { SetAndNotify(ref _chipLoad, value); }
|
||||
}
|
||||
|
||||
private SectionState _substrateOut;
|
||||
public SectionState SubstrateOut
|
||||
{
|
||||
get { return _substrateOut; }
|
||||
set { SetAndNotify(ref _substrateOut, value); }
|
||||
}
|
||||
|
||||
private SectionState _chipUnload;
|
||||
public SectionState ChipUnload
|
||||
{
|
||||
get { return _chipUnload; }
|
||||
set { SetAndNotify(ref _chipUnload, value); }
|
||||
}
|
||||
|
||||
private MagazineMapViewModel _magazineMap;
|
||||
public MagazineMapViewModel MagazineMap
|
||||
{
|
||||
get { return _magazineMap; }
|
||||
set { SetAndNotify(ref _magazineMap, value); }
|
||||
}
|
||||
|
||||
private string _feedbackTitle;
|
||||
public string FeedbackTitle
|
||||
{
|
||||
get { return _feedbackTitle; }
|
||||
set { SetAndNotify(ref _feedbackTitle, value); }
|
||||
}
|
||||
|
||||
private bool _isScanning;
|
||||
public bool IsScanning
|
||||
{
|
||||
get { return _isScanning; }
|
||||
set { SetAndNotify(ref _isScanning, value); }
|
||||
}
|
||||
|
||||
private int _scanProgress;
|
||||
public int ScanProgress
|
||||
{
|
||||
get { return _scanProgress; }
|
||||
set { SetAndNotify(ref _scanProgress, value); }
|
||||
}
|
||||
|
||||
private bool _isManualWithoutEfem;
|
||||
public bool IsManualWithoutEfem
|
||||
{
|
||||
get { return _isManualWithoutEfem; }
|
||||
set { SetAndNotify(ref _isManualWithoutEfem, value); }
|
||||
}
|
||||
|
||||
public string LastFeedbackTime
|
||||
{
|
||||
get
|
||||
{
|
||||
var last = FeedbackRecords.LastOrDefault();
|
||||
return last == null ? "--:--:--" : last.Time.ToString("HH:mm:ss");
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<FeedbackRecord> FeedbackRecords { get; } = new ObservableCollection<FeedbackRecord>();
|
||||
|
||||
public LogisticsOperationState()
|
||||
{
|
||||
FeedbackRecords.CollectionChanged += OnFeedbackRecordsChanged;
|
||||
AddFeedback("内部物流动作已独立触发并完成。");
|
||||
}
|
||||
|
||||
public void AddFeedback(string message, string level = "DEBUG")
|
||||
{
|
||||
FeedbackRecords.Add(new FeedbackRecord
|
||||
{
|
||||
Time = DateTime.Now,
|
||||
Level = level,
|
||||
Message = message
|
||||
});
|
||||
|
||||
while (FeedbackRecords.Count > 3)
|
||||
{
|
||||
FeedbackRecords.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFeedbackRecordsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(LastFeedbackTime));
|
||||
}
|
||||
}
|
||||
|
||||
public class SectionState : PropertyChangedBase
|
||||
{
|
||||
private string _title;
|
||||
public string Title
|
||||
{
|
||||
get { return _title; }
|
||||
set { SetAndNotify(ref _title, value); }
|
||||
}
|
||||
|
||||
private string _status;
|
||||
public string Status
|
||||
{
|
||||
get { return _status; }
|
||||
set { SetAndNotify(ref _status, value); }
|
||||
}
|
||||
|
||||
private string _primaryActionText;
|
||||
public string PrimaryActionText
|
||||
{
|
||||
get { return _primaryActionText; }
|
||||
set { SetAndNotify(ref _primaryActionText, value); }
|
||||
}
|
||||
|
||||
private string _secondaryActionText;
|
||||
public string SecondaryActionText
|
||||
{
|
||||
get { return _secondaryActionText; }
|
||||
set { SetAndNotify(ref _secondaryActionText, value); }
|
||||
}
|
||||
|
||||
public ObservableCollection<IndicatorState> Indicators { get; } = new ObservableCollection<IndicatorState>();
|
||||
|
||||
public ObservableCollection<PhaseStepState> PhaseSteps { get; } = new ObservableCollection<PhaseStepState>();
|
||||
}
|
||||
|
||||
public class PhaseStepState : PropertyChangedBase
|
||||
{
|
||||
private string _title;
|
||||
public string Title
|
||||
{
|
||||
get { return _title; }
|
||||
set { SetAndNotify(ref _title, value); }
|
||||
}
|
||||
|
||||
private string _status;
|
||||
public string Status
|
||||
{
|
||||
get { return _status; }
|
||||
set { SetAndNotify(ref _status, value); }
|
||||
}
|
||||
|
||||
private string _actionText;
|
||||
public string ActionText
|
||||
{
|
||||
get { return _actionText; }
|
||||
set { SetAndNotify(ref _actionText, value); }
|
||||
}
|
||||
|
||||
private bool _isEnabled;
|
||||
public bool IsEnabled
|
||||
{
|
||||
get { return _isEnabled; }
|
||||
set { SetAndNotify(ref _isEnabled, value); }
|
||||
}
|
||||
|
||||
private string _controlTag;
|
||||
public string ControlTag
|
||||
{
|
||||
get { return _controlTag; }
|
||||
set { SetAndNotify(ref _controlTag, value); }
|
||||
}
|
||||
|
||||
private bool _requiresEfemCommunication;
|
||||
public bool RequiresEfemCommunication
|
||||
{
|
||||
get { return _requiresEfemCommunication; }
|
||||
set { SetAndNotify(ref _requiresEfemCommunication, value); }
|
||||
}
|
||||
|
||||
private int _targetLayer = 1;
|
||||
public int TargetLayer
|
||||
{
|
||||
get { return _targetLayer; }
|
||||
set { SetAndNotify(ref _targetLayer, value); }
|
||||
}
|
||||
}
|
||||
|
||||
public class IndicatorState : PropertyChangedBase
|
||||
{
|
||||
private string _name;
|
||||
public string Name
|
||||
{
|
||||
get { return _name; }
|
||||
set { SetAndNotify(ref _name, value); }
|
||||
}
|
||||
|
||||
private bool _isOn;
|
||||
public bool IsOn
|
||||
{
|
||||
get { return _isOn; }
|
||||
set { SetAndNotify(ref _isOn, value); }
|
||||
}
|
||||
}
|
||||
|
||||
public class FeedbackRecord : PropertyChangedBase
|
||||
{
|
||||
private DateTime _time;
|
||||
public DateTime Time
|
||||
{
|
||||
get { return _time; }
|
||||
set { SetAndNotify(ref _time, value); }
|
||||
}
|
||||
|
||||
private string _level;
|
||||
public string Level
|
||||
{
|
||||
get { return _level; }
|
||||
set { SetAndNotify(ref _level, value); }
|
||||
}
|
||||
|
||||
private string _message;
|
||||
public string Message
|
||||
{
|
||||
get { return _message; }
|
||||
set { SetAndNotify(ref _message, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Manual.Model;
|
||||
using MainShell.Process;
|
||||
using MainShell.ProcessResult;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Recipe.Models.SubstrateParameter;
|
||||
using MW.WorkFlow;
|
||||
using Stylet;
|
||||
using StyletIoC;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Manual.ViewModel
|
||||
{
|
||||
public class SubstrateHeightMeasureDisplayItem : PropertyChangedBase
|
||||
{
|
||||
private string _pointName;
|
||||
public string PointName
|
||||
{
|
||||
get { return _pointName; }
|
||||
set { SetAndNotify(ref _pointName, value); }
|
||||
}
|
||||
|
||||
private int _rowIndex;
|
||||
public int RowIndex
|
||||
{
|
||||
get { return _rowIndex; }
|
||||
set { SetAndNotify(ref _rowIndex, value); }
|
||||
}
|
||||
|
||||
private int _columnIndex;
|
||||
public int ColumnIndex
|
||||
{
|
||||
get { return _columnIndex; }
|
||||
set { SetAndNotify(ref _columnIndex, value); }
|
||||
}
|
||||
|
||||
private double _positionX;
|
||||
public double PositionX
|
||||
{
|
||||
get { return _positionX; }
|
||||
set { SetAndNotify(ref _positionX, value); }
|
||||
}
|
||||
|
||||
private double _positionY;
|
||||
public double PositionY
|
||||
{
|
||||
get { return _positionY; }
|
||||
set { SetAndNotify(ref _positionY, value); }
|
||||
}
|
||||
|
||||
private double? _heightValue;
|
||||
public double? HeightValue
|
||||
{
|
||||
get { return _heightValue; }
|
||||
set { SetAndNotify(ref _heightValue, value); }
|
||||
}
|
||||
}
|
||||
|
||||
public class SubstrateHeightMeasureViewModel : OperateViewModelBase
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly RecipeManager _recipeManager;
|
||||
private readonly ProcessResultManager _processResultManager;
|
||||
private readonly SubstrateHeightMeasureService _substrateHeightMeasureService;
|
||||
|
||||
private ObservableCollection<SubstrateHeightMeasureDisplayItem> _pointResults = new ObservableCollection<SubstrateHeightMeasureDisplayItem>();
|
||||
public ObservableCollection<SubstrateHeightMeasureDisplayItem> PointResults
|
||||
{
|
||||
get { return _pointResults; }
|
||||
set { SetAndNotify(ref _pointResults, value); }
|
||||
}
|
||||
|
||||
private string _recipeName;
|
||||
public string RecipeName
|
||||
{
|
||||
get { return _recipeName; }
|
||||
set { SetAndNotify(ref _recipeName, value); }
|
||||
}
|
||||
|
||||
private int _configuredPointCount;
|
||||
public int ConfiguredPointCount
|
||||
{
|
||||
get { return _configuredPointCount; }
|
||||
set { SetAndNotify(ref _configuredPointCount, value); }
|
||||
}
|
||||
|
||||
private int _measuredPointCount;
|
||||
public int MeasuredPointCount
|
||||
{
|
||||
get { return _measuredPointCount; }
|
||||
set { SetAndNotify(ref _measuredPointCount, value); }
|
||||
}
|
||||
|
||||
private SubstrateHeightMeasureMode _currentMeasureMode;
|
||||
public SubstrateHeightMeasureMode CurrentMeasureMode
|
||||
{
|
||||
get { return _currentMeasureMode; }
|
||||
set { SetAndNotify(ref _currentMeasureMode, value); }
|
||||
}
|
||||
|
||||
public SubstrateHeightMeasureViewModel(
|
||||
IEventAggregator eventAggregator,
|
||||
RecipeManager recipeManager,
|
||||
ProcessResultManager processResultManager,
|
||||
HardwareManager hardwareManager,
|
||||
SubstrateHeightMeasureService substrateHeightMeasureService)
|
||||
{
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_recipeManager = recipeManager ?? throw new ArgumentNullException(nameof(recipeManager));
|
||||
_processResultManager = processResultManager ?? throw new ArgumentNullException(nameof(processResultManager));
|
||||
_substrateHeightMeasureService = substrateHeightMeasureService ?? throw new ArgumentNullException(nameof(substrateHeightMeasureService));
|
||||
|
||||
_cameraAxisViewModel = IoC.Get<Common.Display.ViewModel.CameraAxisViewModel>();
|
||||
_cameraAxisViewModel.CameraAxisDevices.HardwareDeviceList = hardwareManager.CameraAxisManager.TopCameraAxisDevices;
|
||||
|
||||
RefreshRecipeSummary();
|
||||
LoadLastMeasureResult();
|
||||
}
|
||||
|
||||
protected override void OnViewLoaded()
|
||||
{
|
||||
base.OnViewLoaded();
|
||||
RefreshRecipeSummary();
|
||||
LoadLastMeasureResult();
|
||||
}
|
||||
|
||||
public async Task StartProcess()
|
||||
{
|
||||
RefreshRecipeSummary();
|
||||
|
||||
WorkflowContext context = new WorkflowContext();
|
||||
context[WorkflowContextKeys.EventAggregator] = _eventAggregator;
|
||||
context[WorkflowContextKeys.RecipeManager] = _recipeManager;
|
||||
context[WorkflowContextKeys.ProcessResultManager] = _processResultManager;
|
||||
context[WorkflowContextKeys.WorkflowName] = ProcessFlowName.SubstrateHeightMeasureFlow;
|
||||
|
||||
WorkflowRunCompletedEventArgs result = await RunManualActivityAsync(
|
||||
new SubstrateHeightMeasureActivity(ProcessFlowName.SubstrateHeightMeasureFlow, _substrateHeightMeasureService),
|
||||
context);
|
||||
|
||||
if (!IsWorkflowCompleted(result))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LoadLastMeasureResult();
|
||||
}
|
||||
|
||||
private void RefreshRecipeSummary()
|
||||
{
|
||||
SubstrateRecipe currentRecipe = _recipeManager.CurrentSubstrateRecipe;
|
||||
RecipeName = currentRecipe == null ? string.Empty : currentRecipe.RecipeName;
|
||||
|
||||
if (currentRecipe == null || currentRecipe.HeightMeasureSetting == null)
|
||||
{
|
||||
ConfiguredPointCount = 0;
|
||||
CurrentMeasureMode = SubstrateHeightMeasureMode.StandardTeachPosition;
|
||||
return;
|
||||
}
|
||||
|
||||
ConfiguredPointCount = currentRecipe.HeightMeasureSetting.Points == null ? 0 : currentRecipe.HeightMeasureSetting.Points.Count;
|
||||
CurrentMeasureMode = currentRecipe.HeightMeasureSetting.Mode;
|
||||
}
|
||||
|
||||
private void LoadLastMeasureResult()
|
||||
{
|
||||
PointResults.Clear();
|
||||
|
||||
SubstrateHeightMeasureProcessResult processResult = _processResultManager.SubstrateHeightMeasureResult;
|
||||
if (processResult == null || processResult.PointResults == null)
|
||||
{
|
||||
MeasuredPointCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (SubstrateHeightMeasurePointResult pointResult in processResult.PointResults)
|
||||
{
|
||||
PointResults.Add(new SubstrateHeightMeasureDisplayItem
|
||||
{
|
||||
PointName = pointResult.PointName,
|
||||
RowIndex = pointResult.RowIndex,
|
||||
ColumnIndex = pointResult.ColumnIndex,
|
||||
PositionX = pointResult.PositionX,
|
||||
PositionY = pointResult.PositionY,
|
||||
HeightValue = pointResult.HeightValue
|
||||
});
|
||||
}
|
||||
|
||||
MeasuredPointCount = PointResults.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.EventArgsFolder;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Manual.Model;
|
||||
using MainShell.Models;
|
||||
using MainShell.Process;
|
||||
using MainShell.ProcessResult;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Resources.CustomControl;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Manual.ViewModel
|
||||
{
|
||||
public class MarkResultModel : PropertyChangedBase
|
||||
{
|
||||
private int _id;
|
||||
|
||||
public int Id
|
||||
{
|
||||
get { return _id; }
|
||||
set { SetAndNotify(ref _id, value); }
|
||||
}
|
||||
|
||||
private MPoint _basePos;
|
||||
|
||||
public MPoint BasePose
|
||||
{
|
||||
get { return _basePos; }
|
||||
set { SetAndNotify(ref _basePos, value); }
|
||||
}
|
||||
private MPoint _resultPose;
|
||||
|
||||
public MPoint ResultPose
|
||||
{
|
||||
get { return _resultPose; }
|
||||
set { SetAndNotify(ref _resultPose, value); }
|
||||
}
|
||||
|
||||
}
|
||||
// 实现3个事件接口
|
||||
public class SubstratePositionViewModel : OperateViewModelBase,
|
||||
IHandle<SubstrateProcessStartedEventArgs>,
|
||||
IHandle<SubstrateMarkFoundEventArgs>,
|
||||
IHandle<SubstrateResultEventArgs>
|
||||
{
|
||||
private sealed class DelegateCommand : ICommand
|
||||
{
|
||||
private readonly Action<object> _execute;
|
||||
|
||||
public DelegateCommand(Action<object> execute)
|
||||
{
|
||||
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
|
||||
}
|
||||
|
||||
public event EventHandler CanExecuteChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
}
|
||||
remove
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanExecute(object parameter)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Execute(object parameter)
|
||||
{
|
||||
_execute(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
// 属性定义保持不变...
|
||||
private ObservableCollection<MarkResultModel> _markResults = new ObservableCollection<MarkResultModel>();
|
||||
public ObservableCollection<MarkResultModel> MarkResults
|
||||
{
|
||||
get { return _markResults; }
|
||||
set { SetAndNotify(ref _markResults, value); }
|
||||
}
|
||||
|
||||
private int _row;
|
||||
|
||||
public int RowIndex
|
||||
{
|
||||
get { return _row; }
|
||||
set { SetAndNotify(ref _row, value); }
|
||||
}
|
||||
private int _col;
|
||||
|
||||
public int ColumnIndex
|
||||
{
|
||||
get { return _col; }
|
||||
set { SetAndNotify(ref _col, value); }
|
||||
}
|
||||
|
||||
private int _rowMax;
|
||||
|
||||
public int RowMax
|
||||
{
|
||||
get { return _rowMax; }
|
||||
set { SetAndNotify(ref _rowMax, value); }
|
||||
}
|
||||
|
||||
private int _colMax;
|
||||
|
||||
public int ColMax
|
||||
{
|
||||
get { return _colMax; }
|
||||
set { SetAndNotify(ref _colMax, value); }
|
||||
}
|
||||
|
||||
private double _substrateAngle;
|
||||
public double SubstrateAngle
|
||||
{
|
||||
get { return _substrateAngle; }
|
||||
set { SetAndNotify(ref _substrateAngle, value); }
|
||||
}
|
||||
|
||||
private DieMapModel _substrateMapModel = new DieMapModel();
|
||||
public DieMapModel SubstrateMapModel
|
||||
{
|
||||
get => _substrateMapModel;
|
||||
set => SetAndNotify(ref _substrateMapModel, value);
|
||||
}
|
||||
|
||||
private bool _isClickEnabled = true;
|
||||
public bool IsClickEnabled
|
||||
{
|
||||
get => _isClickEnabled;
|
||||
set => SetAndNotify(ref _isClickEnabled, value);
|
||||
}
|
||||
|
||||
public ICommand DieClickedCommand { get; private set; }
|
||||
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly RecipeManager _recipeManager;
|
||||
private readonly IEventAggregator _eventAggregator; // 引用 EventAggregator
|
||||
private readonly ProcessResultManager _processResultManager;
|
||||
private readonly SubstratePositionMotionService _substratePositionMotionService;
|
||||
|
||||
|
||||
public SubstratePositionViewModel(HardwareManager hardwareManager, RecipeManager recipeManager, IEventAggregator eventAggregator, ProcessResultManager processResultManager, SubstratePositionMotionService substratePositionMotionService)
|
||||
{
|
||||
_recipeManager = recipeManager ?? throw new ArgumentNullException(nameof(recipeManager));
|
||||
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_processResultManager = processResultManager ?? throw new ArgumentNullException(nameof(processResultManager));
|
||||
_substratePositionMotionService = substratePositionMotionService ?? throw new ArgumentNullException(nameof(substratePositionMotionService));
|
||||
|
||||
_eventAggregator.Subscribe(this);
|
||||
DieClickedCommand = new DelegateCommand(ExecuteDieClicked);
|
||||
|
||||
_cameraAxisViewModel = IoC.Get<Common.Display.ViewModel.CameraAxisViewModel>();
|
||||
_cameraAxisViewModel.CameraAxisDevices.HardwareDeviceList = hardwareManager.CameraAxisManager.TopCameraAxisDevices;
|
||||
}
|
||||
|
||||
private void ExecuteDieClicked(object parameter)
|
||||
{
|
||||
if (!(parameter is ValueTuple<int, int, DieState>))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ValueTuple<int, int, DieState> dieInfo = (ValueTuple<int, int, DieState>)parameter;
|
||||
OnDieClicked(this, new DieMapControl.DieClickedEventArgs(dieInfo.Item1, dieInfo.Item2, dieInfo.Item3));
|
||||
}
|
||||
|
||||
public void Handle(SubstrateProcessStartedEventArgs message)
|
||||
{
|
||||
Stylet.Execute.OnUIThread(() =>
|
||||
{
|
||||
MarkResults.Clear();
|
||||
SubstrateAngle = 0;
|
||||
});
|
||||
}
|
||||
|
||||
// 2. 找到 Mark 点:更新列表
|
||||
public void Handle(SubstrateMarkFoundEventArgs message)
|
||||
{
|
||||
Stylet.Execute.OnUIThread(() =>
|
||||
{
|
||||
var existing = MarkResults.FirstOrDefault(x => x.Id == message.Id);
|
||||
if (existing != null)
|
||||
{
|
||||
existing.BasePose = message.BasePose;
|
||||
existing.ResultPose = message.ResultPose;
|
||||
}
|
||||
else
|
||||
{
|
||||
MarkResults.Add(new MarkResultModel
|
||||
{
|
||||
Id = message.Id,
|
||||
BasePose = message.BasePose,
|
||||
ResultPose = message.ResultPose
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 计算完成:更新角度
|
||||
public void Handle(SubstrateResultEventArgs message)
|
||||
{
|
||||
Stylet.Execute.OnUIThread(() =>
|
||||
{
|
||||
SubstrateAngle = message.Angle;
|
||||
});
|
||||
}
|
||||
|
||||
// 手动启动流程
|
||||
public async Task StartProcess()
|
||||
{
|
||||
var context = new MW.WorkFlow.WorkflowContext();
|
||||
context[WorkflowContextKeys.EventAggregator] = _eventAggregator;
|
||||
context[WorkflowContextKeys.RecipeManager] = _recipeManager;
|
||||
context[WorkflowContextKeys.ProcessResultManager] = _processResultManager;
|
||||
context[WorkflowContextKeys.WorkflowName] = ProcessFlowName.SubstratePositionFlow;
|
||||
|
||||
await RunManualActivityAsync(new MainShell.Process.SubstratePositionActivity(ProcessFlowName.SubstratePositionFlow, _substratePositionMotionService), context);
|
||||
}
|
||||
|
||||
protected override void OnViewLoaded()
|
||||
{
|
||||
base.OnViewLoaded();
|
||||
if (_recipeManager.CurrentSubstrateRecipe != null && _recipeManager.CurrentSubstrateRecipe.SubstrateInfo != null)
|
||||
{
|
||||
RowMax = _recipeManager.CurrentSubstrateRecipe.SubstrateInfo.RowNumber;
|
||||
ColMax = _recipeManager.CurrentSubstrateRecipe.SubstrateInfo.ColNumber;
|
||||
|
||||
// 初始化基板Map
|
||||
if (RowMax > 0 && ColMax > 0)
|
||||
{
|
||||
SubstrateMapModel.Initialize(RowMax, ColMax);
|
||||
for (int r = 0; r < RowMax; r++)
|
||||
{
|
||||
for (int c = 0; c < ColMax; c++)
|
||||
{
|
||||
SubstrateMapModel.SetDieState(r, c, DieState.Available);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public void OnDieClicked(object sender, DieMapControl.DieClickedEventArgs e)
|
||||
{
|
||||
if (!IsClickEnabled) return;
|
||||
|
||||
// 更新选中的行列
|
||||
RowIndex = e.Row + 1; // 界面显示通常从1开始
|
||||
ColumnIndex = e.Col + 1;
|
||||
|
||||
// 提示用户
|
||||
MessageBox.Show($"已选择基板位置: 行 {RowIndex}, 列 {ColumnIndex}", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
|
||||
public void MoveToSelectPad()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Manual.Model;
|
||||
using MainShell.ProcessResult;
|
||||
using MainShell.Recipe.Models;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Manual.ViewModel
|
||||
{
|
||||
public class WaferAngleAdjustmentViewModel : OperateViewModelBase
|
||||
{
|
||||
private readonly RecipeManager _recipeManager;
|
||||
private readonly IEventAggregator _eventAggregator; // 引用 EventAggregator
|
||||
private readonly ProcessResultManager _processResultManager;
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
public WaferAngleAdjustmentViewModel(IEventAggregator eventAggregator,RecipeManager recipeManager,
|
||||
ProcessResultManager processResultManager, HardwareManager hardwareManager)
|
||||
{
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_recipeManager = recipeManager ?? throw new ArgumentNullException(nameof(recipeManager));
|
||||
_processResultManager = processResultManager ?? throw new ArgumentNullException(nameof(processResultManager));
|
||||
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
|
||||
_cameraAxisViewModel = IoC.Get<Common.Display.ViewModel.CameraAxisViewModel>();
|
||||
_cameraAxisViewModel.CameraAxisDevices.HardwareDeviceList = hardwareManager.CameraAxisManager.TopCameraAxisDevices;
|
||||
}
|
||||
|
||||
public async Task StartProcess()
|
||||
{
|
||||
var context = new MW.WorkFlow.WorkflowContext();
|
||||
context[WorkflowContextKeys.EventAggregator] = _eventAggregator;
|
||||
context[WorkflowContextKeys.RecipeManager] = _recipeManager;
|
||||
context[WorkflowContextKeys.ProcessResultManager] = _processResultManager;
|
||||
context[WorkflowContextKeys.WorkflowName] = MainShell.Process.ProcessFlowName.ChipStraighteningFlow;
|
||||
|
||||
await RunManualActivityAsync(new MainShell.Process.WaferAngleAdjustmentActivity(MainShell.Process.ProcessFlowName.ChipStraighteningFlow), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user