添加 MX-PD-盘古 项目文件
将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Filewritable;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using MwFramework.Controls.Components;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class CarrierRecipeViewModel : RecipeViewModelBase<CarrierRecipe>,
|
||||
IHandle<SubstrateNameChangedEventArgs>, IHandle<SubstrateDeletedEventArgs>
|
||||
{
|
||||
#region //Command
|
||||
public ICommand RemoveSubstrateCommand { get; private set; }
|
||||
#endregion
|
||||
|
||||
private CarrierRecipe _carrierRecipe;
|
||||
public CarrierRecipe CarrierRecipe
|
||||
{
|
||||
get { return _carrierRecipe; }
|
||||
set { SetAndNotify(ref _carrierRecipe, value); }
|
||||
}
|
||||
|
||||
protected override CarrierRecipe CurrentRecipe
|
||||
{
|
||||
get => CarrierRecipe;
|
||||
set => CarrierRecipe = value;
|
||||
}
|
||||
|
||||
protected override string RecipeFolderPath => Paths.CarrierRecipe;
|
||||
protected override string EmptyRecipeDisplayName => "当前载具配方";
|
||||
|
||||
protected override string GetActiveRecipeName()
|
||||
{
|
||||
return RecipeManager.CurrentCarrierRecipe?.RecipeName;
|
||||
}
|
||||
|
||||
protected override void SwitchActiveRecipe(string recipeName)
|
||||
{
|
||||
RecipeManager.SwitchCarrierRecipe(recipeName);
|
||||
}
|
||||
|
||||
protected override void ClearActiveRecipe()
|
||||
{
|
||||
RecipeManager.ClearCarrierRecipe();
|
||||
}
|
||||
|
||||
public ObservableCollection<RecipeWrap> SubstrateRecipes => _wrapManager.SubstrateRecipeWraps;
|
||||
|
||||
private readonly RecipeWrapManager _wrapManager;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
|
||||
public CarrierRecipeViewModel(RecipeWrapManager wrapManager, IEventAggregator eventAggregator, RecipeManager recipeManager)
|
||||
: base(recipeManager)
|
||||
{
|
||||
_wrapManager = wrapManager;
|
||||
_eventAggregator = eventAggregator;
|
||||
_eventAggregator.Unsubscribe(this);
|
||||
_eventAggregator.Subscribe(this);
|
||||
|
||||
RegisterButtonGroupEvents();
|
||||
RecipeWraps = wrapManager.CarrierRecipeWraps;
|
||||
RemoveSubstrateCommand = new DelegateCommand<object>(RemoveSubstrate);
|
||||
}
|
||||
|
||||
protected override void OnViewLoaded()
|
||||
{
|
||||
base.OnViewLoaded();
|
||||
InitializeSelection(RecipeManager.CurrentCarrierRecipe?.RecipeName);
|
||||
}
|
||||
|
||||
protected override void OnRecipeLoaded(CarrierRecipe recipe, RecipeWrap recipeWrap)
|
||||
{
|
||||
foreach (var wrap in RecipeWraps)
|
||||
{
|
||||
wrap.IsInUse = ReferenceEquals(wrap, recipeWrap);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnCopyRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
base.OnCopyRecipe(sender, eventArgs);
|
||||
}
|
||||
|
||||
public override void OnImportRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs args)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSaveRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs)
|
||||
{
|
||||
SaveCurrentRecipe(true);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnExportRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs args)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnReNameRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeRenameEventArgs args)
|
||||
{
|
||||
TryRenameRecipe(args);
|
||||
}
|
||||
}
|
||||
|
||||
#region //Methods
|
||||
public void AddSubstrate()
|
||||
{
|
||||
if (CarrierRecipe == null)
|
||||
{
|
||||
MwMessageBox.Show("请先选择或创建载具配方");
|
||||
return;
|
||||
}
|
||||
CarrierRecipe.SubstrateSelectInfos.Add(new SubstrateSelectInfo());
|
||||
}
|
||||
|
||||
public void RemoveSubstrate(object pars)
|
||||
{
|
||||
if (pars is SubstrateSelectInfo selectInfo)
|
||||
{
|
||||
CarrierRecipe.SubstrateSelectInfos.Remove(selectInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(SubstrateNameChangedEventArgs message)
|
||||
{
|
||||
if (CarrierRecipe != null)
|
||||
{
|
||||
if (CarrierRecipe.SubstrateSelectInfos != null)
|
||||
{
|
||||
foreach (var substrateInfo in CarrierRecipe.SubstrateSelectInfos)
|
||||
{
|
||||
if (substrateInfo.SubstrateName == message.OldName)
|
||||
{
|
||||
substrateInfo.SubstrateName = message.NewName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(SubstrateDeletedEventArgs message)
|
||||
{
|
||||
if (CarrierRecipe == null || CarrierRecipe.SubstrateSelectInfos == null)
|
||||
return;
|
||||
|
||||
var list = CarrierRecipe.SubstrateSelectInfos;
|
||||
var targetName = message?.DeletedName;
|
||||
|
||||
CommonUti.RunOnUi(() => RemoveByNameFromCollection(list, targetName));
|
||||
}
|
||||
|
||||
private void RemoveByNameFromCollection(ObservableCollection<SubstrateSelectInfo> list, string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) return;
|
||||
|
||||
for (int i = list.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (list[i]?.SubstrateName == name)
|
||||
list.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using MainShell.Recipe.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public interface IRecipeViewModel
|
||||
{
|
||||
RecipeManager RecipeManager { get; }
|
||||
ObservableCollection<RecipeWrap> RecipeWraps { get; }
|
||||
RecipeWrap SelectedRecipeWrap { get; set; }
|
||||
void SyncSelectionFromActiveRecipe();
|
||||
RecipeButtonGroupViewModel RecipeButtonGroupViewModel { get;}
|
||||
bool HideRecipePanel { get; set; }
|
||||
bool CanCopyRecipe { get; }
|
||||
bool CanImportRecipe { get; }
|
||||
bool CanExportRecipe { get; }
|
||||
bool HasUnsavedChanges { get; }
|
||||
string CurrentRecipeDisplayName { get; }
|
||||
bool TryLeaveCurrentRecipeContext();
|
||||
void SaveCurrentRecipeSilently();
|
||||
void OnCreateNewRecipe(object sender, EventArgs eventArgs);
|
||||
void OnDeleteRecipe(object sender, EventArgs eventArgs);
|
||||
void OnCopyRecipe(object sender, EventArgs eventArgs);
|
||||
void OnApplyRecipe(object sender, EventArgs eventArgs);
|
||||
void OnImportRecipe(object sender, EventArgs eventArgs);
|
||||
void OnSaveRecipe(object sender, EventArgs eventArgs);
|
||||
void OnExportRecipe(object sender, EventArgs eventArgs);
|
||||
void OnReNameRecipe(object sender, EventArgs eventArgs);
|
||||
void OnClearRecipe(object sender, EventArgs eventArgs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using MainShell.Recipe.Models.SubstrateParameter;
|
||||
using MaxwellFramework.Core.Common.Command;
|
||||
using Stylet;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class MarkCoordinatePreviewWindowModel : PropertyChangedBase
|
||||
{
|
||||
private readonly MarkTeachViewModel _markTeachViewModel;
|
||||
private ObservableCollection<MarkCoordinatePoint> _observedPoints;
|
||||
|
||||
public ObservableCollection<MarkCoordinatePoint> Points => _markTeachViewModel?.CoordinatePreviewPoints;
|
||||
|
||||
public int PointCount => Points?.Count ?? 0;
|
||||
|
||||
public ICommand CloseCommand { get; }
|
||||
|
||||
public MarkCoordinatePreviewWindowModel(MarkTeachViewModel markTeachViewModel)
|
||||
{
|
||||
_markTeachViewModel = markTeachViewModel;
|
||||
CloseCommand = new DelegateCommand(OnClose);
|
||||
AttachToMarkTeachViewModel();
|
||||
}
|
||||
|
||||
private void OnClose(object obj)
|
||||
{
|
||||
if (obj is Window window)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshViewState()
|
||||
{
|
||||
NotifyOfPropertyChange(() => Points);
|
||||
NotifyOfPropertyChange(() => PointCount);
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
if (_markTeachViewModel != null)
|
||||
{
|
||||
_markTeachViewModel.PropertyChanged -= OnMarkTeachViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
DetachPointCollection();
|
||||
}
|
||||
|
||||
private void AttachToMarkTeachViewModel()
|
||||
{
|
||||
if (_markTeachViewModel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_markTeachViewModel.PropertyChanged += OnMarkTeachViewModelPropertyChanged;
|
||||
AttachPointCollection(_markTeachViewModel.CoordinatePreviewPoints);
|
||||
}
|
||||
|
||||
private void OnMarkTeachViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(e.PropertyName)
|
||||
|| e.PropertyName == nameof(MarkTeachViewModel.CoordinatePreviewPoints))
|
||||
{
|
||||
DetachPointCollection();
|
||||
AttachPointCollection(_markTeachViewModel.CoordinatePreviewPoints);
|
||||
RefreshViewState();
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachPointCollection(ObservableCollection<MarkCoordinatePoint> points)
|
||||
{
|
||||
_observedPoints = points;
|
||||
if (_observedPoints != null)
|
||||
{
|
||||
_observedPoints.CollectionChanged += OnPointsCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void DetachPointCollection()
|
||||
{
|
||||
if (_observedPoints != null)
|
||||
{
|
||||
_observedPoints.CollectionChanged -= OnPointsCollectionChanged;
|
||||
_observedPoints = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPointsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
NotifyOfPropertyChange(() => PointCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,718 @@
|
||||
using MainShell.Common.ViewModel;
|
||||
using MainShell.Common;
|
||||
using MainShell.Filewritable;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Recipe.Models.SubstrateParameter;
|
||||
using MainShell.Recipe.View;
|
||||
using MaxwellFramework.Core.Common.Command;
|
||||
using Microsoft.Win32;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class MarkTeachViewModel : PropertyChangedBase
|
||||
{
|
||||
private const double PitchTolerance = 0.001d;
|
||||
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly IWindowManager _windowManager;
|
||||
private SubstrateInfo _observedSubstrateInfo;
|
||||
private MarkCoordinatePreviewWindow _coordinatePreviewWindow;
|
||||
private MarkCoordinatePreviewWindowModel _coordinatePreviewWindowModel;
|
||||
|
||||
public MarkTeachViewModel(HardwareManager hardwareManager,IWindowManager windowManager)
|
||||
{
|
||||
_hardwareManager = hardwareManager;
|
||||
_windowManager = windowManager;
|
||||
AddMarkCmd = new DelegateCommand(AddMark);
|
||||
DeleteMarkCmd = new DelegateCommand(DeleteMark);
|
||||
TeachMarkCmd = new DelegateCommand(TeachMark);
|
||||
MoveToMarkCmd = new DelegateCommand(MoveToMark);
|
||||
MarkModelCmd = new DelegateCommand(MarkModel);
|
||||
VisionParSetCmd = new DelegateCommand(VisionParSet);
|
||||
GenerateCoordinatesCmd = new DelegateCommand(GenerateCoordinates);
|
||||
ImportCoordinatesCmd = new DelegateCommand(ImportCoordinates);
|
||||
ClearCoordinatesCmd = new DelegateCommand(ClearCoordinates);
|
||||
ShowCoordinatePreviewCmd = new DelegateCommand(ShowCoordinatePreview);
|
||||
RefreshTemplates();
|
||||
}
|
||||
|
||||
public ObservableCollection<string> AvailableTemplates { get; set; } = new ObservableCollection<string>();
|
||||
|
||||
private void RefreshTemplates()
|
||||
{
|
||||
AvailableTemplates.Clear();
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(Paths.TemplateBasePath))
|
||||
{
|
||||
var files = Directory.GetFiles(Paths.TemplateBasePath);
|
||||
foreach (var file in files)
|
||||
{
|
||||
AvailableTemplates.Add(Path.GetFileName(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Handle or log exception
|
||||
}
|
||||
}
|
||||
|
||||
private SubstrateRecipe _substrateRecipe;
|
||||
public SubstrateRecipe SubstrateRecipe
|
||||
{
|
||||
get => _substrateRecipe;
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _substrateRecipe, value))
|
||||
{
|
||||
EnsureCoordinateGenerationState();
|
||||
ObserveSubstrateInfo(value?.SubstrateInfo);
|
||||
NotifyOfPropertyChange(() => MarkPars);
|
||||
NotifyOfPropertyChange(() => MarkDatas);
|
||||
NotifyOfPropertyChange(() => CoordinateGenerationState);
|
||||
NotifyOfPropertyChange(() => CoordinatePreviewPoints);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SubtrateMarkPars MarkPars => SubstrateRecipe?.SubtrateMarkParameterInfo;
|
||||
public ObservableCollection<MarkData> MarkDatas => MarkPars?.MarkDatas;
|
||||
public MarkCoordinateGenerationState CoordinateGenerationState => MarkPars?.CoordinateGenerationState;
|
||||
public ObservableCollection<MarkCoordinatePoint> CoordinatePreviewPoints => CoordinateGenerationState?.Points;
|
||||
|
||||
private MarkData _selectedMarkData;
|
||||
public MarkData SelectedMarkData
|
||||
{
|
||||
get => _selectedMarkData;
|
||||
set => SetAndNotify(ref _selectedMarkData, value);
|
||||
}
|
||||
private bool _isTeachPose;
|
||||
|
||||
public bool IsTeachPose
|
||||
{
|
||||
get { return _isTeachPose; }
|
||||
set { SetAndNotify(ref _isTeachPose, value); }
|
||||
}
|
||||
|
||||
#region Commands
|
||||
|
||||
public ICommand AddMarkCmd { get; }
|
||||
public ICommand DeleteMarkCmd { get; }
|
||||
public ICommand TeachMarkCmd { get; }
|
||||
public ICommand MoveToMarkCmd { get; }
|
||||
public ICommand MarkModelCmd { get; }
|
||||
public ICommand VisionParSetCmd { get; }
|
||||
public ICommand GenerateCoordinatesCmd { get; }
|
||||
public ICommand ImportCoordinatesCmd { get; }
|
||||
public ICommand ClearCoordinatesCmd { get; }
|
||||
public ICommand ShowCoordinatePreviewCmd { get; }
|
||||
|
||||
private void ObserveSubstrateInfo(SubstrateInfo substrateInfo)
|
||||
{
|
||||
if (_observedSubstrateInfo != null)
|
||||
{
|
||||
_observedSubstrateInfo.PropertyChanged -= OnSubstrateInfoChanged;
|
||||
}
|
||||
|
||||
_observedSubstrateInfo = substrateInfo;
|
||||
|
||||
if (_observedSubstrateInfo != null)
|
||||
{
|
||||
_observedSubstrateInfo.PropertyChanged += OnSubstrateInfoChanged;
|
||||
}
|
||||
|
||||
SyncCoordinateGenerationSource();
|
||||
}
|
||||
|
||||
private void OnSubstrateInfoChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(e.PropertyName)
|
||||
|| e.PropertyName == nameof(SubstrateInfo.RowNumber)
|
||||
|| e.PropertyName == nameof(SubstrateInfo.ColNumber)
|
||||
|| e.PropertyName == nameof(SubstrateInfo.PitchX)
|
||||
|| e.PropertyName == nameof(SubstrateInfo.PitchY))
|
||||
{
|
||||
SyncCoordinateGenerationSource();
|
||||
}
|
||||
}
|
||||
|
||||
private bool EnsureCoordinateGenerationState()
|
||||
{
|
||||
if (SubstrateRecipe == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SubstrateRecipe.SubtrateMarkParameterInfo == null)
|
||||
{
|
||||
SubstrateRecipe.SubtrateMarkParameterInfo = new SubtrateMarkPars();
|
||||
}
|
||||
|
||||
if (SubstrateRecipe.SubtrateMarkParameterInfo.CoordinateGenerationState == null)
|
||||
{
|
||||
SubstrateRecipe.SubtrateMarkParameterInfo.CoordinateGenerationState = new MarkCoordinateGenerationState();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SyncCoordinateGenerationSource()
|
||||
{
|
||||
if (!EnsureCoordinateGenerationState())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var substrateInfo = SubstrateRecipe?.SubstrateInfo;
|
||||
var coordinateState = CoordinateGenerationState;
|
||||
if (substrateInfo == null || coordinateState == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
coordinateState.Rows = substrateInfo.RowNumber;
|
||||
coordinateState.Cols = substrateInfo.ColNumber;
|
||||
coordinateState.PitchX = substrateInfo.PitchX;
|
||||
coordinateState.PitchY = substrateInfo.PitchY;
|
||||
|
||||
NotifyOfPropertyChange(() => CoordinateGenerationState);
|
||||
NotifyOfPropertyChange(() => CoordinatePreviewPoints);
|
||||
}
|
||||
|
||||
private bool EnsureRecipeSelected()
|
||||
{
|
||||
if (SubstrateRecipe != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
MwMessageBox.Show("请先选择或创建基板配方", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void AddMark(object obj)
|
||||
{
|
||||
if (MarkDatas == null) return;
|
||||
var newMark = new MarkData
|
||||
{
|
||||
TemplateName = $"Mark{MarkDatas.Count + 1}",
|
||||
BasePos = new MPoint(),
|
||||
CameraPos = new MPoint()
|
||||
};
|
||||
MarkDatas.Add(newMark);
|
||||
SelectedMarkData = newMark;
|
||||
}
|
||||
|
||||
private void DeleteMark(object obj)
|
||||
{
|
||||
if (SelectedMarkData != null && MarkDatas != null)
|
||||
{
|
||||
MarkDatas.Remove(SelectedMarkData);
|
||||
}
|
||||
}
|
||||
|
||||
private void TeachMark(object obj)
|
||||
{
|
||||
if (SelectedMarkData == null) return;
|
||||
|
||||
var device = _hardwareManager.CameraAxisManager.TopCameraAxisDevices.FirstOrDefault();
|
||||
if (device != null)
|
||||
{
|
||||
if (SelectedMarkData.BasePos == null) SelectedMarkData.BasePos = new MPoint();
|
||||
if (SelectedMarkData.CameraPos == null) SelectedMarkData.CameraPos = new MPoint();
|
||||
|
||||
if (device.AxisX != null) SelectedMarkData.BasePos.X = device.AxisX.State.ActualPos;
|
||||
if (device.AxisY != null) SelectedMarkData.BasePos.Y = device.AxisY.State.ActualPos;
|
||||
|
||||
if (device.AxisX != null) SelectedMarkData.CameraPos.X = device.AxisX.State.ActualPos;
|
||||
if (device.AxisY != null) SelectedMarkData.CameraPos.Y = device.AxisY.State.ActualPos;
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveToMark(object obj)
|
||||
{
|
||||
if (SelectedMarkData == null) return;
|
||||
var device = _hardwareManager.CameraAxisManager.TopCameraAxisDevices.FirstOrDefault();
|
||||
if (device != null)
|
||||
{
|
||||
// TODO: Implement Safe Move logic using AxisMotion or similar
|
||||
}
|
||||
}
|
||||
|
||||
private void MarkModel(object obj)
|
||||
{
|
||||
// TODO: Open Mark Model Dialog
|
||||
// Refresh templates after returning from model dialog as new templates might have been created
|
||||
RefreshTemplates();
|
||||
}
|
||||
|
||||
private void VisionParSet(object obj)
|
||||
{
|
||||
// TODO: Open Vision Parameter Settings
|
||||
CameraSettingsViewModel settingsViewModel=new CameraSettingsViewModel(_windowManager,_hardwareManager);
|
||||
settingsViewModel.Initialize(_substrateRecipe.SubtrateMarkParameterInfo.MarkVisionConfig,
|
||||
_substrateRecipe.SubtrateMarkParameterInfo.MarkLightConfig, Common.CameraType.TopPositionCamera);
|
||||
_windowManager.ShowDialog(settingsViewModel);
|
||||
}
|
||||
|
||||
private void GenerateCoordinates(object obj)
|
||||
{
|
||||
if (!EnsureRecipeSelected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SyncCoordinateGenerationSource();
|
||||
var coordinateState = CoordinateGenerationState;
|
||||
if (coordinateState == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (coordinateState.Rows <= 0 || coordinateState.Cols <= 0)
|
||||
{
|
||||
MwMessageBox.Show("基板信息中的行列数必须大于 0。", "生成失败", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (coordinateState.Cols > 1 && Math.Abs(coordinateState.PitchX) <= double.Epsilon)
|
||||
{
|
||||
MwMessageBox.Show("基板信息中的 Pitch X 无效,无法生成点位。", "生成失败", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (coordinateState.Rows > 1 && Math.Abs(coordinateState.PitchY) <= double.Epsilon)
|
||||
{
|
||||
MwMessageBox.Show("基板信息中的 Pitch Y 无效,无法生成点位。", "生成失败", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var generatedPoints = new ObservableCollection<MarkCoordinatePoint>();
|
||||
var index = 1;
|
||||
for (var row = 1; row <= coordinateState.Rows; row++)
|
||||
{
|
||||
for (var col = 1; col <= coordinateState.Cols; col++)
|
||||
{
|
||||
generatedPoints.Add(new MarkCoordinatePoint
|
||||
{
|
||||
Index = index++,
|
||||
PointName = CreatePointName(row, col),
|
||||
Row = row,
|
||||
Col = col,
|
||||
TheoryX = (col - 1) * coordinateState.PitchX,
|
||||
TheoryY = (row - 1) * coordinateState.PitchY,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
coordinateState.Points = generatedPoints;
|
||||
NotifyOfPropertyChange(() => CoordinatePreviewPoints);
|
||||
}
|
||||
|
||||
private void ImportCoordinates(object obj)
|
||||
{
|
||||
if (!EnsureRecipeSelected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
Title = "导入坐标文件",
|
||||
Filter = "CSV Files (*.csv)|*.csv|All Files (*.*)|*.*",
|
||||
CheckFileExists = true,
|
||||
Multiselect = false,
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<MarkCoordinatePoint> importedPoints;
|
||||
string errorMessage;
|
||||
if (!TryReadCoordinateFile(dialog.FileName, out importedPoints, out errorMessage))
|
||||
{
|
||||
MwMessageBox.Show(errorMessage, "导入失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (importedPoints.Count == 0)
|
||||
{
|
||||
MwMessageBox.Show("导入文件中没有可用的坐标数据。", "导入失败", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
SyncCoordinateGenerationSource();
|
||||
var validationMessage = BuildImportValidationMessage(importedPoints);
|
||||
if (!string.IsNullOrWhiteSpace(validationMessage))
|
||||
{
|
||||
var confirmResult = MwMessageBox.Show(
|
||||
validationMessage + Environment.NewLine + Environment.NewLine + "是否继续导入?",
|
||||
"导入校验",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Warning,
|
||||
MessageBoxResult.No);
|
||||
|
||||
if (confirmResult != MessageBoxResult.Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CoordinateGenerationState.Points = new ObservableCollection<MarkCoordinatePoint>(importedPoints);
|
||||
NotifyOfPropertyChange(() => CoordinatePreviewPoints);
|
||||
}
|
||||
|
||||
private void ClearCoordinates(object obj)
|
||||
{
|
||||
if (!EnsureRecipeSelected() || CoordinateGenerationState == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CoordinateGenerationState.Points.Clear();
|
||||
NotifyOfPropertyChange(() => CoordinatePreviewPoints);
|
||||
}
|
||||
|
||||
private void ShowCoordinatePreview(object obj)
|
||||
{
|
||||
if (!EnsureRecipeSelected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_coordinatePreviewWindow != null)
|
||||
{
|
||||
_coordinatePreviewWindowModel?.RefreshViewState();
|
||||
|
||||
if (_coordinatePreviewWindow.WindowState == WindowState.Minimized)
|
||||
{
|
||||
_coordinatePreviewWindow.WindowState = WindowState.Normal;
|
||||
}
|
||||
|
||||
_coordinatePreviewWindow.Activate();
|
||||
_coordinatePreviewWindow.Focus();
|
||||
return;
|
||||
}
|
||||
|
||||
_coordinatePreviewWindowModel = new MarkCoordinatePreviewWindowModel(this);
|
||||
_coordinatePreviewWindow = new MarkCoordinatePreviewWindow
|
||||
{
|
||||
DataContext = _coordinatePreviewWindowModel
|
||||
};
|
||||
|
||||
var ownerWindow = Application.Current?.Windows
|
||||
.OfType<Window>()
|
||||
.FirstOrDefault(window => window.IsActive && window != _coordinatePreviewWindow);
|
||||
if (ownerWindow != null)
|
||||
{
|
||||
_coordinatePreviewWindow.Owner = ownerWindow;
|
||||
}
|
||||
|
||||
_coordinatePreviewWindow.Closed += OnCoordinatePreviewWindowClosed;
|
||||
_coordinatePreviewWindow.Show();
|
||||
_coordinatePreviewWindow.Activate();
|
||||
}
|
||||
|
||||
private void OnCoordinatePreviewWindowClosed(object sender, EventArgs e)
|
||||
{
|
||||
if (_coordinatePreviewWindow != null)
|
||||
{
|
||||
_coordinatePreviewWindow.Closed -= OnCoordinatePreviewWindowClosed;
|
||||
}
|
||||
|
||||
_coordinatePreviewWindow = null;
|
||||
_coordinatePreviewWindowModel = null;
|
||||
}
|
||||
|
||||
private string BuildImportValidationMessage(IReadOnlyList<MarkCoordinatePoint> importedPoints)
|
||||
{
|
||||
var coordinateState = CoordinateGenerationState;
|
||||
if (coordinateState == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var importedRows = importedPoints.Select(point => point.Row).Distinct().Count();
|
||||
var importedCols = importedPoints.Select(point => point.Col).Distinct().Count();
|
||||
var importedPitchX = CalculatePitch(importedPoints, true);
|
||||
var importedPitchY = CalculatePitch(importedPoints, false);
|
||||
|
||||
var mismatchMessages = new List<string>();
|
||||
if (coordinateState.Rows > 0 && importedRows != coordinateState.Rows)
|
||||
{
|
||||
mismatchMessages.Add($"行数不匹配:基板信息为 {coordinateState.Rows},导入数据为 {importedRows}。");
|
||||
}
|
||||
|
||||
if (coordinateState.Cols > 0 && importedCols != coordinateState.Cols)
|
||||
{
|
||||
mismatchMessages.Add($"列数不匹配:基板信息为 {coordinateState.Cols},导入数据为 {importedCols}。");
|
||||
}
|
||||
|
||||
if (coordinateState.Cols > 1 && importedPitchX.HasValue && Math.Abs(importedPitchX.Value - coordinateState.PitchX) > PitchTolerance)
|
||||
{
|
||||
mismatchMessages.Add($"Pitch X 不匹配:基板信息为 {coordinateState.PitchX:F4},导入数据为 {importedPitchX.Value:F4}。");
|
||||
}
|
||||
|
||||
if (coordinateState.Rows > 1 && importedPitchY.HasValue && Math.Abs(importedPitchY.Value - coordinateState.PitchY) > PitchTolerance)
|
||||
{
|
||||
mismatchMessages.Add($"Pitch Y 不匹配:基板信息为 {coordinateState.PitchY:F4},导入数据为 {importedPitchY.Value:F4}。");
|
||||
}
|
||||
|
||||
return mismatchMessages.Count == 0 ? string.Empty : string.Join(Environment.NewLine, mismatchMessages);
|
||||
}
|
||||
|
||||
private static double? CalculatePitch(IReadOnlyList<MarkCoordinatePoint> points, bool isXAxis)
|
||||
{
|
||||
var pitchSamples = new List<double>();
|
||||
if (points == null || points.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isXAxis)
|
||||
{
|
||||
foreach (var rowGroup in points.GroupBy(point => point.Row))
|
||||
{
|
||||
var ordered = rowGroup.OrderBy(point => point.Col).ToList();
|
||||
for (var i = 1; i < ordered.Count; i++)
|
||||
{
|
||||
var colDelta = ordered[i].Col - ordered[i - 1].Col;
|
||||
if (colDelta <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var pitch = Math.Abs((ordered[i].TheoryX - ordered[i - 1].TheoryX) / colDelta);
|
||||
if (pitch > double.Epsilon)
|
||||
{
|
||||
pitchSamples.Add(pitch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var colGroup in points.GroupBy(point => point.Col))
|
||||
{
|
||||
var ordered = colGroup.OrderBy(point => point.Row).ToList();
|
||||
for (var i = 1; i < ordered.Count; i++)
|
||||
{
|
||||
var rowDelta = ordered[i].Row - ordered[i - 1].Row;
|
||||
if (rowDelta <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var pitch = Math.Abs((ordered[i].TheoryY - ordered[i - 1].TheoryY) / rowDelta);
|
||||
if (pitch > double.Epsilon)
|
||||
{
|
||||
pitchSamples.Add(pitch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pitchSamples.Count == 0 ? (double?)null : pitchSamples.Average();
|
||||
}
|
||||
|
||||
private static string CreatePointName(int row, int col)
|
||||
{
|
||||
return $"R{row:D2}C{col:D2}";
|
||||
}
|
||||
|
||||
private static bool TryReadCoordinateFile(string filePath, out List<MarkCoordinatePoint> importedPoints, out string errorMessage)
|
||||
{
|
||||
importedPoints = new List<MarkCoordinatePoint>();
|
||||
errorMessage = string.Empty;
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
errorMessage = "坐标文件不存在。";
|
||||
return false;
|
||||
}
|
||||
|
||||
var rawLines = File.ReadAllLines(filePath, Encoding.UTF8)
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line))
|
||||
.ToList();
|
||||
|
||||
if (rawLines.Count == 0)
|
||||
{
|
||||
errorMessage = "坐标文件为空。";
|
||||
return false;
|
||||
}
|
||||
|
||||
var headers = SplitCsvLine(rawLines[0]);
|
||||
var nameIndex = FindColumnIndex(headers, "pointname", "name", "点位名称", "点位名", "point");
|
||||
var rowIndex = FindColumnIndex(headers, "row", "行");
|
||||
var colIndex = FindColumnIndex(headers, "col", "column", "列");
|
||||
var xIndex = FindColumnIndex(headers, "theoryx", "x", "理论x");
|
||||
var yIndex = FindColumnIndex(headers, "theoryy", "y", "理论y");
|
||||
|
||||
if (rowIndex < 0 || colIndex < 0 || xIndex < 0 || yIndex < 0)
|
||||
{
|
||||
errorMessage = "CSV 需至少包含 Row、Col、TheoryX、TheoryY 列。";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var lineIndex = 1; lineIndex < rawLines.Count; lineIndex++)
|
||||
{
|
||||
var cells = SplitCsvLine(rawLines[lineIndex]);
|
||||
if (cells.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int row;
|
||||
int col;
|
||||
double theoryX;
|
||||
double theoryY;
|
||||
if (!TryParseInt(GetCell(cells, rowIndex), out row)
|
||||
|| !TryParseInt(GetCell(cells, colIndex), out col)
|
||||
|| !TryParseDouble(GetCell(cells, xIndex), out theoryX)
|
||||
|| !TryParseDouble(GetCell(cells, yIndex), out theoryY))
|
||||
{
|
||||
errorMessage = $"第 {lineIndex + 1} 行数据格式无效,请检查 Row、Col、TheoryX、TheoryY。";
|
||||
return false;
|
||||
}
|
||||
|
||||
var pointName = nameIndex >= 0 ? GetCell(cells, nameIndex) : string.Empty;
|
||||
importedPoints.Add(new MarkCoordinatePoint
|
||||
{
|
||||
PointName = string.IsNullOrWhiteSpace(pointName) ? CreatePointName(row, col) : pointName,
|
||||
Row = row,
|
||||
Col = col,
|
||||
TheoryX = theoryX,
|
||||
TheoryY = theoryY,
|
||||
});
|
||||
}
|
||||
|
||||
importedPoints = importedPoints
|
||||
.OrderBy(point => point.Row)
|
||||
.ThenBy(point => point.Col)
|
||||
.ToList();
|
||||
|
||||
for (var i = 0; i < importedPoints.Count; i++)
|
||||
{
|
||||
importedPoints[i].Index = i + 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int FindColumnIndex(IReadOnlyList<string> headers, params string[] candidates)
|
||||
{
|
||||
for (var index = 0; index < headers.Count; index++)
|
||||
{
|
||||
var normalizedHeader = NormalizeHeader(headers[index]);
|
||||
if (candidates.Any(candidate => normalizedHeader == NormalizeHeader(candidate)))
|
||||
{
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static string NormalizeHeader(string value)
|
||||
{
|
||||
return (value ?? string.Empty)
|
||||
.Trim()
|
||||
.Trim('\uFEFF')
|
||||
.Replace(" ", string.Empty)
|
||||
.Replace("_", string.Empty)
|
||||
.Replace("-", string.Empty)
|
||||
.ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static string GetCell(IReadOnlyList<string> cells, int index)
|
||||
{
|
||||
if (index < 0 || index >= cells.Count)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return cells[index]?.Trim() ?? string.Empty;
|
||||
}
|
||||
|
||||
private static bool TryParseInt(string value, out int result)
|
||||
{
|
||||
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return int.TryParse(value, NumberStyles.Integer, CultureInfo.CurrentCulture, out result);
|
||||
}
|
||||
|
||||
private static bool TryParseDouble(string value, out double result)
|
||||
{
|
||||
if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.CurrentCulture, out result);
|
||||
}
|
||||
|
||||
private static List<string> SplitCsvLine(string line)
|
||||
{
|
||||
var cells = new List<string>();
|
||||
if (line == null)
|
||||
{
|
||||
return cells;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
var inQuotes = false;
|
||||
for (var i = 0; i < line.Length; i++)
|
||||
{
|
||||
var current = line[i];
|
||||
if (current == '"')
|
||||
{
|
||||
if (inQuotes && i + 1 < line.Length && line[i + 1] == '"')
|
||||
{
|
||||
builder.Append('"');
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
inQuotes = !inQuotes;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current == ',' && !inQuotes)
|
||||
{
|
||||
cells.Add(builder.ToString());
|
||||
builder.Clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append(current);
|
||||
}
|
||||
|
||||
cells.Add(builder.ToString());
|
||||
return cells;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using MainShell.Recipe.Models;
|
||||
using Stylet;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class ProcessCompensationViewModel : PropertyChangedBase
|
||||
{
|
||||
private ProcessRecipe _processRecipe;
|
||||
public ProcessRecipe ProcessRecipe
|
||||
{
|
||||
get { return _processRecipe; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _processRecipe, value))
|
||||
{
|
||||
NotifyCompensationPropertiesChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double PhsX1Compensation
|
||||
{
|
||||
get { return GetCompensationParameter().PhsX1Compensation; }
|
||||
set
|
||||
{
|
||||
ProcessAxisCompensationParameter parameter = GetCompensationParameter();
|
||||
if (parameter.PhsX1Compensation == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
parameter.PhsX1Compensation = value;
|
||||
NotifyOfPropertyChange(nameof(PhsX1Compensation));
|
||||
}
|
||||
}
|
||||
|
||||
public double PhsY1Compensation
|
||||
{
|
||||
get { return GetCompensationParameter().PhsY1Compensation; }
|
||||
set
|
||||
{
|
||||
ProcessAxisCompensationParameter parameter = GetCompensationParameter();
|
||||
if (parameter.PhsY1Compensation == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
parameter.PhsY1Compensation = value;
|
||||
NotifyOfPropertyChange(nameof(PhsY1Compensation));
|
||||
}
|
||||
}
|
||||
|
||||
public double WsXCompensation
|
||||
{
|
||||
get { return GetCompensationParameter().WsXCompensation; }
|
||||
set
|
||||
{
|
||||
ProcessAxisCompensationParameter parameter = GetCompensationParameter();
|
||||
if (parameter.WsXCompensation == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
parameter.WsXCompensation = value;
|
||||
NotifyOfPropertyChange(nameof(WsXCompensation));
|
||||
}
|
||||
}
|
||||
|
||||
public double StageYCompensation
|
||||
{
|
||||
get { return GetCompensationParameter().StageYCompensation; }
|
||||
set
|
||||
{
|
||||
ProcessAxisCompensationParameter parameter = GetCompensationParameter();
|
||||
if (parameter.StageYCompensation == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
parameter.StageYCompensation = value;
|
||||
NotifyOfPropertyChange(nameof(StageYCompensation));
|
||||
}
|
||||
}
|
||||
|
||||
private ProcessAxisCompensationParameter GetCompensationParameter()
|
||||
{
|
||||
if (ProcessRecipe == null)
|
||||
{
|
||||
return new ProcessAxisCompensationParameter();
|
||||
}
|
||||
|
||||
if (ProcessRecipe.ProcessAxisCompensationParameter == null)
|
||||
{
|
||||
ProcessRecipe.ProcessAxisCompensationParameter = new ProcessAxisCompensationParameter();
|
||||
}
|
||||
|
||||
return ProcessRecipe.ProcessAxisCompensationParameter;
|
||||
}
|
||||
|
||||
private void NotifyCompensationPropertiesChanged()
|
||||
{
|
||||
NotifyOfPropertyChange(nameof(PhsX1Compensation));
|
||||
NotifyOfPropertyChange(nameof(PhsY1Compensation));
|
||||
NotifyOfPropertyChange(nameof(WsXCompensation));
|
||||
NotifyOfPropertyChange(nameof(StageYCompensation));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,684 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Filewritable;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Log;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Recipe.Models.PID;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using StyletIoC;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class ProcessRecipeViewModel : RecipeViewModelBase<ProcessRecipe>
|
||||
{
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly PIDOperater _pidOperater;
|
||||
|
||||
private static readonly string[] _fixedProfileNames = new string[]
|
||||
{
|
||||
"默认PID组"
|
||||
};
|
||||
|
||||
private static readonly string[] _fixedCompensationAxisNames = new string[]
|
||||
{
|
||||
"PHS-X1",
|
||||
"PHS-Y1",
|
||||
"WS-X",
|
||||
"Stage-Y"
|
||||
};
|
||||
|
||||
private ProcessRecipe _processRecipe;
|
||||
public ProcessRecipe ProcessRecipe
|
||||
{
|
||||
get { return _processRecipe; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _processRecipe, value))
|
||||
{
|
||||
if (ProcessCompensationViewModel != null)
|
||||
{
|
||||
ProcessCompensationViewModel.ProcessRecipe = value;
|
||||
}
|
||||
|
||||
EnsureProcessRecipeState();
|
||||
SyncProfileSelection();
|
||||
NotifyOfPropertyChange(() => AxisPIDParameters);
|
||||
NotifyOfPropertyChange(() => AxisOptions);
|
||||
NotifyOfPropertyChange(() => SelectedAxes);
|
||||
NotifyOfPropertyChange(() => SelectedFilteringParameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override ProcessRecipe CurrentRecipe
|
||||
{
|
||||
get { return ProcessRecipe; }
|
||||
set { ProcessRecipe = value; }
|
||||
}
|
||||
|
||||
protected override string RecipeFolderPath => Paths.ProcessRecipe;
|
||||
protected override string EmptyRecipeDisplayName => "当前工艺配方";
|
||||
|
||||
protected override string GetActiveRecipeName()
|
||||
{
|
||||
return RecipeManager.CurrentProcessRecipe != null ? RecipeManager.CurrentProcessRecipe.RecipeName : null;
|
||||
}
|
||||
|
||||
public ObservableCollection<PIDProfile> AxisPIDParameters => ProcessRecipe != null ? ProcessRecipe.AxisPIDParameters : null;
|
||||
|
||||
private PIDProfile _selectedPIDProfile;
|
||||
public PIDProfile SelectedPIDProfile
|
||||
{
|
||||
get { return _selectedPIDProfile; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _selectedPIDProfile, value))
|
||||
{
|
||||
EnsureProfileState(_selectedPIDProfile);
|
||||
NotifyOfPropertyChange(() => SelectedAxes);
|
||||
NotifyOfPropertyChange(() => SelectedFilteringParameters);
|
||||
SyncFilteringSelection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<AxisPIDParameter> SelectedAxes => SelectedPIDProfile != null ? SelectedPIDProfile.Axes : null;
|
||||
|
||||
public ObservableCollection<AxisFilteringParameter> SelectedFilteringParameters => SelectedPIDProfile != null ? SelectedPIDProfile.FilteringParameters : null;
|
||||
|
||||
private ObservableCollection<PidAxisOption> _axisOptions = new ObservableCollection<PidAxisOption>();
|
||||
public ObservableCollection<PidAxisOption> AxisOptions
|
||||
{
|
||||
get { return _axisOptions; }
|
||||
set { SetAndNotify(ref _axisOptions, value); }
|
||||
}
|
||||
|
||||
private AxisPIDParameter _selectedAxisParameter;
|
||||
public AxisPIDParameter SelectedAxisParameter
|
||||
{
|
||||
get { return _selectedAxisParameter; }
|
||||
set { SetAndNotify(ref _selectedAxisParameter, value); }
|
||||
}
|
||||
|
||||
private AxisFilteringParameter _selectedFilteringParameter;
|
||||
public AxisFilteringParameter SelectedFilteringParameter
|
||||
{
|
||||
get { return _selectedFilteringParameter; }
|
||||
set { SetAndNotify(ref _selectedFilteringParameter, value); }
|
||||
}
|
||||
|
||||
private AxisFilteringParameter _selectedNotchFilteringParameter;
|
||||
public AxisFilteringParameter SelectedNotchFilteringParameter
|
||||
{
|
||||
get { return _selectedNotchFilteringParameter; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _selectedNotchFilteringParameter, value) && value != null)
|
||||
{
|
||||
SelectedFilteringParameter = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AxisFilteringParameter _selectedLowPassFilteringParameter;
|
||||
public AxisFilteringParameter SelectedLowPassFilteringParameter
|
||||
{
|
||||
get { return _selectedLowPassFilteringParameter; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _selectedLowPassFilteringParameter, value) && value != null)
|
||||
{
|
||||
SelectedFilteringParameter = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _selectedParameterTabIndex;
|
||||
public int SelectedParameterTabIndex
|
||||
{
|
||||
get { return _selectedParameterTabIndex; }
|
||||
set { SetAndNotify(ref _selectedParameterTabIndex, value); }
|
||||
}
|
||||
|
||||
private ProcessCompensationViewModel _processCompensationViewModel;
|
||||
[Inject]
|
||||
public ProcessCompensationViewModel ProcessCompensationViewModel
|
||||
{
|
||||
get { return _processCompensationViewModel; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _processCompensationViewModel, value) && value != null)
|
||||
{
|
||||
value.ProcessRecipe = ProcessRecipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ProcessRecipeViewModel(RecipeWrapManager wrapManager, RecipeManager recipeManager, HardwareManager hardwareManager, PIDOperater pidOperater)
|
||||
: base(recipeManager)
|
||||
{
|
||||
_hardwareManager = hardwareManager;
|
||||
_pidOperater = pidOperater;
|
||||
RecipeWraps = wrapManager.ProcessRecipeWraps;
|
||||
RegisterButtonGroupEvents();
|
||||
AxisOptions = BuildAxisOptions();
|
||||
PidAxisCatalog.SetAxisOptions(AxisOptions);
|
||||
}
|
||||
|
||||
protected override void OnViewLoaded()
|
||||
{
|
||||
base.OnViewLoaded();
|
||||
|
||||
AxisOptions = BuildAxisOptions();
|
||||
PidAxisCatalog.SetAxisOptions(AxisOptions);
|
||||
InitializeSelection(RecipeManager.CurrentProcessRecipe != null ? RecipeManager.CurrentProcessRecipe.RecipeName : null);
|
||||
}
|
||||
|
||||
protected override void SwitchActiveRecipe(string recipeName)
|
||||
{
|
||||
RecipeManager.SwitchProcessRecipe(recipeName);
|
||||
}
|
||||
|
||||
protected override void ClearActiveRecipe()
|
||||
{
|
||||
RecipeManager.ClearProcessRecipe();
|
||||
}
|
||||
|
||||
protected override void OnRecipeLoaded(ProcessRecipe recipe, RecipeWrap recipeWrap)
|
||||
{
|
||||
EnsureProcessRecipeState();
|
||||
SyncProfileSelection();
|
||||
}
|
||||
|
||||
public override void OnCopyRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
base.OnCopyRecipe(sender, eventArgs);
|
||||
}
|
||||
|
||||
public override void OnImportRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnExportRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnReNameRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
RecipeRenameEventArgs args = eventArgs as RecipeRenameEventArgs;
|
||||
if (args != null)
|
||||
{
|
||||
TryRenameRecipe(args);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddAxis()
|
||||
{
|
||||
if (SelectedPIDProfile == null)
|
||||
{
|
||||
MwMessageBox.Show("请先选择一个 PID 配置组");
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureProfileState(SelectedPIDProfile);
|
||||
|
||||
PidAxisOption axisOption = GetNextAxisOption(SelectedPIDProfile.Axes.Select(item => item.AxisIndex));
|
||||
AxisPIDParameter axis = new AxisPIDParameter();
|
||||
if (axisOption != null)
|
||||
{
|
||||
axis.ModuleName = axisOption.AxisName;
|
||||
}
|
||||
else
|
||||
{
|
||||
axis.ModuleName = string.Format("Axis{0}", SelectedPIDProfile.Axes.Count + 1);
|
||||
axis.AxisIndex = SelectedPIDProfile.Axes.Count;
|
||||
}
|
||||
|
||||
SelectedPIDProfile.Axes.Add(axis);
|
||||
SelectedAxisParameter = axis;
|
||||
NotifyOfPropertyChange(() => SelectedAxes);
|
||||
NotifyOfPropertyChange(() => HasUnsavedChanges);
|
||||
}
|
||||
|
||||
public void DeleteAxis()
|
||||
{
|
||||
if (SelectedPIDProfile == null || SelectedAxisParameter == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureProfileState(SelectedPIDProfile);
|
||||
|
||||
int index = SelectedPIDProfile.Axes.IndexOf(SelectedAxisParameter);
|
||||
SelectedPIDProfile.Axes.Remove(SelectedAxisParameter);
|
||||
SelectedAxisParameter = SelectedPIDProfile.Axes.Count == 0
|
||||
? null
|
||||
: SelectedPIDProfile.Axes[Math.Min(index, SelectedPIDProfile.Axes.Count - 1)];
|
||||
NotifyOfPropertyChange(() => SelectedAxes);
|
||||
NotifyOfPropertyChange(() => HasUnsavedChanges);
|
||||
}
|
||||
|
||||
public void AddFilteringParameter()
|
||||
{
|
||||
if (SelectedPIDProfile == null)
|
||||
{
|
||||
MwMessageBox.Show("请先选择一个 PID 配置组");
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureProfileState(SelectedPIDProfile);
|
||||
|
||||
PidAxisOption axisOption = GetNextAxisOption(SelectedPIDProfile.FilteringParameters.Select(item => item.AxisIndex));
|
||||
AxisFilteringParameter filteringParameter = new AxisFilteringParameter
|
||||
{
|
||||
AxisIndex = axisOption != null ? axisOption.AxisIndex : GetDefaultAxisIndex()
|
||||
};
|
||||
|
||||
SelectedPIDProfile.FilteringParameters.Add(filteringParameter);
|
||||
SelectedFilteringParameter = filteringParameter;
|
||||
NotifyOfPropertyChange(() => SelectedFilteringParameters);
|
||||
NotifyOfPropertyChange(() => HasUnsavedChanges);
|
||||
}
|
||||
|
||||
public void ReadParameters()
|
||||
{
|
||||
if (SelectedPIDProfile == null)
|
||||
{
|
||||
LocalizedMessageBox.Show(MessageKey.PidNoProfileSelected, MessageKey.TitleWarning);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (SelectedParameterTabIndex == 1)
|
||||
{
|
||||
ReadFilteringParameters();
|
||||
}
|
||||
else
|
||||
{
|
||||
ReadPidAxisParameters();
|
||||
}
|
||||
|
||||
NotifyOfPropertyChange(() => SelectedAxes);
|
||||
NotifyOfPropertyChange(() => SelectedFilteringParameters);
|
||||
NotifyOfPropertyChange(() => HasUnsavedChanges);
|
||||
LocalizedMessageBox.Show(MessageKey.PidReadSucceeded, MessageKey.TitleInfo);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
$"PID参数读取失败: {exception}".LogSysError();
|
||||
LocalizedMessageBox.Show(MessageKey.PidReadFailed, MessageKey.TitleError);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteParameters()
|
||||
{
|
||||
if (SelectedPIDProfile == null)
|
||||
{
|
||||
LocalizedMessageBox.Show(MessageKey.PidNoProfileSelected, MessageKey.TitleWarning);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (SelectedParameterTabIndex == 1)
|
||||
{
|
||||
WriteFilteringParameters();
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePidAxisParameters();
|
||||
}
|
||||
|
||||
LocalizedMessageBox.Show(MessageKey.PidWriteSucceeded, MessageKey.TitleInfo);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
$"PID参数写入失败: {exception}".LogSysError();
|
||||
LocalizedMessageBox.Show(MessageKey.PidWriteFailed, MessageKey.TitleError);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteFilteringParameter()
|
||||
{
|
||||
if (SelectedPIDProfile == null || SelectedFilteringParameter == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureProfileState(SelectedPIDProfile);
|
||||
|
||||
int index = SelectedPIDProfile.FilteringParameters.IndexOf(SelectedFilteringParameter);
|
||||
SelectedPIDProfile.FilteringParameters.Remove(SelectedFilteringParameter);
|
||||
SelectedFilteringParameter = SelectedPIDProfile.FilteringParameters.Count == 0
|
||||
? null
|
||||
: SelectedPIDProfile.FilteringParameters[Math.Min(index, SelectedPIDProfile.FilteringParameters.Count - 1)];
|
||||
NotifyOfPropertyChange(() => SelectedFilteringParameters);
|
||||
NotifyOfPropertyChange(() => HasUnsavedChanges);
|
||||
}
|
||||
|
||||
private void EnsureProcessRecipeState()
|
||||
{
|
||||
if (ProcessRecipe == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ProcessRecipe.AxisPIDParameters == null)
|
||||
{
|
||||
ProcessRecipe.AxisPIDParameters = new ObservableCollection<PIDProfile>();
|
||||
}
|
||||
|
||||
if (ProcessRecipe.ProcessCompensationParameters == null)
|
||||
{
|
||||
ProcessRecipe.ProcessCompensationParameters = new ObservableCollection<ProcessAxisCompensationParameter>();
|
||||
}
|
||||
|
||||
EnsureFixedProfiles();
|
||||
EnsureFixedCompensationParameters();
|
||||
|
||||
foreach (PIDProfile profile in ProcessRecipe.AxisPIDParameters)
|
||||
{
|
||||
EnsureProfileState(profile);
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncProfileSelection()
|
||||
{
|
||||
if (AxisPIDParameters == null || AxisPIDParameters.Count == 0)
|
||||
{
|
||||
SelectedPIDProfile = null;
|
||||
SelectedAxisParameter = null;
|
||||
SelectedFilteringParameter = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedPIDProfile == null || !AxisPIDParameters.Contains(SelectedPIDProfile))
|
||||
{
|
||||
SelectedPIDProfile = AxisPIDParameters[0];
|
||||
}
|
||||
|
||||
EnsureProfileState(SelectedPIDProfile);
|
||||
|
||||
if (SelectedAxes == null || SelectedAxes.Count == 0)
|
||||
{
|
||||
SelectedAxisParameter = null;
|
||||
}
|
||||
else if (SelectedAxisParameter == null || !SelectedAxes.Contains(SelectedAxisParameter))
|
||||
{
|
||||
SelectedAxisParameter = SelectedAxes[0];
|
||||
}
|
||||
|
||||
SyncFilteringSelection();
|
||||
}
|
||||
|
||||
private void EnsureProfileState(PIDProfile profile)
|
||||
{
|
||||
if (profile == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
profile.EnsureAxes();
|
||||
|
||||
if (profile.FilteringParameters == null)
|
||||
{
|
||||
profile.FilteringParameters = new ObservableCollection<AxisFilteringParameter>();
|
||||
}
|
||||
|
||||
if (profile.FilteringParameters.Count == 0)
|
||||
{
|
||||
AxisFilteringParameter filteringParameter = new AxisFilteringParameter();
|
||||
filteringParameter.AxisIndex = GetDefaultAxisIndex();
|
||||
profile.FilteringParameters.Add(filteringParameter);
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncFilteringSelection()
|
||||
{
|
||||
if (SelectedFilteringParameters == null || SelectedFilteringParameters.Count == 0)
|
||||
{
|
||||
SelectedFilteringParameter = null;
|
||||
SelectedNotchFilteringParameter = null;
|
||||
SelectedLowPassFilteringParameter = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedFilteringParameter == null || !SelectedFilteringParameters.Contains(SelectedFilteringParameter))
|
||||
{
|
||||
SelectedFilteringParameter = SelectedFilteringParameters[0];
|
||||
}
|
||||
|
||||
if (SelectedNotchFilteringParameter == null || !SelectedFilteringParameters.Contains(SelectedNotchFilteringParameter))
|
||||
{
|
||||
SelectedNotchFilteringParameter = SelectedFilteringParameter;
|
||||
}
|
||||
|
||||
if (SelectedLowPassFilteringParameter == null || !SelectedFilteringParameters.Contains(SelectedLowPassFilteringParameter))
|
||||
{
|
||||
SelectedLowPassFilteringParameter = SelectedFilteringParameter;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureFixedProfiles()
|
||||
{
|
||||
foreach (string profileName in _fixedProfileNames)
|
||||
{
|
||||
PIDProfile profile = AxisPIDParameters.FirstOrDefault(item => string.Equals(item.Name, profileName, StringComparison.OrdinalIgnoreCase));
|
||||
if (profile == null)
|
||||
{
|
||||
profile = new PIDProfile(profileName);
|
||||
profile.Description = "程序初始化固定配置组";
|
||||
AxisPIDParameters.Add(profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureFixedCompensationParameters()
|
||||
{
|
||||
foreach (string axisName in _fixedCompensationAxisNames)
|
||||
{
|
||||
ProcessAxisCompensationParameter parameter = ProcessRecipe.ProcessCompensationParameters
|
||||
.FirstOrDefault(item => string.Equals(item.AxisName, axisName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (parameter == null)
|
||||
{
|
||||
parameter = new ProcessAxisCompensationParameter();
|
||||
parameter.AxisName = axisName;
|
||||
parameter.CompensationValue = 0d;
|
||||
ProcessRecipe.ProcessCompensationParameters.Add(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadPidAxisParameters()
|
||||
{
|
||||
if (SelectedAxes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (AxisPIDParameter axisParameter in SelectedAxes)
|
||||
{
|
||||
AxisPIDParameter deviceParameter = _pidOperater.ReadPIDParametersFromDevice(axisParameter.ModuleName);
|
||||
CopyAxisParameterValues(deviceParameter, axisParameter);
|
||||
}
|
||||
}
|
||||
|
||||
private void WritePidAxisParameters()
|
||||
{
|
||||
if (SelectedAxes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (AxisPIDParameter axisParameter in SelectedAxes)
|
||||
{
|
||||
_pidOperater.SendPIDParameters(axisParameter);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadFilteringParameters()
|
||||
{
|
||||
if (SelectedFilteringParameter == null)
|
||||
{
|
||||
LocalizedMessageBox.Show(MessageKey.PidNoFilteringParameterSelected, MessageKey.TitleWarning);
|
||||
return;
|
||||
}
|
||||
|
||||
AxisFilteringParameter deviceParameter = _pidOperater.ReadPIDFilteringParametersFromDevice();
|
||||
CopyFilteringParameterValues(deviceParameter, SelectedFilteringParameter);
|
||||
}
|
||||
|
||||
private void WriteFilteringParameters()
|
||||
{
|
||||
if (SelectedFilteringParameter == null)
|
||||
{
|
||||
LocalizedMessageBox.Show(MessageKey.PidNoFilteringParameterSelected, MessageKey.TitleWarning);
|
||||
return;
|
||||
}
|
||||
|
||||
_pidOperater.SendPIDFilteringParameters(SelectedFilteringParameter);
|
||||
}
|
||||
|
||||
private void CopyAxisParameterValues(AxisPIDParameter source, AxisPIDParameter target)
|
||||
{
|
||||
if (source == null || target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
target.PA_SLVKP.Value = source.PA_SLVKP.Value;
|
||||
target.PA_SLVKI.Value = source.PA_SLVKI.Value;
|
||||
target.PA_SLVKPSF.Value = source.PA_SLVKPSF.Value;
|
||||
target.PA_SLVKPIF.Value = source.PA_SLVKPIF.Value;
|
||||
target.PA_SLVKISF.Value = source.PA_SLVKISF.Value;
|
||||
target.PA_SLVKIIF.Value = source.PA_SLVKIIF.Value;
|
||||
target.PA_SLPKP.Value = source.PA_SLPKP.Value;
|
||||
target.PA_SLPKPIF.Value = source.PA_SLPKPIF.Value;
|
||||
target.PA_SLPKPSF.Value = source.PA_SLPKPSF.Value;
|
||||
target.PA_SLAFF.Value = source.PA_SLAFF.Value;
|
||||
target.PA_SLJFF.Value = source.PA_SLJFF.Value;
|
||||
}
|
||||
|
||||
private void CopyFilteringParameterValues(AxisFilteringParameter source, AxisFilteringParameter target)
|
||||
{
|
||||
if (source == null || target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
target.IsUseNotch.Value = source.IsUseNotch.Value;
|
||||
target.NotchFrequency.Value = source.NotchFrequency.Value;
|
||||
target.NotchWidth.Value = source.NotchWidth.Value;
|
||||
target.NotchFalloff.Value = source.NotchFalloff.Value;
|
||||
target.IsUseFiltering.Value = source.IsUseFiltering.Value;
|
||||
target.FilteringFrequency.Value = source.FilteringFrequency.Value;
|
||||
target.FilteringDamping.Value = source.FilteringDamping.Value;
|
||||
}
|
||||
|
||||
private PidAxisOption GetNextAxisOption(IEnumerable<int> usedAxisIndices)
|
||||
{
|
||||
if (AxisOptions == null || AxisOptions.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
HashSet<int> usedAxisSet = new HashSet<int>();
|
||||
if (usedAxisIndices != null)
|
||||
{
|
||||
foreach (int axisIndex in usedAxisIndices)
|
||||
{
|
||||
usedAxisSet.Add(axisIndex);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (PidAxisOption axisOption in AxisOptions)
|
||||
{
|
||||
if (!usedAxisSet.Contains(axisOption.AxisIndex))
|
||||
{
|
||||
return axisOption;
|
||||
}
|
||||
}
|
||||
|
||||
return AxisOptions[0];
|
||||
}
|
||||
|
||||
private int GetDefaultAxisIndex()
|
||||
{
|
||||
if (AxisOptions == null || AxisOptions.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return AxisOptions[0].AxisIndex;
|
||||
}
|
||||
|
||||
private ObservableCollection<PidAxisOption> BuildAxisOptions()
|
||||
{
|
||||
ObservableCollection<PidAxisOption> axisOptions = new ObservableCollection<PidAxisOption>();
|
||||
|
||||
if (_hardwareManager != null && _hardwareManager.AxesDic != null && _hardwareManager.AxesDic.Count > 0)
|
||||
{
|
||||
int order = 0;
|
||||
foreach (string axisName in _hardwareManager.AxesDic.Keys.OrderBy(item => item))
|
||||
{
|
||||
axisOptions.Add(new PidAxisOption
|
||||
{
|
||||
AxisName = axisName,
|
||||
AxisIndex = ExtractAxisIndex(axisName, order)
|
||||
});
|
||||
order++;
|
||||
}
|
||||
}
|
||||
|
||||
if (axisOptions.Count > 0)
|
||||
{
|
||||
return axisOptions;
|
||||
}
|
||||
|
||||
FieldInfo[] fields = typeof(AxisName).GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||
int fallbackOrder = 0;
|
||||
foreach (FieldInfo fieldInfo in fields.OrderBy(item => item.Name))
|
||||
{
|
||||
object value = fieldInfo.GetValue(null);
|
||||
string axisName = value as string;
|
||||
if (string.IsNullOrWhiteSpace(axisName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
axisOptions.Add(new PidAxisOption
|
||||
{
|
||||
AxisName = axisName,
|
||||
AxisIndex = ExtractAxisIndex(axisName, fallbackOrder)
|
||||
});
|
||||
fallbackOrder++;
|
||||
}
|
||||
|
||||
return axisOptions;
|
||||
}
|
||||
|
||||
private int ExtractAxisIndex(string axisName, int fallbackValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(axisName))
|
||||
{
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
string digits = new string(axisName.Reverse().TakeWhile(char.IsDigit).Reverse().ToArray());
|
||||
int axisIndex;
|
||||
if (int.TryParse(digits, out axisIndex))
|
||||
{
|
||||
return axisIndex;
|
||||
}
|
||||
|
||||
return fallbackValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Recipe.View;
|
||||
using MaxwellControl.Tools;
|
||||
using MaxwellFramework.Core.Common.Command;
|
||||
using MwFramework.ManagerService;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class RecipeButtonGroupViewModel :BaseScreen
|
||||
{
|
||||
public ICommand CreateNewCmd { get;private set; }
|
||||
public ICommand DeleteCmd { get; private set; }
|
||||
public ICommand CopyCmd { get; private set; }
|
||||
public ICommand ApplyCmd { get; private set; }
|
||||
public ICommand ImportCmd { get; private set; }
|
||||
public ICommand SaveCmd { get; private set; }
|
||||
public ICommand ExportCmd { get; private set; }
|
||||
public ICommand ReNameCmd { get; private set; }
|
||||
public ICommand ClearCmd { get; private set; }
|
||||
|
||||
// 新增事件 —— 发布者(按钮组)向外广播意图
|
||||
public event EventHandler CreateNewRequested;
|
||||
public event EventHandler DeleteRequested;
|
||||
public event EventHandler CopyRequested;
|
||||
public event EventHandler ApplyRequested;
|
||||
public event EventHandler ImportRequested;
|
||||
public event EventHandler SaveRequested;
|
||||
public event EventHandler ExportRequested;
|
||||
public event EventHandler ReNameRequested;
|
||||
public event EventHandler ClearRequested;
|
||||
|
||||
public RecipeButtonGroupViewModel()
|
||||
{
|
||||
CreateNewCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
if (obj != null && obj is IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
var existingNames = recipeViewModel.RecipeWraps.Select(r => r.RecipeName).ToList();
|
||||
var recipeNameWindow = new RecipeNameWindowModel
|
||||
{
|
||||
ExistingRecipeNames = existingNames
|
||||
};
|
||||
ShowRecipeWindow<RecipeNameWindow,RecipeNameWindowModel>(recipeNameWindow, out bool? result);
|
||||
if (result == true)
|
||||
{
|
||||
CreateNewRequested?.Invoke(this, new RecipeEventArgs
|
||||
{
|
||||
RecipeName = recipeNameWindow.RecipeName
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
DeleteCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
if (obj != null && obj is IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
bool flowControl = CheckOperate(recipeViewModel);
|
||||
if (!flowControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var result = MwMessageBox.Show($"确认删除当前选中配方:{recipeViewModel.SelectedRecipeWrap.RecipeName}?", "确认删除", MessageBoxButton.YesNo, MessageBoxImage.Warning);
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
DeleteRequested?.Invoke(this, new RecipeEventArgs
|
||||
{
|
||||
RecipeName = recipeViewModel.SelectedRecipeWrap.RecipeName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
CopyCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
if (obj != null && obj is IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
if (!EnsureActionSupported(recipeViewModel.CanCopyRecipe, "复制"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool flowControl = CheckOperate(recipeViewModel);
|
||||
if (!flowControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var existingNames = recipeViewModel.RecipeWraps.Select(r => r.RecipeName).ToList();
|
||||
var recipeNameWindow = new RecipeNameWindowModel
|
||||
{
|
||||
ExistingRecipeNames = existingNames,
|
||||
RecipeName = recipeViewModel.SelectedRecipeWrap.RecipeName + "_Copy"
|
||||
};
|
||||
ShowRecipeWindow<RecipeNameWindow, RecipeNameWindowModel>(recipeNameWindow, out bool? result);
|
||||
if (result == true)
|
||||
{
|
||||
CopyRequested?.Invoke(this, new RecipeEventArgs
|
||||
{
|
||||
RecipeName = recipeNameWindow.RecipeName
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
ApplyCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
if (obj != null && obj is IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
bool flowControl = CheckOperate(recipeViewModel);
|
||||
if (!flowControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ApplyRequested?.Invoke(this, new RecipeEventArgs
|
||||
{
|
||||
RecipeName = recipeViewModel.SelectedRecipeWrap.RecipeName
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ImportCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
if (obj != null && obj is IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
if (!EnsureActionSupported(recipeViewModel.CanImportRecipe, "导入"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool flowControl = CheckOperate(recipeViewModel);
|
||||
if (!flowControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ImportRequested?.Invoke(this, new RecipeEventArgs
|
||||
{
|
||||
RecipeName = recipeViewModel.SelectedRecipeWrap.RecipeName
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
SaveCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
if (obj != null && obj is IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
bool flowControl = CheckOperate(recipeViewModel);
|
||||
if (!flowControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SaveRequested?.Invoke(this, new RecipeEventArgs
|
||||
{
|
||||
RecipeName = recipeViewModel.SelectedRecipeWrap.RecipeName
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ExportCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
if (obj != null && obj is IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
if (!EnsureActionSupported(recipeViewModel.CanExportRecipe, "导出"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool flowControl = CheckOperate(recipeViewModel);
|
||||
if (!flowControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ExportRequested?.Invoke(this, new RecipeEventArgs
|
||||
{
|
||||
RecipeName = recipeViewModel.SelectedRecipeWrap.RecipeName
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ReNameCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
if (obj != null && obj is IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
bool flowControl = CheckOperate(recipeViewModel);
|
||||
if (!flowControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var existingNames = recipeViewModel.RecipeWraps.Select(r => r.RecipeName).ToList();
|
||||
var recipeNameWindow = new RecipeReNameWindowModel
|
||||
{
|
||||
ExistingRecipeNames = existingNames,
|
||||
OldRecipeName = recipeViewModel.SelectedRecipeWrap.RecipeName
|
||||
};
|
||||
ShowRecipeWindow<RecipeReNameWindow,RecipeReNameWindowModel>( recipeNameWindow, out bool? result);
|
||||
if (result == true)
|
||||
{
|
||||
ReNameRequested?.Invoke(this, new RecipeRenameEventArgs
|
||||
{
|
||||
NewRecipeName = recipeNameWindow.RecipeName,
|
||||
OldRecipeName = recipeNameWindow.OldRecipeName
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
ClearCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
ClearRequested?.Invoke(this, EventArgs.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
private static bool CheckOperate(IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
if (recipeViewModel.RecipeWraps.Count == 0)
|
||||
{
|
||||
MwMessageBox.Show("当前无可操作的配方,请先创建或导入配方。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return false;
|
||||
}
|
||||
else if (recipeViewModel.SelectedRecipeWrap == null)
|
||||
{
|
||||
MwMessageBox.Show("请先选择一个配方再进行操作。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool EnsureActionSupported(bool isSupported, string actionName)
|
||||
{
|
||||
if (isSupported)
|
||||
return true;
|
||||
|
||||
MwMessageBox.Show($"当前版本暂不支持{actionName}功能。");
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ShowRecipeWindow<TWindow, TViewModel>(TViewModel viewModel, out bool? result)
|
||||
where TWindow : Window, new()
|
||||
{
|
||||
result = null;
|
||||
var owner = WindowHelper.GetActiveWindow();
|
||||
|
||||
try
|
||||
{
|
||||
// 确保在 UI 线程创建/显示窗口
|
||||
if (Application.Current == null || Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
var window = new TWindow
|
||||
{
|
||||
DataContext = viewModel,
|
||||
Owner = owner,
|
||||
ShowInTaskbar = owner == null,
|
||||
WindowStartupLocation = owner != null ? WindowStartupLocation.CenterOwner : WindowStartupLocation.CenterScreen
|
||||
};
|
||||
|
||||
// 如果确实需要置顶,可由调用方或窗口自身控制;这里不强制 Topmost
|
||||
result = window.ShowDialog();
|
||||
}
|
||||
else
|
||||
{
|
||||
var windowResult= default(bool?);
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var window = new TWindow
|
||||
{
|
||||
DataContext = viewModel,
|
||||
Owner = owner,
|
||||
ShowInTaskbar = owner == null,
|
||||
WindowStartupLocation = owner != null ? WindowStartupLocation.CenterOwner : WindowStartupLocation.CenterScreen
|
||||
};
|
||||
windowResult = window.ShowDialog();
|
||||
});
|
||||
result = windowResult;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 避免抛出异常到调用方(视需求可记录日志)
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using MaxwellFramework.Core.Common.Command;
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class RecipeNameWindowModel : PropertyChangedBase
|
||||
{
|
||||
private bool? _dialogResult;
|
||||
public bool? DialogResult
|
||||
{
|
||||
get => _dialogResult;
|
||||
set => SetAndNotify(ref _dialogResult, value);
|
||||
}
|
||||
|
||||
private string _recipeName;
|
||||
public string RecipeName
|
||||
{
|
||||
get => _recipeName;
|
||||
set
|
||||
{
|
||||
if(SetAndNotify(ref _recipeName, value))
|
||||
{
|
||||
UpdateValidationMessage();
|
||||
((DelegateCommand)OkCommand).RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
private string _validationMessage;
|
||||
/// <summary>
|
||||
/// 在界面上显示的验证信息(为空时不显示)
|
||||
/// </summary>
|
||||
public string ValidationMessage
|
||||
{
|
||||
get => _validationMessage;
|
||||
set => SetAndNotify(ref _validationMessage, value);
|
||||
}
|
||||
public IEnumerable<string> ExistingRecipeNames { get; set; }
|
||||
public ICommand OkCommand { get; }
|
||||
public ICommand CancelCommand { get; }
|
||||
public RecipeNameWindowModel()
|
||||
{
|
||||
OkCommand = new DelegateCommand(OnOk, CanOk);
|
||||
CancelCommand = new DelegateCommand(OnCancel);
|
||||
}
|
||||
private void OnOk(object obj)
|
||||
{
|
||||
DialogResult = true;
|
||||
}
|
||||
private bool CanOk(object obj)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(RecipeName))
|
||||
return false;
|
||||
if (ExistingRecipeNames != null && ExistingRecipeNames.Contains(RecipeName, StringComparer.OrdinalIgnoreCase))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
private void OnCancel(object obj)
|
||||
{
|
||||
DialogResult = false;
|
||||
}
|
||||
private void UpdateValidationMessage()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(RecipeName))
|
||||
{
|
||||
ValidationMessage = "名称不能为空";
|
||||
return;
|
||||
}
|
||||
|
||||
if (ExistingRecipeNames != null && ExistingRecipeNames.Contains(RecipeName, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
ValidationMessage = "名称已存在";
|
||||
return;
|
||||
}
|
||||
|
||||
ValidationMessage = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using MaxwellFramework.Core.Common.Command;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class RecipeReNameWindowModel : PropertyChangedBase
|
||||
{
|
||||
private bool? _dialogResult;
|
||||
public bool? DialogResult
|
||||
{
|
||||
get => _dialogResult;
|
||||
set => SetAndNotify(ref _dialogResult, value);
|
||||
}
|
||||
|
||||
private string _recipeName;
|
||||
public string RecipeName
|
||||
{
|
||||
get => _recipeName;
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _recipeName, value))
|
||||
{
|
||||
UpdateValidationMessage();
|
||||
((DelegateCommand)OkCommand).RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
private string _oldRecipeName;
|
||||
|
||||
public string OldRecipeName
|
||||
{
|
||||
get { return _oldRecipeName; }
|
||||
set { SetAndNotify(ref _oldRecipeName, value); }
|
||||
}
|
||||
|
||||
private string _validationMessage;
|
||||
/// <summary>
|
||||
/// 在界面上显示的验证信息(为空时不显示)
|
||||
/// </summary>
|
||||
public string ValidationMessage
|
||||
{
|
||||
get => _validationMessage;
|
||||
set => SetAndNotify(ref _validationMessage, value);
|
||||
}
|
||||
|
||||
public IEnumerable<string> ExistingRecipeNames { get; set; }
|
||||
public ICommand OkCommand { get; }
|
||||
public ICommand CancelCommand { get; }
|
||||
public RecipeReNameWindowModel()
|
||||
{
|
||||
OkCommand = new DelegateCommand(OnOk, CanOk);
|
||||
CancelCommand = new DelegateCommand(OnCancel);
|
||||
}
|
||||
private void OnOk(object obj)
|
||||
{
|
||||
DialogResult = true;
|
||||
}
|
||||
private bool CanOk(object obj)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(RecipeName))
|
||||
return false;
|
||||
if (ExistingRecipeNames != null && ExistingRecipeNames.Contains(RecipeName, StringComparer.OrdinalIgnoreCase))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
private void OnCancel(object obj)
|
||||
{
|
||||
DialogResult = false;
|
||||
}
|
||||
private void UpdateValidationMessage()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(RecipeName))
|
||||
{
|
||||
ValidationMessage = "名称不能为空";
|
||||
return;
|
||||
}
|
||||
|
||||
if (ExistingRecipeNames != null && ExistingRecipeNames.Contains(RecipeName, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
ValidationMessage = "名称已存在";
|
||||
return;
|
||||
}
|
||||
|
||||
ValidationMessage = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using MaxwellFramework.Core.Interfaces;
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using StyletIoC;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class RecipeViewModel :BaseScreen ,IPage
|
||||
{
|
||||
private bool _isReactivatingAfterBlockedLeave;
|
||||
|
||||
public string Name => "Recipe";
|
||||
private const string CARRIER = "载具配方";
|
||||
private const string SUBSTRATE = "基板配方";
|
||||
private const string WAFER = "芯片配方";
|
||||
private const string PROCESS = "工艺配方";
|
||||
private readonly Dictionary<string, BaseScreen> _viewModelDict = new Dictionary<string, BaseScreen>();
|
||||
public ObservableCollection<MenuItemWrap> MenuItemWraps { get; private set; }
|
||||
|
||||
private MenuItemWrap _selectedMenuItem;
|
||||
|
||||
public MenuItemWrap SelectedMenuItem
|
||||
{
|
||||
get { return _selectedMenuItem; }
|
||||
set
|
||||
{
|
||||
if (_selectedMenuItem == value)
|
||||
return;
|
||||
|
||||
if (_selectedMenuItem != null && value != null && !TryLeaveCurrentScreen())
|
||||
{
|
||||
NotifyOfPropertyChange(() => SelectedMenuItem);
|
||||
return;
|
||||
}
|
||||
|
||||
if (SetAndNotify(ref _selectedMenuItem, value))
|
||||
{
|
||||
CurrentScreen = value != null ? _viewModelDict[value.Header] : null;
|
||||
SyncCurrentScreenSelectionFromActiveRecipe();
|
||||
}
|
||||
}
|
||||
}
|
||||
private BaseScreen _currentScreen;
|
||||
|
||||
public BaseScreen CurrentScreen
|
||||
{
|
||||
get { return _currentScreen; }
|
||||
set
|
||||
{
|
||||
if (_currentScreen == value)
|
||||
return;
|
||||
|
||||
UnsubscribeScreenState(_currentScreen as INotifyPropertyChanged);
|
||||
|
||||
if (SetAndNotify(ref _currentScreen, value))
|
||||
{
|
||||
SubscribeScreenState(value as INotifyPropertyChanged);
|
||||
NotifyOfPropertyChange(() => IsRecipePanelHidden);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRecipePanelHidden
|
||||
{
|
||||
get
|
||||
{
|
||||
var recipeScreen = CurrentScreen as IRecipeViewModel;
|
||||
return recipeScreen != null && recipeScreen.HideRecipePanel;
|
||||
}
|
||||
set
|
||||
{
|
||||
var recipeScreen = CurrentScreen as IRecipeViewModel;
|
||||
if (recipeScreen == null || recipeScreen.HideRecipePanel == value)
|
||||
return;
|
||||
|
||||
recipeScreen.HideRecipePanel = value;
|
||||
NotifyOfPropertyChange(() => IsRecipePanelHidden);
|
||||
}
|
||||
}
|
||||
|
||||
private CarrierRecipeViewModel _carrierRecipeViewModel;
|
||||
[Inject]
|
||||
public CarrierRecipeViewModel CarrierRecipeViewModel
|
||||
{
|
||||
get { return _carrierRecipeViewModel; }
|
||||
set { _carrierRecipeViewModel = value; }
|
||||
}
|
||||
private SubstrateRecipeViewModel _substrateRecipeViewModel;
|
||||
[Inject]
|
||||
public SubstrateRecipeViewModel SubstrateRecipeViewModel
|
||||
{
|
||||
get { return _substrateRecipeViewModel; }
|
||||
set { _substrateRecipeViewModel = value; }
|
||||
}
|
||||
private WaferRecipeViewModel _waferRecipeViewModel;
|
||||
[Inject]
|
||||
public WaferRecipeViewModel WaferRecipeViewModel
|
||||
{
|
||||
get { return _waferRecipeViewModel; }
|
||||
set { _waferRecipeViewModel = value; }
|
||||
}
|
||||
private ProcessRecipeViewModel _processRecipeViewModel;
|
||||
[Inject]
|
||||
public ProcessRecipeViewModel ProcessRecipeViewModel
|
||||
{
|
||||
get { return _processRecipeViewModel; }
|
||||
set { _processRecipeViewModel = value; }
|
||||
}
|
||||
|
||||
|
||||
public RecipeViewModel()
|
||||
{
|
||||
InitMenuItems();
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override void OnViewLoaded()
|
||||
{
|
||||
base.OnViewLoaded();
|
||||
EnsureViewModelDict();
|
||||
if(SelectedMenuItem==null)
|
||||
SelectedMenuItem = MenuItemWraps[0];
|
||||
|
||||
SyncCurrentScreenSelectionFromActiveRecipe();
|
||||
}
|
||||
private void InitMenuItems()
|
||||
{
|
||||
MenuItemWraps = new ObservableCollection<MenuItemWrap>
|
||||
{
|
||||
//new MenuItemWrap() { Header=CARRIER,Tag=CARRIER },
|
||||
new MenuItemWrap() { Header=SUBSTRATE,Tag=SUBSTRATE },
|
||||
new MenuItemWrap() { Header=WAFER,Tag=WAFER },
|
||||
new MenuItemWrap() { Header=PROCESS,Tag=PROCESS },
|
||||
};
|
||||
}
|
||||
private void InitViewModelDict()
|
||||
{
|
||||
_viewModelDict.Clear();
|
||||
_viewModelDict.Add(CARRIER, CarrierRecipeViewModel);
|
||||
_viewModelDict.Add(SUBSTRATE, SubstrateRecipeViewModel);
|
||||
_viewModelDict.Add(WAFER, WaferRecipeViewModel);
|
||||
_viewModelDict.Add(PROCESS, ProcessRecipeViewModel);
|
||||
}
|
||||
|
||||
private void EnsureViewModelDict()
|
||||
{
|
||||
if (_viewModelDict.Count == 0)
|
||||
{
|
||||
InitViewModelDict();
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncCurrentScreenSelectionFromActiveRecipe()
|
||||
{
|
||||
var recipeScreen = CurrentScreen as IRecipeViewModel;
|
||||
recipeScreen?.SyncSelectionFromActiveRecipe();
|
||||
}
|
||||
|
||||
private bool TryLeaveCurrentScreen()
|
||||
{
|
||||
var guard = CurrentScreen as IRecipeViewModel;
|
||||
if (guard == null)
|
||||
return true;
|
||||
|
||||
return guard.TryLeaveCurrentRecipeContext();
|
||||
}
|
||||
|
||||
public bool TrySelectMenuItem(MenuItemWrap targetMenuItem)
|
||||
{
|
||||
if (targetMenuItem == null)
|
||||
return false;
|
||||
|
||||
SelectedMenuItem = targetMenuItem;
|
||||
return SelectedMenuItem == targetMenuItem;
|
||||
}
|
||||
|
||||
public bool TrySelectRecipeWrap(RecipeWrap targetRecipeWrap)
|
||||
{
|
||||
var guard = CurrentScreen as IRecipeViewModel;
|
||||
if (guard == null || targetRecipeWrap == null)
|
||||
return false;
|
||||
|
||||
if (guard.SelectedRecipeWrap == targetRecipeWrap)
|
||||
return true;
|
||||
|
||||
guard.SelectedRecipeWrap = targetRecipeWrap;
|
||||
return guard.SelectedRecipeWrap == targetRecipeWrap;
|
||||
}
|
||||
|
||||
private void SubscribeScreenState(INotifyPropertyChanged screen)
|
||||
{
|
||||
if (screen != null)
|
||||
{
|
||||
screen.PropertyChanged += OnCurrentScreenPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void UnsubscribeScreenState(INotifyPropertyChanged screen)
|
||||
{
|
||||
if (screen != null)
|
||||
{
|
||||
screen.PropertyChanged -= OnCurrentScreenPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCurrentScreenPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(e.PropertyName) || string.Equals(e.PropertyName, nameof(BaseScreen.HideRecipePanel), StringComparison.Ordinal))
|
||||
{
|
||||
NotifyOfPropertyChange(() => IsRecipePanelHidden);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnStateChanged(ScreenState previousState, ScreenState newState)
|
||||
{
|
||||
base.OnStateChanged(previousState, newState);
|
||||
|
||||
if (_isReactivatingAfterBlockedLeave)
|
||||
return;
|
||||
|
||||
if (newState == ScreenState.Active)
|
||||
{
|
||||
EnsureViewModelDict();
|
||||
if (SelectedMenuItem == null && MenuItemWraps != null && MenuItemWraps.Count > 0)
|
||||
{
|
||||
SelectedMenuItem = MenuItemWraps[0];
|
||||
}
|
||||
|
||||
SyncCurrentScreenSelectionFromActiveRecipe();
|
||||
}
|
||||
|
||||
if (previousState == ScreenState.Active && newState == ScreenState.Deactivated && !TryLeaveCurrentScreen())
|
||||
{
|
||||
try
|
||||
{
|
||||
_isReactivatingAfterBlockedLeave = true;
|
||||
Execute.OnUIThread(() => ((IScreenState)this).Activate());
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isReactivatingAfterBlockedLeave = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDeactivate()
|
||||
{
|
||||
UnsubscribeScreenState(_currentScreen as INotifyPropertyChanged);
|
||||
base.OnDeactivate();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,442 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public abstract class RecipeViewModelBase<TRecipe> : BaseScreen, IRecipeViewModel
|
||||
where TRecipe : RecipeBase, new()
|
||||
{
|
||||
private string _lastSavedSnapshot = string.Empty;
|
||||
protected string LastSavedSnapshot => _lastSavedSnapshot;
|
||||
private bool _skipLeaveCheck;
|
||||
private RecipeWrap _selectedRecipeWrap;
|
||||
|
||||
public ObservableCollection<RecipeWrap> RecipeWraps { get; protected set; } = new ObservableCollection<RecipeWrap>();
|
||||
|
||||
public RecipeWrap SelectedRecipeWrap
|
||||
{
|
||||
get { return _selectedRecipeWrap; }
|
||||
set
|
||||
{
|
||||
if (_selectedRecipeWrap == value)
|
||||
return;
|
||||
|
||||
if (!_skipLeaveCheck && _selectedRecipeWrap != null && value != null && !TryLeaveCurrentRecipeContext())
|
||||
{
|
||||
NotifyOfPropertyChange(() => SelectedRecipeWrap);
|
||||
return;
|
||||
}
|
||||
|
||||
if (SetAndNotify(ref _selectedRecipeWrap, value))
|
||||
{
|
||||
LoadRecipeFromWrap(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RecipeButtonGroupViewModel RecipeButtonGroupViewModel { get; protected set; }
|
||||
public virtual bool CanCopyRecipe => true;
|
||||
public virtual bool CanImportRecipe => false;
|
||||
public virtual bool CanExportRecipe => false;
|
||||
public bool HasUnsavedChanges => IsRecipeDirty();
|
||||
public string CurrentRecipeDisplayName => CurrentRecipe != null ? CurrentRecipe.RecipeName : EmptyRecipeDisplayName;
|
||||
public RecipeManager RecipeManager { get; }
|
||||
|
||||
protected RecipeViewModelBase(RecipeManager recipeManager)
|
||||
{
|
||||
RecipeManager = recipeManager;
|
||||
}
|
||||
|
||||
protected abstract TRecipe CurrentRecipe { get; set; }
|
||||
protected abstract string RecipeFolderPath { get; }
|
||||
protected abstract string EmptyRecipeDisplayName { get; }
|
||||
protected abstract string GetActiveRecipeName();
|
||||
protected abstract void SwitchActiveRecipe(string recipeName);
|
||||
protected abstract void ClearActiveRecipe();
|
||||
protected virtual void OnRecipeLoaded(TRecipe recipe, RecipeWrap recipeWrap) { }
|
||||
protected virtual string GetUnsavedChangesMessage() => $"<22>䷽<EFBFBD><E4B7BD>{CurrentRecipeDisplayName}<7D><><EFBFBD><EFBFBD>δ<EFBFBD><CEB4><EFBFBD><EFBFBD><EFBFBD>ģ<DEB8><C4A3>Ƿ<EFBFBD><C7B7>ȱ<EFBFBD><C8B1>棿";
|
||||
protected virtual void AfterDeleteCurrentRecipe(string deletedRecipeName) { }
|
||||
protected virtual void AfterRecipeRenamed(string oldRecipeName, string newRecipeName) { }
|
||||
|
||||
public virtual bool TryLeaveCurrentRecipeContext()
|
||||
{
|
||||
if (!HasUnsavedChanges)
|
||||
return true;
|
||||
|
||||
var result = MwMessageBox.Show(GetUnsavedChangesMessage(), "δ<><CEB4><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ", System.Windows.MessageBoxButton.YesNoCancel, System.Windows.MessageBoxImage.Warning);
|
||||
if (result == System.Windows.MessageBoxResult.Cancel)
|
||||
return false;
|
||||
|
||||
if (result == System.Windows.MessageBoxResult.Yes)
|
||||
SaveCurrentRecipe(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SaveCurrentRecipeSilently()
|
||||
{
|
||||
SaveCurrentRecipe(false);
|
||||
}
|
||||
|
||||
protected void RegisterButtonGroupEvents()
|
||||
{
|
||||
RecipeButtonGroupViewModel = new RecipeButtonGroupViewModel();
|
||||
RecipeButtonGroupViewModel.CreateNewRequested -= OnCreateNewRecipe;
|
||||
RecipeButtonGroupViewModel.ApplyRequested -= OnApplyRecipe;
|
||||
RecipeButtonGroupViewModel.DeleteRequested -= OnDeleteRecipe;
|
||||
RecipeButtonGroupViewModel.CopyRequested -= OnCopyRecipe;
|
||||
RecipeButtonGroupViewModel.ImportRequested -= OnImportRecipe;
|
||||
RecipeButtonGroupViewModel.SaveRequested -= OnSaveRecipe;
|
||||
RecipeButtonGroupViewModel.ExportRequested -= OnExportRecipe;
|
||||
RecipeButtonGroupViewModel.ReNameRequested -= OnReNameRecipe;
|
||||
RecipeButtonGroupViewModel.ClearRequested -= OnClearRecipe;
|
||||
RecipeButtonGroupViewModel.CreateNewRequested += OnCreateNewRecipe;
|
||||
RecipeButtonGroupViewModel.ApplyRequested += OnApplyRecipe;
|
||||
RecipeButtonGroupViewModel.DeleteRequested += OnDeleteRecipe;
|
||||
RecipeButtonGroupViewModel.CopyRequested += OnCopyRecipe;
|
||||
RecipeButtonGroupViewModel.ImportRequested += OnImportRecipe;
|
||||
RecipeButtonGroupViewModel.SaveRequested += OnSaveRecipe;
|
||||
RecipeButtonGroupViewModel.ExportRequested += OnExportRecipe;
|
||||
RecipeButtonGroupViewModel.ReNameRequested += OnReNameRecipe;
|
||||
RecipeButtonGroupViewModel.ClearRequested += OnClearRecipe;
|
||||
}
|
||||
|
||||
public virtual void OnCreateNewRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs args)
|
||||
{
|
||||
if (!TryLeaveCurrentRecipeContext())
|
||||
return;
|
||||
|
||||
var recipeWrap = new RecipeWrap
|
||||
{
|
||||
RecipeName = args.RecipeName,
|
||||
CreateTime = DateTime.UtcNow,
|
||||
ModifiedTime = DateTime.UtcNow,
|
||||
};
|
||||
|
||||
RecipeWraps.Add(recipeWrap);
|
||||
SelectRecipeWrapInternal(recipeWrap);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnDeleteRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (SelectedRecipeWrap == null)
|
||||
return;
|
||||
|
||||
if (!TryLeaveCurrentRecipeContext())
|
||||
return;
|
||||
|
||||
var deletedRecipeName = SelectedRecipeWrap.RecipeName;
|
||||
if (!RecipeManager.DeleteRecipeFolder(RecipeFolderPath, deletedRecipeName, out var error))
|
||||
{
|
||||
MwMessageBox.Show(error);
|
||||
return;
|
||||
}
|
||||
|
||||
RecipeWraps.Remove(SelectedRecipeWrap);
|
||||
AfterDeleteCurrentRecipe(deletedRecipeName);
|
||||
|
||||
if (RecipeWraps.Count > 0)
|
||||
{
|
||||
SelectRecipeWrapInternal(RecipeWraps[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedRecipeWrap = null;
|
||||
CurrentRecipe = null;
|
||||
ClearActiveRecipe();
|
||||
CaptureSavedSnapshot();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnCopyRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (!(eventArgs is RecipeEventArgs args) || SelectedRecipeWrap == null)
|
||||
return;
|
||||
|
||||
var sourceRecipeName = SelectedRecipeWrap.RecipeName;
|
||||
if (string.IsNullOrWhiteSpace(sourceRecipeName) || string.IsNullOrWhiteSpace(args.RecipeName))
|
||||
return;
|
||||
|
||||
if (CurrentRecipe != null &&
|
||||
string.Equals(CurrentRecipe.RecipeName, sourceRecipeName, StringComparison.Ordinal) &&
|
||||
HasUnsavedChanges)
|
||||
{
|
||||
SaveCurrentRecipe(false);
|
||||
}
|
||||
|
||||
if (!RecipeManager.CopyRecipeFolder(RecipeFolderPath, sourceRecipeName, args.RecipeName, out var error))
|
||||
{
|
||||
MwMessageBox.Show(error);
|
||||
return;
|
||||
}
|
||||
|
||||
var copiedRecipeWrap = new RecipeWrap
|
||||
{
|
||||
RecipeName = args.RecipeName,
|
||||
CreateTime = DateTime.UtcNow,
|
||||
ModifiedTime = DateTime.UtcNow,
|
||||
};
|
||||
|
||||
RecipeWraps.Add(copiedRecipeWrap);
|
||||
SelectRecipeWrapInternal(copiedRecipeWrap);
|
||||
}
|
||||
|
||||
public virtual void OnApplyRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs args)
|
||||
{
|
||||
var recipeWrap = RecipeWraps.FirstOrDefault(p => p.RecipeName == args.RecipeName);
|
||||
if (recipeWrap == null)
|
||||
return;
|
||||
|
||||
if (!ReferenceEquals(SelectedRecipeWrap, recipeWrap))
|
||||
{
|
||||
if (!TryLeaveCurrentRecipeContext())
|
||||
return;
|
||||
|
||||
SelectRecipeWrapInternal(recipeWrap);
|
||||
}
|
||||
|
||||
if (SelectedRecipeWrap == null)
|
||||
return;
|
||||
|
||||
if (CurrentRecipe != null && IsRecipeDirty())
|
||||
{
|
||||
SaveCurrentRecipe(false);
|
||||
}
|
||||
|
||||
SwitchActiveRecipe(SelectedRecipeWrap.RecipeName);
|
||||
UpdateInUseState(SelectedRecipeWrap);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void OnImportRecipe(object sender, EventArgs eventArgs);
|
||||
|
||||
public virtual void OnSaveRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
CommonUti.RunOnUi(() =>
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs)
|
||||
{
|
||||
SaveCurrentRecipe(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public abstract void OnExportRecipe(object sender, EventArgs eventArgs);
|
||||
public abstract void OnReNameRecipe(object sender, EventArgs eventArgs);
|
||||
|
||||
public virtual void OnClearRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (!TryLeaveCurrentRecipeContext())
|
||||
return;
|
||||
|
||||
if (SelectedRecipeWrap == null)
|
||||
return;
|
||||
|
||||
var clearedRecipe = new TRecipe
|
||||
{
|
||||
RecipeName = SelectedRecipeWrap.RecipeName
|
||||
};
|
||||
|
||||
CurrentRecipe = clearedRecipe;
|
||||
OnRecipeLoaded(clearedRecipe, SelectedRecipeWrap);
|
||||
NotifyOfPropertyChange(() => HasUnsavedChanges);
|
||||
}
|
||||
|
||||
protected bool IsRecipeDirty()
|
||||
{
|
||||
if (CurrentRecipe == null)
|
||||
return false;
|
||||
|
||||
var path = Path.Combine(CurrentRecipe.Dir ?? string.Empty, CurrentRecipe.FileName ?? string.Empty);
|
||||
if (!File.Exists(path))
|
||||
return true;
|
||||
|
||||
return !AreSnapshotsEquivalent(_lastSavedSnapshot, CurrentRecipe.CreateSnapshot());
|
||||
}
|
||||
|
||||
protected void CaptureSavedSnapshot()
|
||||
{
|
||||
_lastSavedSnapshot = CurrentRecipe != null ? CurrentRecipe.CreateSnapshot() : string.Empty;
|
||||
NotifyOfPropertyChange(() => HasUnsavedChanges);
|
||||
NotifyOfPropertyChange(() => CurrentRecipeDisplayName);
|
||||
}
|
||||
|
||||
protected bool TryRenameRecipe(RecipeRenameEventArgs args)
|
||||
{
|
||||
if (args == null)
|
||||
return false;
|
||||
|
||||
var recipeWrap = RecipeWraps.FirstOrDefault(p => p.RecipeName == args.OldRecipeName);
|
||||
if (recipeWrap == null)
|
||||
return false;
|
||||
|
||||
if (!RecipeManager.RenameFolder(RecipeFolderPath, args.OldRecipeName, args.NewRecipeName, out var error))
|
||||
{
|
||||
MwMessageBox.Show(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
recipeWrap.RecipeName = args.NewRecipeName;
|
||||
recipeWrap.ModifiedTime = DateTime.UtcNow;
|
||||
|
||||
if (CurrentRecipe != null && string.Equals(CurrentRecipe.RecipeName, args.OldRecipeName, StringComparison.Ordinal))
|
||||
{
|
||||
CurrentRecipe.RecipeName = args.NewRecipeName;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(recipeWrap, SelectedRecipeWrap))
|
||||
{
|
||||
SwitchActiveRecipe(args.NewRecipeName);
|
||||
}
|
||||
|
||||
CaptureSavedSnapshot();
|
||||
AfterRecipeRenamed(args.OldRecipeName, args.NewRecipeName);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void SaveCurrentRecipe(bool showSuccessMessage)
|
||||
{
|
||||
if (CurrentRecipe == null)
|
||||
return;
|
||||
|
||||
CurrentRecipe.Write();
|
||||
CaptureSavedSnapshot();
|
||||
|
||||
if (SelectedRecipeWrap != null)
|
||||
{
|
||||
SelectedRecipeWrap.ModifiedTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
if (showSuccessMessage)
|
||||
{
|
||||
MwMessageBox.Show("<22><><EFBFBD><EFBFBD><EFBFBD>ɹ<EFBFBD>");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AreSnapshotsEquivalent(string leftSnapshot, string rightSnapshot)
|
||||
{
|
||||
var left = string.IsNullOrWhiteSpace(leftSnapshot) ? null : ParseSnapshotToken(leftSnapshot);
|
||||
var right = string.IsNullOrWhiteSpace(rightSnapshot) ? null : ParseSnapshotToken(rightSnapshot);
|
||||
|
||||
if (left == null || right == null)
|
||||
return false;
|
||||
|
||||
return JToken.DeepEquals(left, right);
|
||||
}
|
||||
|
||||
private static JToken ParseSnapshotToken(string snapshot)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JToken.Parse(snapshot);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void SelectRecipeWrapInternal(RecipeWrap recipeWrap)
|
||||
{
|
||||
try
|
||||
{
|
||||
_skipLeaveCheck = true;
|
||||
SelectedRecipeWrap = recipeWrap;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_skipLeaveCheck = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void InitializeSelection(string activeRecipeName)
|
||||
{
|
||||
SyncSelection(activeRecipeName, true);
|
||||
}
|
||||
|
||||
protected virtual void UpdateInUseState(RecipeWrap activeRecipeWrap)
|
||||
{
|
||||
foreach (var wrap in RecipeWraps)
|
||||
{
|
||||
wrap.IsInUse = ReferenceEquals(wrap, activeRecipeWrap);
|
||||
}
|
||||
}
|
||||
|
||||
public void SyncSelectionFromActiveRecipe()
|
||||
{
|
||||
SyncSelection(GetActiveRecipeName(), false);
|
||||
}
|
||||
|
||||
private void SyncSelection(string recipeName, bool fallbackToFirst)
|
||||
{
|
||||
if (RecipeWraps == null || RecipeWraps.Count == 0)
|
||||
{
|
||||
UpdateInUseState(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var target = string.IsNullOrWhiteSpace(recipeName)
|
||||
? null
|
||||
: RecipeWraps.FirstOrDefault(p => string.Equals(p.RecipeName, recipeName, StringComparison.Ordinal));
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
UpdateInUseState(null);
|
||||
if (!fallbackToFirst)
|
||||
return;
|
||||
|
||||
target = RecipeWraps[0];
|
||||
}
|
||||
|
||||
if (ReferenceEquals(SelectedRecipeWrap, target))
|
||||
{
|
||||
UpdateInUseState(target);
|
||||
return;
|
||||
}
|
||||
|
||||
SelectRecipeWrapInternal(target);
|
||||
}
|
||||
|
||||
private void LoadRecipeFromWrap(RecipeWrap recipeWrap)
|
||||
{
|
||||
if (recipeWrap == null)
|
||||
{
|
||||
UpdateInUseStateByActiveRecipe();
|
||||
return;
|
||||
}
|
||||
|
||||
var newRecipe = new TRecipe
|
||||
{
|
||||
RecipeName = recipeWrap.RecipeName
|
||||
};
|
||||
newRecipe.Read();
|
||||
|
||||
CurrentRecipe = newRecipe;
|
||||
CaptureSavedSnapshot();
|
||||
UpdateInUseStateByActiveRecipe();
|
||||
OnRecipeLoaded(newRecipe, recipeWrap);
|
||||
}
|
||||
|
||||
private void UpdateInUseStateByActiveRecipe()
|
||||
{
|
||||
var activeRecipeName = GetActiveRecipeName();
|
||||
var activeRecipeWrap = RecipeWraps == null || string.IsNullOrWhiteSpace(activeRecipeName)
|
||||
? null
|
||||
: RecipeWraps.FirstOrDefault(p => string.Equals(p.RecipeName, activeRecipeName, StringComparison.Ordinal));
|
||||
|
||||
UpdateInUseState(activeRecipeWrap);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Recipe.Models.SubstrateParameter;
|
||||
using MaxwellFramework.Core.Common.Command;
|
||||
using Stylet;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class SubstrateHeightMeasureViewModel : PropertyChangedBase
|
||||
{
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
|
||||
public SubstrateHeightMeasureViewModel(HardwareManager hardwareManager)
|
||||
{
|
||||
_hardwareManager = hardwareManager;
|
||||
AddPointCmd = new DelegateCommand(AddPoint);
|
||||
DeletePointCmd = new DelegateCommand(DeletePoint);
|
||||
TeachPointCmd = new DelegateCommand(TeachPoint);
|
||||
ApplyOffsetToAllCmd = new DelegateCommand(ApplyOffsetToAll);
|
||||
}
|
||||
|
||||
private SubstrateRecipe _substrateRecipe;
|
||||
public SubstrateRecipe SubstrateRecipe
|
||||
{
|
||||
get { return _substrateRecipe; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _substrateRecipe, value))
|
||||
{
|
||||
NotifyOfPropertyChange(() => HeightMeasureSetting);
|
||||
NotifyOfPropertyChange(() => CommonOffsetCompensation);
|
||||
NotifyOfPropertyChange(() => Points);
|
||||
NotifyOfPropertyChange(() => Mode);
|
||||
NotifyOfPropertyChange(() => IsTeachMode);
|
||||
NotifyOfPropertyChange(() => IsRowColumnMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SubstrateHeightMeasureSetting HeightMeasureSetting => SubstrateRecipe?.HeightMeasureSetting;
|
||||
|
||||
public SubstrateHeightMeasureMode Mode
|
||||
{
|
||||
get { return HeightMeasureSetting == null ? SubstrateHeightMeasureMode.StandardTeachPosition : HeightMeasureSetting.Mode; }
|
||||
set
|
||||
{
|
||||
if (HeightMeasureSetting == null || HeightMeasureSetting.Mode == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HeightMeasureSetting.Mode = value;
|
||||
NotifyOfPropertyChange(() => Mode);
|
||||
NotifyOfPropertyChange(() => IsTeachMode);
|
||||
NotifyOfPropertyChange(() => IsRowColumnMode);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTeachMode
|
||||
{
|
||||
get { return Mode == SubstrateHeightMeasureMode.StandardTeachPosition; }
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
Mode = SubstrateHeightMeasureMode.StandardTeachPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRowColumnMode
|
||||
{
|
||||
get { return Mode == SubstrateHeightMeasureMode.RowColumnOffset; }
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
Mode = SubstrateHeightMeasureMode.RowColumnOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public System.Collections.ObjectModel.ObservableCollection<SubstrateHeightMeasurePoint> Points => HeightMeasureSetting?.Points;
|
||||
public MPoint CommonOffsetCompensation => HeightMeasureSetting?.CommonOffsetCompensation;
|
||||
|
||||
private SubstrateHeightMeasurePoint _selectedPoint;
|
||||
public SubstrateHeightMeasurePoint SelectedPoint
|
||||
{
|
||||
get { return _selectedPoint; }
|
||||
set { SetAndNotify(ref _selectedPoint, value); }
|
||||
}
|
||||
|
||||
public ICommand AddPointCmd { get; }
|
||||
public ICommand DeletePointCmd { get; }
|
||||
public ICommand TeachPointCmd { get; }
|
||||
public ICommand ApplyOffsetToAllCmd { get; }
|
||||
|
||||
private void ApplyOffsetToAll(object obj)
|
||||
{
|
||||
if (Points == null || CommonOffsetCompensation == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var point in Points)
|
||||
{
|
||||
if (point.OffsetCompensation == null)
|
||||
{
|
||||
point.OffsetCompensation = new MPoint();
|
||||
}
|
||||
|
||||
point.OffsetCompensation.X = CommonOffsetCompensation.X;
|
||||
point.OffsetCompensation.Y = CommonOffsetCompensation.Y;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddPoint(object obj)
|
||||
{
|
||||
if (Points == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var point = new SubstrateHeightMeasurePoint
|
||||
{
|
||||
PointName = $"Point{Points.Count + 1}",
|
||||
TeachPosition = new MPoint(),
|
||||
OffsetCompensation = new MPoint()
|
||||
};
|
||||
Points.Add(point);
|
||||
SelectedPoint = point;
|
||||
}
|
||||
|
||||
private void DeletePoint(object obj)
|
||||
{
|
||||
if (Points == null || SelectedPoint == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Points.Remove(SelectedPoint);
|
||||
SelectedPoint = Points.LastOrDefault();
|
||||
}
|
||||
|
||||
private void TeachPoint(object obj)
|
||||
{
|
||||
if (SelectedPoint == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var device = _hardwareManager.CameraAxisManager.TopCameraAxisDevices.FirstOrDefault();
|
||||
if (device == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedPoint.TeachPosition == null)
|
||||
{
|
||||
SelectedPoint.TeachPosition = new MPoint();
|
||||
}
|
||||
|
||||
if (device.AxisX != null)
|
||||
{
|
||||
SelectedPoint.TeachPosition.X = device.AxisX.State.ActualPos;
|
||||
}
|
||||
|
||||
if (device.AxisY != null)
|
||||
{
|
||||
SelectedPoint.TeachPosition.Y = device.AxisY.State.ActualPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Filewritable;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Recipe.Services;
|
||||
using MaxwellFramework.Core.Common.Command;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Stylet;
|
||||
using StyletIoC;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class SubstrateRecipeViewModel : RecipeViewModelBase<SubstrateRecipe>, IHandle<WaferNameChangedEventArgs>
|
||||
{
|
||||
private readonly RecipeWrapManager _wrapManager;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
|
||||
private SubstrateRecipe _substrateRecipe;
|
||||
public SubstrateRecipe SubstrateRecipe
|
||||
{
|
||||
get { return _substrateRecipe; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _substrateRecipe, value))
|
||||
{
|
||||
if (SubstrateTeachViewModel != null)
|
||||
{
|
||||
SubstrateTeachViewModel.SubstrateRecipe = value;
|
||||
}
|
||||
|
||||
if (SubstrateHeightMeasureViewModel != null)
|
||||
{
|
||||
SubstrateHeightMeasureViewModel.SubstrateRecipe = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override SubstrateRecipe CurrentRecipe
|
||||
{
|
||||
get => SubstrateRecipe;
|
||||
set => SubstrateRecipe = value;
|
||||
}
|
||||
|
||||
protected override string RecipeFolderPath => Paths.SubstrateRecipe;
|
||||
protected override string EmptyRecipeDisplayName => "当前基板配方";
|
||||
|
||||
protected override string GetActiveRecipeName()
|
||||
{
|
||||
return RecipeManager.CurrentSubstrateRecipe?.RecipeName;
|
||||
}
|
||||
|
||||
protected override void SwitchActiveRecipe(string recipeName)
|
||||
{
|
||||
RecipeManager.SwitchSubstrateRecipe(recipeName);
|
||||
}
|
||||
|
||||
protected override void ClearActiveRecipe()
|
||||
{
|
||||
RecipeManager.ClearSubstrateRecipe();
|
||||
}
|
||||
|
||||
private int _selectedTabIndex;
|
||||
/// <summary>
|
||||
/// 当前选中的 Tab 索引:0=基础设置(显示配方列表),1=参数示教(隐藏配方列表)
|
||||
/// </summary>
|
||||
public int SelectedTabIndex
|
||||
{
|
||||
get => _selectedTabIndex;
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _selectedTabIndex, value))
|
||||
HideRecipePanel = value != 0;
|
||||
}
|
||||
}
|
||||
|
||||
private SubstrateTeachViewModel _substrateTeachViewModel;
|
||||
[Inject]
|
||||
public SubstrateTeachViewModel SubstrateTeachViewModel
|
||||
{
|
||||
get { return _substrateTeachViewModel; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _substrateTeachViewModel, value) && value != null)
|
||||
{
|
||||
value.SubstrateRecipe = SubstrateRecipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SubstrateHeightMeasureViewModel _substrateHeightMeasureViewModel;
|
||||
[Inject]
|
||||
public SubstrateHeightMeasureViewModel SubstrateHeightMeasureViewModel
|
||||
{
|
||||
get { return _substrateHeightMeasureViewModel; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _substrateHeightMeasureViewModel, value) && value != null)
|
||||
{
|
||||
value.SubstrateRecipe = SubstrateRecipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<RecipeWrap> WaferRecipes => _wrapManager.WaferRecipeWraps;
|
||||
|
||||
public SubstrateRecipeViewModel(RecipeWrapManager wrapManager, IEventAggregator eventAggregator, RecipeManager recipeManager)
|
||||
: base(recipeManager)
|
||||
{
|
||||
_wrapManager = wrapManager;
|
||||
RecipeWraps = wrapManager.SubstrateRecipeWraps;
|
||||
RegisterButtonGroupEvents();
|
||||
_eventAggregator = eventAggregator;
|
||||
_eventAggregator.Unsubscribe(this);
|
||||
_eventAggregator.Subscribe(this);
|
||||
|
||||
DeleteSelectWaferInfoCmd = new DelegateCommand(DeleteSelectWaferInfo);
|
||||
}
|
||||
|
||||
#region//Commands
|
||||
public ICommand DeleteSelectWaferInfoCmd { get; private set; }
|
||||
#endregion
|
||||
|
||||
protected override string GetUnsavedChangesMessage()
|
||||
{
|
||||
var differenceSummary = GetRecipeDifferenceSummary();
|
||||
return string.IsNullOrWhiteSpace(differenceSummary)
|
||||
? $"配方“{CurrentRecipeDisplayName}”有未保存修改,是否先保存?"
|
||||
: $"配方“{CurrentRecipeDisplayName}”有未保存修改:\r\n{differenceSummary}\r\n是否先保存?";
|
||||
}
|
||||
|
||||
protected override void OnViewLoaded()
|
||||
{
|
||||
base.OnViewLoaded();
|
||||
InitializeSelection(RecipeManager.CurrentSubstrateRecipe?.RecipeName);
|
||||
}
|
||||
|
||||
protected override void OnRecipeLoaded(SubstrateRecipe recipe, RecipeWrap recipeWrap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void AfterDeleteCurrentRecipe(string deletedRecipeName)
|
||||
{
|
||||
RecipeManager.DeleteSubstrateRecipe(deletedRecipeName);
|
||||
_eventAggregator.Publish(new SubstrateDeletedEventArgs
|
||||
{
|
||||
DeletedName = deletedRecipeName,
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnCopyRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
base.OnCopyRecipe(sender, eventArgs);
|
||||
}
|
||||
|
||||
public override void OnImportRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs args)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnExportRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs args)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnReNameRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeRenameEventArgs args)
|
||||
{
|
||||
if (TryRenameRecipe(args))
|
||||
{
|
||||
_eventAggregator.Publish(new SubstrateNameChangedEventArgs
|
||||
{
|
||||
OldName = args.OldRecipeName,
|
||||
NewName = args.NewRecipeName,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(WaferNameChangedEventArgs message)
|
||||
{
|
||||
}
|
||||
|
||||
private string GetRecipeDifferenceSummary()
|
||||
{
|
||||
if (SubstrateRecipe == null)
|
||||
return string.Empty;
|
||||
|
||||
var path = System.IO.Path.Combine(SubstrateRecipe.Dir ?? string.Empty, SubstrateRecipe.FileName ?? string.Empty);
|
||||
if (!System.IO.File.Exists(path))
|
||||
return "1. 当前配方文件尚未保存到磁盘";
|
||||
|
||||
var currentSnapshot = SubstrateRecipe.CreateSnapshot();
|
||||
if (string.Equals(LastSavedSnapshot ?? string.Empty, currentSnapshot, StringComparison.Ordinal))
|
||||
return string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var originalToken = JToken.Parse(LastSavedSnapshot ?? string.Empty);
|
||||
var currentToken = JToken.Parse(currentSnapshot);
|
||||
var differences = new List<string>();
|
||||
CollectDifferences(originalToken, currentToken, string.Empty, differences, 6);
|
||||
|
||||
if (differences.Count == 0)
|
||||
return "1. 当前配方内容与上次保存快照不一致";
|
||||
|
||||
return string.Join("\r\n", differences);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "1. 当前配方内容与上次保存快照不一致";
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectDifferences(JToken originalToken, JToken currentToken, string path, IList<string> differences, int maxCount)
|
||||
{
|
||||
if (differences.Count >= maxCount)
|
||||
return;
|
||||
|
||||
if (JToken.DeepEquals(originalToken, currentToken))
|
||||
return;
|
||||
|
||||
if (originalToken == null || currentToken == null)
|
||||
{
|
||||
differences.Add($"{differences.Count + 1}. {FormatDifference(path, originalToken, currentToken)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (originalToken.Type == JTokenType.Object && currentToken.Type == JTokenType.Object)
|
||||
{
|
||||
var originalObject = (JObject)originalToken;
|
||||
var currentObject = (JObject)currentToken;
|
||||
var propertyNames = originalObject.Properties().Select(p => p.Name)
|
||||
.Union(currentObject.Properties().Select(p => p.Name))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
foreach (var propertyName in propertyNames)
|
||||
{
|
||||
var nextPath = string.IsNullOrWhiteSpace(path) ? propertyName : $"{path}.{propertyName}";
|
||||
CollectDifferences(originalObject[propertyName], currentObject[propertyName], nextPath, differences, maxCount);
|
||||
if (differences.Count >= maxCount)
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (originalToken.Type == JTokenType.Array && currentToken.Type == JTokenType.Array)
|
||||
{
|
||||
var originalArray = (JArray)originalToken;
|
||||
var currentArray = (JArray)currentToken;
|
||||
var maxLength = Math.Max(originalArray.Count, currentArray.Count);
|
||||
|
||||
for (var i = 0; i < maxLength; i++)
|
||||
{
|
||||
var nextPath = $"{path}[{i}]";
|
||||
var originalItem = i < originalArray.Count ? originalArray[i] : null;
|
||||
var currentItem = i < currentArray.Count ? currentArray[i] : null;
|
||||
CollectDifferences(originalItem, currentItem, nextPath, differences, maxCount);
|
||||
if (differences.Count >= maxCount)
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
differences.Add($"{differences.Count + 1}. {FormatDifference(path, originalToken, currentToken)}");
|
||||
}
|
||||
|
||||
private string FormatDifference(string path, JToken originalToken, JToken currentToken)
|
||||
{
|
||||
var displayPath = string.IsNullOrWhiteSpace(path) ? "配方根节点" : path;
|
||||
return $"{displayPath}:{FormatTokenValue(originalToken)} -> {FormatTokenValue(currentToken)}";
|
||||
}
|
||||
|
||||
private string FormatTokenValue(JToken token)
|
||||
{
|
||||
if (token == null || token.Type == JTokenType.Null || token.Type == JTokenType.Undefined)
|
||||
return "空";
|
||||
|
||||
var value = token.ToString(Newtonsoft.Json.Formatting.None);
|
||||
if (value.Length > 80)
|
||||
return value.Substring(0, 80) + "...";
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
#region//Methods
|
||||
public void AddWaferSelectInfo()
|
||||
{
|
||||
if (SubstrateRecipe == null)
|
||||
{
|
||||
MwMessageBox.Show("请先选择或创建基板配方");
|
||||
return;
|
||||
}
|
||||
SubstrateRecipe.WaferSelectInfos.Add(new Models.SubstrateParameter.WaferSelectInfo());
|
||||
}
|
||||
|
||||
private void DeleteSelectWaferInfo(object obj)
|
||||
{
|
||||
if (obj is Models.SubstrateParameter.WaferSelectInfo info)
|
||||
{
|
||||
SubstrateRecipe.WaferSelectInfos.Remove(info);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Recipe.Services;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class SubstrateTeachViewModel : CameraBaseViewModel
|
||||
{
|
||||
|
||||
private SubstrateRecipe _substrateRecipe;
|
||||
public SubstrateRecipe SubstrateRecipe
|
||||
{
|
||||
get { return _substrateRecipe; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _substrateRecipe, value))
|
||||
{
|
||||
if (MarkTeachViewModel != null)
|
||||
{
|
||||
MarkTeachViewModel.SubstrateRecipe = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MarkTeachViewModel _markTeachViewModel;
|
||||
public MarkTeachViewModel MarkTeachViewModel
|
||||
{
|
||||
get { return _markTeachViewModel; }
|
||||
set { SetAndNotify(ref _markTeachViewModel, value); }
|
||||
}
|
||||
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly IWindowManager _windowManager;
|
||||
public SubstrateTeachViewModel(HardwareManager hardwareManager, IWindowManager windowManager)
|
||||
{
|
||||
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
|
||||
_windowManager = windowManager ?? throw new ArgumentNullException(nameof(windowManager));
|
||||
_cameraAxisViewModel = IoC.Get<Common.Display.ViewModel.CameraAxisViewModel>();
|
||||
MarkTeachViewModel = new MarkTeachViewModel(hardwareManager, _windowManager);
|
||||
_cameraAxisViewModel.CameraAxisDevices.HardwareDeviceList = hardwareManager.CameraAxisManager.TopCameraAxisDevices;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
using MainShell.Filewritable;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using Stylet;
|
||||
using StyletIoC;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class WaferRecipeViewModel : RecipeViewModelBase<WaferRecipe>, IHandle<ProcessNameChangedEventArgs>
|
||||
{
|
||||
private WaferRecipe _waferRecipe;
|
||||
public WaferRecipe WaferRecipe
|
||||
{
|
||||
get { return _waferRecipe; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _waferRecipe, value))
|
||||
{
|
||||
if (WaferTeachViewModel != null)
|
||||
{
|
||||
WaferTeachViewModel.WaferRecipe = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override WaferRecipe CurrentRecipe
|
||||
{
|
||||
get => WaferRecipe;
|
||||
set => WaferRecipe = value;
|
||||
}
|
||||
|
||||
protected override string RecipeFolderPath => Paths.WaferRecipe;
|
||||
protected override string EmptyRecipeDisplayName => "当前芯片配方";
|
||||
|
||||
protected override string GetActiveRecipeName()
|
||||
{
|
||||
return RecipeManager.CurrentWaferRecipe?.RecipeName;
|
||||
}
|
||||
|
||||
private bool _skipProcessSelectionWriteBack;
|
||||
private int _selectedTabIndex;
|
||||
|
||||
public int SelectedTabIndex
|
||||
{
|
||||
get { return _selectedTabIndex; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _selectedTabIndex, value))
|
||||
{
|
||||
HideRecipePanel = value != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private WaferTeachViewModel _waferTeachViewModel;
|
||||
[Inject]
|
||||
public WaferTeachViewModel WaferTeachViewModel
|
||||
{
|
||||
get { return _waferTeachViewModel; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _waferTeachViewModel, value) && value != null)
|
||||
{
|
||||
value.WaferRecipe = WaferRecipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RecipeWrap _selectedProcessRecipeWrap;
|
||||
public RecipeWrap SelectedProcessRecipeWrap
|
||||
{
|
||||
get { return _selectedProcessRecipeWrap; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _selectedProcessRecipeWrap, value))
|
||||
{
|
||||
if (!_skipProcessSelectionWriteBack && WaferRecipe != null)
|
||||
{
|
||||
WaferRecipe.ProcessRecipeName = value != null ? value.RecipeName : string.Empty;
|
||||
NotifyOfPropertyChange(() => HasUnsavedChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<RecipeWrap> ProcessRecipes => _wrapManager.ProcessRecipeWraps;
|
||||
|
||||
private readonly RecipeWrapManager _wrapManager;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
|
||||
public WaferRecipeViewModel(RecipeWrapManager wrapManager, IEventAggregator eventAggregator, RecipeManager recipeManager)
|
||||
: base(recipeManager)
|
||||
{
|
||||
_wrapManager = wrapManager;
|
||||
_eventAggregator = eventAggregator;
|
||||
RegisterButtonGroupEvents();
|
||||
RecipeWraps = wrapManager.WaferRecipeWraps;
|
||||
|
||||
_eventAggregator.Unsubscribe(this);
|
||||
_eventAggregator.Subscribe(this);
|
||||
}
|
||||
|
||||
protected override void OnViewLoaded()
|
||||
{
|
||||
base.OnViewLoaded();
|
||||
InitializeSelection(RecipeManager.CurrentWaferRecipe?.RecipeName);
|
||||
SelectedTabIndex = HideRecipePanel ? 1 : 0;
|
||||
}
|
||||
|
||||
protected override void SwitchActiveRecipe(string recipeName)
|
||||
{
|
||||
RecipeManager.SwitchWaferRecipe(recipeName);
|
||||
}
|
||||
|
||||
protected override void ClearActiveRecipe()
|
||||
{
|
||||
RecipeManager.ClearWaferRecipe();
|
||||
}
|
||||
|
||||
protected override void OnRecipeLoaded(WaferRecipe recipe, RecipeWrap recipeWrap)
|
||||
{
|
||||
SyncProcessSelectionFromWaferRecipe();
|
||||
}
|
||||
|
||||
protected override void AfterDeleteCurrentRecipe(string deletedRecipeName)
|
||||
{
|
||||
_eventAggregator.Publish(new WaferDeletedEventArgs
|
||||
{
|
||||
DeletedName = deletedRecipeName,
|
||||
});
|
||||
}
|
||||
|
||||
protected override void AfterRecipeRenamed(string oldRecipeName, string newRecipeName)
|
||||
{
|
||||
_eventAggregator.Publish(new WaferNameChangedEventArgs
|
||||
{
|
||||
OldName = oldRecipeName,
|
||||
NewName = newRecipeName,
|
||||
});
|
||||
|
||||
SyncProcessSelectionFromWaferRecipe();
|
||||
}
|
||||
|
||||
public override void OnCopyRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
base.OnCopyRecipe(sender, eventArgs);
|
||||
}
|
||||
|
||||
public override void OnImportRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs args)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnExportRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs args)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnReNameRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeRenameEventArgs args)
|
||||
{
|
||||
TryRenameRecipe(args);
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(ProcessNameChangedEventArgs message)
|
||||
{
|
||||
if (message == null || string.IsNullOrWhiteSpace(message.OldName) || string.IsNullOrWhiteSpace(message.NewName))
|
||||
return;
|
||||
|
||||
foreach (RecipeWrap wrap in RecipeWraps)
|
||||
{
|
||||
WaferRecipe recipe = new WaferRecipe
|
||||
{
|
||||
RecipeName = wrap.RecipeName
|
||||
};
|
||||
recipe.Read();
|
||||
|
||||
if (string.Equals(recipe.ProcessRecipeName, message.OldName, StringComparison.Ordinal))
|
||||
{
|
||||
recipe.ProcessRecipeName = message.NewName;
|
||||
recipe.Write();
|
||||
}
|
||||
}
|
||||
|
||||
if (WaferRecipe != null && string.Equals(WaferRecipe.ProcessRecipeName, message.OldName, StringComparison.Ordinal))
|
||||
{
|
||||
WaferRecipe.ProcessRecipeName = message.NewName;
|
||||
CaptureSavedSnapshot();
|
||||
}
|
||||
|
||||
SyncProcessSelectionFromWaferRecipe();
|
||||
}
|
||||
|
||||
private void SyncProcessSelectionFromWaferRecipe()
|
||||
{
|
||||
if (WaferRecipe == null || ProcessRecipes == null)
|
||||
{
|
||||
SetSelectedProcessRecipeWrapInternal(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(WaferRecipe.ProcessRecipeName))
|
||||
{
|
||||
SetSelectedProcessRecipeWrapInternal(null);
|
||||
return;
|
||||
}
|
||||
|
||||
RecipeWrap processWrap = ProcessRecipes.FirstOrDefault(p => p.RecipeName == WaferRecipe.ProcessRecipeName);
|
||||
if (processWrap != null)
|
||||
{
|
||||
SetSelectedProcessRecipeWrapInternal(processWrap);
|
||||
return;
|
||||
}
|
||||
|
||||
SetSelectedProcessRecipeWrapInternal(null);
|
||||
}
|
||||
|
||||
private void SetSelectedProcessRecipeWrapInternal(RecipeWrap recipeWrap)
|
||||
{
|
||||
try
|
||||
{
|
||||
_skipProcessSelectionWriteBack = true;
|
||||
SelectedProcessRecipeWrap = recipeWrap;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_skipProcessSelectionWriteBack = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void MakeTemplate()
|
||||
{
|
||||
if (WaferTeachViewModel != null)
|
||||
{
|
||||
WaferTeachViewModel.MakeTemplate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Common.Display.ViewModel;
|
||||
using MainShell.Common.ViewModel;
|
||||
using MainShell.Common.VisionTemple.ViewModel;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Log;
|
||||
using MainShell.Models;
|
||||
using MainShell.Motion;
|
||||
using MainShell.Recipe.Models;
|
||||
using MwFramework.Device;
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class WaferTeachViewModel : CameraBaseViewModel
|
||||
{
|
||||
private WaferRecipe _waferRecipe;
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly IWindowManager _windowManager;
|
||||
private readonly SafeAxisMotion _safeAxisMotion;
|
||||
private readonly VisionTempleWindowViewModel _visionTempleWindowViewModel;
|
||||
|
||||
public WaferRecipe WaferRecipe
|
||||
{
|
||||
get { return _waferRecipe; }
|
||||
set { SetAndNotify(ref _waferRecipe, value); }
|
||||
}
|
||||
|
||||
public WaferTeachViewModel(
|
||||
HardwareManager hardwareManager,
|
||||
IWindowManager windowManager,
|
||||
SafeAxisMotion safeAxisMotion,
|
||||
VisionTempleWindowViewModel visionTempleWindowViewModel,
|
||||
CameraAxisViewModel cameraAxisViewModel)
|
||||
{
|
||||
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
|
||||
_windowManager = windowManager ?? throw new ArgumentNullException(nameof(windowManager));
|
||||
_safeAxisMotion = safeAxisMotion ?? throw new ArgumentNullException(nameof(safeAxisMotion));
|
||||
_visionTempleWindowViewModel = visionTempleWindowViewModel ?? throw new ArgumentNullException(nameof(visionTempleWindowViewModel));
|
||||
CameraAxisViewModel = cameraAxisViewModel ?? throw new ArgumentNullException(nameof(cameraAxisViewModel));
|
||||
CameraAxisViewModel.CameraAxisDevices.HardwareDeviceList = _hardwareManager.CameraAxisManager.TopCameraWsAxisDevices;
|
||||
}
|
||||
|
||||
public void TeachStartPoint()
|
||||
{
|
||||
try
|
||||
{
|
||||
IAxis axisX;
|
||||
IAxis axisY;
|
||||
if (!TryGetScanAxes(out axisX, out axisY))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WaferRecipe.ScanSettings.StartPointX = GetAxisPosition(axisX);
|
||||
WaferRecipe.ScanSettings.StartPointY = GetAxisPosition(axisY);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.LogSysError(ex);
|
||||
ShowOperationFailed(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveToStartPoint()
|
||||
{
|
||||
try
|
||||
{
|
||||
IAxis axisX;
|
||||
IAxis axisY;
|
||||
if (!TryGetScanAxes(out axisX, out axisY))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_safeAxisMotion.SafeMove(
|
||||
MotionMoveRequest.ForAxis(axisX, WaferRecipe.ScanSettings.StartPointX),
|
||||
MotionMoveRequest.ForAxis(axisY, WaferRecipe.ScanSettings.StartPointY));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.LogSysError(ex);
|
||||
ShowOperationFailed(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenVisionParameterSetting()
|
||||
{
|
||||
CameraConfig cameraConfig = new CameraConfig();
|
||||
UpCamLightConfig lightConfig = new UpCamLightConfig();
|
||||
CameraSettingsViewModel settingsViewModel = new CameraSettingsViewModel(_windowManager, _hardwareManager);
|
||||
settingsViewModel.Initialize(cameraConfig, lightConfig, CameraType.TopWsCamera);
|
||||
_windowManager.ShowDialog(settingsViewModel);
|
||||
}
|
||||
|
||||
public void MakeTemplate()
|
||||
{
|
||||
try
|
||||
{
|
||||
_visionTempleWindowViewModel.ShowWindow();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.LogSysError(ex);
|
||||
LocalizedMessageBox.ShowFormat(
|
||||
MessageKey.OriginCalibOpenVisionTemplateFailed,
|
||||
MessageKey.TitleError,
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error,
|
||||
ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetScanAxes(out IAxis axisX, out IAxis axisY)
|
||||
{
|
||||
axisX = null;
|
||||
axisY = null;
|
||||
|
||||
if (WaferRecipe == null || WaferRecipe.ScanSettings == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CameraAxisViewModel == null || CameraAxisViewModel.CameraAxisDevices == null || CameraAxisViewModel.CameraAxisDevices.SelectedHardwareDevice == null)
|
||||
{
|
||||
LocalizedMessageBox.Show(MessageKey.DeviceNotInitialized, MessageKey.TitleWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
HardwareDevice selectedHardwareDevice = CameraAxisViewModel.CameraAxisDevices.SelectedHardwareDevice;
|
||||
axisX = selectedHardwareDevice.AxisX;
|
||||
axisY = selectedHardwareDevice.AxisY;
|
||||
if (axisX == null || axisY == null)
|
||||
{
|
||||
LocalizedMessageBox.Show(MessageKey.DeviceNotInitialized, MessageKey.TitleWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static double GetAxisPosition(IAxis axis)
|
||||
{
|
||||
if (axis == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(axis));
|
||||
}
|
||||
|
||||
return axis.State != null ? axis.State.ActualPos : axis.GetPositionImmediate();
|
||||
}
|
||||
|
||||
private static void ShowOperationFailed(string message)
|
||||
{
|
||||
string title = LanguageResourceHelper.GetString(MessageKey.TitleError);
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
MwMessageBox.Show(LanguageResourceHelper.GetString(MessageKey.CommonOperationFailed), title, MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
MwMessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user