using Aitex.Core.RT.Log; using Caliburn.Micro; using Caliburn.Micro.Core; using MECF.Framework.Common.DataCenter; using MECF.Framework.UI.Client.CenterViews.Editors; using MECF.Framework.UI.Client.CenterViews.Editors.Recipe; using MECF.Framework.UI.Client.CenterViews.Editors.Sequence; using MECF.Framework.UI.Client.ClientBase; using OpenSEMI.ClientBase; using OpenSEMI.ClientBase.Command; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; using MECF.Framework.UI.Client.CenterViews.Configs.Roles; using MECF.Framework.UI.Client.CenterViews.Core; using MECF.Framework.UI.Client.RecipeEditorLib.DGExtension.CustomColumn; using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel; using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel.Params; using Sicentury.Core; using Action = System.Action; using DataGridCellEditEndingEventArgs = ExtendedGrid.Microsoft.Windows.Controls.DataGridCellEditEndingEventArgs; using DataGridRowEventArgs = ExtendedGrid.Microsoft.Windows.Controls.DataGridRowEventArgs; using Sicentury.Core.Collections; namespace SicUI.Models.RecipeEditors { public class RecipeEditorViewModel : UiViewModelBase, IHandle //BaseModel { #region Variables private readonly IEventAggregator _eventAggregator; private RecipeData _currentRecipe; private bool _isRunPurgeSwitch; private bool _isTmaNotInstall; private bool _isHClNotInstall; private bool _isHClFlowNotInstall; private bool _isLoading; private Window _winValidationInfo; private EditMode _editMode; private int _errorsCount; private readonly RecipeFormatBuilder _columnBuilder; private readonly RecipeProvider _recipeProvider; private ObservableCollection _processTypeFileList; private readonly ObservableRangeCollection _validationResultList; private Point? _lastPosOfValidationWin = null; #endregion #region Constructors public RecipeEditorViewModel() { _eventAggregator = IoC.Get(); _eventAggregator?.Subscribe(this); _columnBuilder = new RecipeFormatBuilder(); _recipeProvider = new RecipeProvider(); _validationResultList = new ObservableRangeCollection(); } #endregion #region Commands private ICommand _RenameFolderCommand; public ICommand RenameFolderCommand { get { if (_RenameFolderCommand == null) _RenameFolderCommand = new BaseCommand(() => RenameFolder()); return _RenameFolderCommand; } } private ICommand _DeleteFolderCommand; public ICommand DeleteFolderCommand { get { if (_DeleteFolderCommand == null) _DeleteFolderCommand = new BaseCommand(() => DeleteFolder()); return _DeleteFolderCommand; } } private ICommand _NewFolderCommand; public ICommand NewFolderCommand { get { if (_NewFolderCommand == null) _NewFolderCommand = new BaseCommand(() => NewFolder()); return _NewFolderCommand; } } private ICommand _NewFolderRootCommand; public ICommand NewFolderRootCommand { get { if (_NewFolderRootCommand == null) _NewFolderRootCommand = new BaseCommand(() => NewFolderRoot()); return _NewFolderRootCommand; } } private ICommand _NewRecipeCommand; public ICommand NewRecipeCommand { get { if (_NewRecipeCommand == null) _NewRecipeCommand = new BaseCommand(() => NewRecipe()); return _NewRecipeCommand; } } private ICommand _NewRecipeRootCommand; public ICommand NewRecipeRootCommand { get { if (_NewRecipeRootCommand == null) _NewRecipeRootCommand = new BaseCommand(() => NewRecipeRoot()); return _NewRecipeRootCommand; } } private ICommand _RenameRecipeCommand; public ICommand RenameRecipeCommand { get { if (_RenameRecipeCommand == null) _RenameRecipeCommand = new BaseCommand(() => RenameRecipe()); return _RenameRecipeCommand; } } private ICommand _DeleteRecipeCommand; public ICommand DeleteRecipeCommand { get { if (_DeleteRecipeCommand == null) _DeleteRecipeCommand = new BaseCommand(() => DeleteRecipe()); return _DeleteRecipeCommand; } } private ICommand _SaveAsRecipeCommand; public ICommand SaveAsRecipeCommand { get { if (_SaveAsRecipeCommand == null) _SaveAsRecipeCommand = new BaseCommand(() => SaveAsRecipe()); return _SaveAsRecipeCommand; } } #endregion #region Properties public ObservableCollection ProcessTypeFileList { get => _processTypeFileList; set { _processTypeFileList = value; NotifyOfPropertyChange(nameof(ProcessTypeFileList)); } } public RecipeData CurrentRecipe { get => _currentRecipe; set { _currentRecipe = value; NotifyOfPropertyChange(nameof(CurrentRecipe)); } } public bool IsPermission => Permission == 3; //&& RtStatus != "AutoRunning"; public FileNode CurrentFileNode { get; set; } private bool IsChanged => _editMode == EditMode.Edit || CurrentRecipe.IsChanged; public ObservableCollection Columns { get; set; } public Dictionary> DicColunms { get; set; } public bool EnableNew { get; set; } public bool EnableReName { get; set; } public bool EnableCopy { get; set; } public bool EnableDelete { get; set; } public bool EnableSave { get; set; } public bool EnableStep { get; set; } public bool EnableReload { get; set; } public bool EnableSaveToAll { get; set; } public bool EnableSaveTo { get; set; } public int ErrorsCount { set { // 如果错误数没发生变化,不要修改Badge的值,以免触发Badge动画。 if (value == _errorsCount) return; if(value == 0) ((RecipeEditorView)View).txtErrorCount.Badge = null; else ((RecipeEditorView)View).txtErrorCount.Badge = value; _errorsCount = value; } } /// /// 是否正在加载配方。 /// public bool IsLoading { get => _isLoading; private set { _isLoading = value; NotifyOfPropertyChange(nameof(IsLoading)); } } public RecipeStepCollection SelectedRecipeSteps { get; set; } public ICollectionView ParamValidationInfoCollection => CollectionViewSource.GetDefaultView(_validationResultList); public ObservableCollection ChamberType { get; set; } public int ChamberTypeIndexSelection { get; set; } public int ProcessTypeIndexSelection { get; set; } public string CurrentChamberType => ChamberType[ChamberTypeIndexSelection]; public string CurrentProcessType => ProcessTypeFileList[ProcessTypeIndexSelection].ProcessType; public Visibility MultiChamberVisibility { get; set; } public ObservableCollection Chambers { get; set; } public string SelectedChamber { get; set; } public object View { get; set; } #endregion protected override void OnInitialize() { base.OnInitialize(); var chamberType = QueryDataClient.Instance.Service.GetConfig("System.Recipe.SupportedChamberType"); if (chamberType == null) { ChamberType = new ObservableCollection() { "Default" }; } else { ChamberType = new ObservableCollection(((string)(chamberType)).Split(',')); } ChamberTypeIndexSelection = 0; var processType = "Process,Routine,Clean"; ProcessTypeFileList = new ObservableCollection(); var recipeProcessType = ((string)processType).Split(','); for (var i = 0; i < recipeProcessType.Length; i++) { var type = new ProcessTypeFileItem(); type.ProcessType = recipeProcessType[i]; var prefix = $"{ChamberType[ChamberTypeIndexSelection]}\\{recipeProcessType[i]}"; var recipes = _recipeProvider.GetXmlRecipeList(prefix); type.FileListByProcessType = RecipeSequenceTreeBuilder.BuildFileNode(prefix, "", false, recipes)[0].Files; type.FilterFileListByProcessType = type.FileListByProcessType; ProcessTypeFileList.Add(type); } DicColunms = new Dictionary>(); UpdateRecipeFormat(); } protected override void OnActivate() { //初始化RoleManager var roleManager = new RoleManager(); roleManager.Initialize(); //得到当前登录的RoleItem var roleItem = roleManager.GetRoleByName(BaseApp.Instance.UserContext.RoleName); var menuPermission = new MenuPermission(); menuPermission.ParsePermission(roleItem.Role.MenuPermission); foreach (var col in Columns) { if (col.ControlName == "StepNo") continue; RecipeFormatBuilder.SetPermission(col, menuPermission); } //this.Columns = this._columnBuilder.Build($"{CurrentChamberType}\\{CurrentProcessType}", SelectedChamber, true, ClientApp.Instance.UserContext.RoleName); base.OnActivate(); } protected override void OnDeactivate(bool close) { base.OnDeactivate(close); if (IsChanged) { if (DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No, DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} content is changed, do you want to save it?") == DialogButton.Yes) { SaveRecipe(); } } _winValidationInfo?.Close(); // 锁定编辑器 ((RecipeEditorView)View).editorLocker.Lock(); } protected override void OnViewLoaded(object view) { View = view; base.OnViewLoaded(view); RecipeFormatBuilder.ApplyTemplate((UserControl)view, Columns); var u = (RecipeEditorView)view; u.dgCustom.Columns.Clear(); Columns.Apply((c) => { c.Header = c; u.dgCustom.Columns.Add(c); }); u.dgCustom.ItemsSource = CurrentRecipe.Steps; u.dgCustom.LostFocus += DgCustom_LostFocus; u.dgCustom.CellEditEnding += DgCustomOnCellEditEnding; u.dgCustom.LoadingRow += DgCustomOnLoadingRow; u.dgCustom.FrozenColumnCount = 4; } private void DgCustomOnLoadingRow(object sender, DataGridRowEventArgs e) { if (sender is XDataGrid dg && e.Row.DataContext is RecipeStep step) { for (var i = 0; i < step.Count; i++) { step[i].RowOwner = e.Row; step[i].ColumnOwner = dg.Columns[i]; } } } private void DgCustomOnCellEditEnding(object sender, DataGridCellEditEndingEventArgs e) { // ValidateEntireRecipe(); if (SelectedRecipeSteps != null && SelectedRecipeSteps.Count == 1) { CalRecipeParameterForRunVent(SelectedRecipeSteps[0]); CheckIfAllCellsValid(out _); } } private void DgCustom_LostFocus(object sender, RoutedEventArgs e) { // ValidateEntireRecipe(); if (SelectedRecipeSteps != null && SelectedRecipeSteps.Count == 1) { CalRecipeParameterForRunVent(SelectedRecipeSteps[0]); CheckIfAllCellsValid(out _); } } /// /// 校验整个Recipe。 /// private void ValidateEntireRecipe() { _isRunPurgeSwitch = false; _isTmaNotInstall = false; _isHClNotInstall = false; _isHClFlowNotInstall = false; CurrentRecipe.RecipeTotalTime = 0; for (var selectIndex = 0; selectIndex < CurrentRecipe.Steps.Count; selectIndex++) { var previousIndex = selectIndex == 0 ? 0 : selectIndex - 1; var currentIndex = selectIndex; var nextIndex = selectIndex == CurrentRecipe.Steps.Count - 1 ? selectIndex : selectIndex + 1; var previousStep = CurrentRecipe.Steps[previousIndex]; var currentStep = CurrentRecipe.Steps[currentIndex]; var nextStep = CurrentRecipe.Steps[nextIndex]; //判断每步与上一步值是否相同,不同则以颜色区分 RenderStepsWithSameValue(previousStep, currentStep); //如果是Routine则在下面代码不执行,在这里continue if (CurrentProcessType == ProcessTypeFileList[1].ProcessType) { //如果是Routine,则总时间需*循环次数 var loopTimes = ((DoubleParam)currentStep[8]).Value <= 0 ? 1 : (int)((DoubleParam)currentStep[8]).Value; CurrentRecipe.RecipeTotalTime += (int)((DoubleParam)currentStep[(int)RecipColNo.StepTime]).Value * loopTimes; continue; } else { //如果是Recipe则总时间直接加 CurrentRecipe.RecipeTotalTime += (int)((DoubleParam)currentStep[(int)RecipColNo.StepTime]).Value; } var stepTime = ((DoubleParam)currentStep[(int)RecipColNo.StepTime]).Value; PurgeRunSwitch(previousStep, currentStep, nextStep); NotInstallDevice(currentStep); /*if (stepTime > 0) {*/ CalRecipeParameterForRunVent(currentStep); //} // 校验单元格内容 foreach (var param in currentStep) { if (param is IParam p) p.Validate(); } } //如果是Routine则在下面代码不执行,在这里返回 if (CurrentProcessType == ProcessTypeFileList[1].ProcessType) { return; } if (_isRunPurgeSwitch) MessageBox.Show("Flow mode 'Run' and 'Purge' can't switch directly", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning); //if (_isTmaNotInstall) // MessageBox.Show("TMA is not install, only Purge mode is valid", "Warning", MessageBoxButton.OK, // MessageBoxImage.Warning); if (_isHClNotInstall) MessageBox.Show("GR Purge HCl is not install, only Disable is valid", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning); if (_isHClFlowNotInstall) MessageBox.Show("GR Purge HCl Flow is not install, lock the value as zero", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning); } private static void RenderStepsWithSameValue(IReadOnlyList previousStep, IReadOnlyList currentStep) { for (var i = 0; i < currentStep.Count; i++) { if (currentStep[i] is DoubleParam dblParamCurr && previousStep[i] is DoubleParam dblParamPrev) { dblParamCurr.IsEqualsToPrevious = GlobalDefs.IsDoubleValueEquals(dblParamCurr.Value, dblParamPrev.Value); } else if (currentStep[i] is ComboxParam cbxParamCurr && previousStep[i] is ComboxParam cbxParamPrev) { if (cbxParamCurr.Value != cbxParamPrev.Value) { cbxParamCurr.IsEqualsToPrevious = false; } else { cbxParamCurr.IsEqualsToPrevious = true; } } } } [Obsolete("验证过程已在FlowModeParam.Validate()中实现,保留此函数仅做测试使用,后续版本将被删除。")] private void PurgeRunSwitch(RecipeStep previousStep, RecipeStep currentStep, RecipeStep nextStep) { var previousSiH4FlowMode = previousStep[(int)RecipColNo.SiH4FlowMode] as ComboxParam; var previousTcsFlowMode = previousStep[(int)RecipColNo.TCSFlowMode] as ComboxParam; var previousHClFlowMode = previousStep[(int)RecipColNo.HCLFlowMode] as ComboxParam; var previousC2H4FlowMode = previousStep[(int)RecipColNo.C2H4FlowMode] as ComboxParam; var previousN2FlowMode = previousStep[(int)RecipColNo.N2FlowMode] as ComboxParam; var previousN2HighFlowMode = previousStep[(int)RecipColNo.N2HighFlowMode] as ComboxParam; var previousTmaFlowMode = previousStep[(int)RecipColNo.TMAFlowMode] as ComboxParam; var currentSiH4FlowMode = currentStep[(int)RecipColNo.SiH4FlowMode] as ComboxParam; var currentTcsFlowMode = currentStep[(int)RecipColNo.TCSFlowMode] as ComboxParam; var currentHClFlowMode = currentStep[(int)RecipColNo.HCLFlowMode] as ComboxParam; var currentC2H4FlowMode = currentStep[(int)RecipColNo.C2H4FlowMode] as ComboxParam; var currentN2FlowMode = currentStep[(int)RecipColNo.N2FlowMode] as ComboxParam; var currentN2HighFlowMode = currentStep[(int)RecipColNo.N2HighFlowMode] as ComboxParam; var currentTmaFlowMode = currentStep[(int)RecipColNo.TMAFlowMode] as ComboxParam; var nextSiH4FlowMode = nextStep[(int)RecipColNo.SiH4FlowMode] as ComboxParam; var nextTcsFlowMode = nextStep[(int)RecipColNo.TCSFlowMode] as ComboxParam; var nextHClFlowMode = nextStep[(int)RecipColNo.HCLFlowMode] as ComboxParam; var nextC2H4FlowMode = nextStep[(int)RecipColNo.C2H4FlowMode] as ComboxParam; var nextN2FlowMode = nextStep[(int)RecipColNo.N2FlowMode] as ComboxParam; var nextN2HighFlowMode = nextStep[(int)RecipColNo.N2HighFlowMode] as ComboxParam; var nextTmaFlowMode = nextStep[(int)RecipColNo.TMAFlowMode] as ComboxParam; var previousFlowMode = new List() { previousSiH4FlowMode, previousTcsFlowMode, previousHClFlowMode, previousC2H4FlowMode, previousN2FlowMode, previousN2FlowMode, previousN2HighFlowMode, previousTmaFlowMode }; var currentFlowMode = new List() { currentSiH4FlowMode, currentTcsFlowMode, currentHClFlowMode, currentC2H4FlowMode, currentN2FlowMode, currentN2FlowMode, currentN2HighFlowMode, currentTmaFlowMode }; var nextFlowMode = new List() { nextSiH4FlowMode, nextTcsFlowMode, nextHClFlowMode, nextC2H4FlowMode, nextN2FlowMode, nextN2FlowMode, nextN2HighFlowMode, nextTmaFlowMode }; for (var i = 0; i < currentFlowMode.Count; i++) { if (string.Equals(previousFlowMode[i].Value, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) && string.Equals(currentFlowMode[i].Value, FlowModeParam.FlowModeEnum.Purge.ToString(), StringComparison.OrdinalIgnoreCase) || string.Equals(previousFlowMode[i].Value, FlowModeParam.FlowModeEnum.Purge.ToString(), StringComparison.OrdinalIgnoreCase) && string.Equals(currentFlowMode[i].Value, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase)) { _isRunPurgeSwitch = true; currentFlowMode[i].Value = FlowModeParam.FlowModeEnum.Vent.ToString(); } if (string.Equals(nextFlowMode[i].Value, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) && string.Equals(currentFlowMode[i].Value, FlowModeParam.FlowModeEnum.Purge.ToString(), StringComparison.OrdinalIgnoreCase) || string.Equals(nextFlowMode[i].Value, FlowModeParam.FlowModeEnum.Purge.ToString(), StringComparison.OrdinalIgnoreCase) && string.Equals(currentFlowMode[i].Value, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase)) { _isRunPurgeSwitch = true; currentFlowMode[i].Value = FlowModeParam.FlowModeEnum.Vent.ToString(); } } } private void NotInstallDevice(RecipeStep currentStep) { if (!(currentStep[(int)RecipColNo.TMAFlowMode] is ComboxParam currentTmaFlowMode)) throw new InvalidOperationException( $"The column {RecipColNo.TMAFlowMode} is not defined in recipe format."); if (string.Equals(currentTmaFlowMode.Value, FlowModeParam.FlowModeEnum.Purge.ToString(), StringComparison.OrdinalIgnoreCase)) return; currentTmaFlowMode.Value = FlowModeParam.FlowModeEnum.Purge.ToString(); _isTmaNotInstall = true; } #region Gas Flow Calculation private static double GetTcsFlow(RecipeStep currentStep, string selectedChamber) { var currentTcsBubbleLowFlow = ((DoubleParam)currentStep[(int)RecipColNo.TCSBubbLowFlow]).Value; var currentTcsBubbleHighFlow = ((DoubleParam)currentStep[(int)RecipColNo.TCSBubbHighFlow]).Value; var currentTcsPushFlow = ((DoubleParam)currentStep[(int)RecipColNo.TCSPushFlow]).Value; var currentTcsFlowMode = (currentStep[(int)RecipColNo.TCSFlowMode] as ComboxParam)?.Value; var tcsFlow = 0d; var tcsEff = (double)QueryDataClient.Instance.Service.GetConfig($"PM.{selectedChamber}.Efficiency.TCS-Eff"); if (string.Equals(currentTcsFlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) || string.Equals(currentTcsFlowMode, FlowModeParam.FlowModeEnum.Vent.ToString(), StringComparison.OrdinalIgnoreCase)) { tcsFlow = (currentTcsBubbleHighFlow + currentTcsBubbleLowFlow) * tcsEff + currentTcsPushFlow; } else if (string.Equals(currentTcsFlowMode, FlowModeParam.FlowModeEnum.Purge.ToString(), StringComparison.OrdinalIgnoreCase)) { tcsFlow = currentTcsBubbleHighFlow + currentTcsBubbleLowFlow + currentTcsPushFlow; } return tcsFlow; } private static double GetTmaFlow(RecipeStep currentStep, string selectedChamber) { var currentTmaBubbleFlow = ((DoubleParam)currentStep[(int)RecipColNo.TMABubbFlow]).Value; var currentTmaPushFlow = ((DoubleParam)currentStep[(int)RecipColNo.TMAPushFlow]).Value; var currentTmaFlowMode = ((ComboxParam)currentStep[(int)RecipColNo.TMAFlowMode]).Value; var tmaFlow = 0d; var tmaEff = (double)QueryDataClient.Instance.Service.GetConfig($"PM.{selectedChamber}.Efficiency.TMA-Eff"); if (string.Equals(currentTmaFlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) || string.Equals(currentTmaFlowMode, FlowModeParam.FlowModeEnum.Vent.ToString(), StringComparison.OrdinalIgnoreCase)) tmaFlow = currentTmaBubbleFlow * tmaEff + currentTmaPushFlow; else if (string.Equals(currentTmaFlowMode, FlowModeParam.FlowModeEnum.Purge.ToString(), StringComparison.OrdinalIgnoreCase)) tmaFlow = currentTmaBubbleFlow + currentTmaPushFlow; return tmaFlow; } private static void CalSiSourceFlow(RecipeStep currentStep) { var currentSiSourSplitRatio = new double[3]; if (currentStep[(int)RecipColNo.SiSourceSplitRatio] is Sets3RatioParam siSourceRatio) currentSiSourSplitRatio = siSourceRatio.Ratio; var currentSiSourTotalFlow = ((DoubleParam)currentStep[(int)RecipColNo.SiSourceTotalFlow]).Value; //double currentSiSourPushFlow = currentSiSourTotalFlow - (currentSiH4FlowMode == FlowMode.Run.ToString() ? currentSiH4Flow : 0) // - (currentTCSFlowMode == FlowMode.Run.ToString() ? TCSFlow : 0) - (currentHClFlowMode == FlowMode.Run.ToString() ? currentHClFlow : 0); //(currentStep[(int)RecipColNo.SiSourceOuterFlow] as DoubleParam).Value = currentSiSourPushFlow.ToString("F1"); double currentSiSourInnerFlow = 0; double currentSiSourMidFlow = 0; double currentSiSourOutFlow = 0; if (currentSiSourSplitRatio.Length == 3) { var rationSum = currentSiSourSplitRatio[0] + currentSiSourSplitRatio[1] + currentSiSourSplitRatio[2]; currentSiSourInnerFlow = currentSiSourTotalFlow / rationSum * currentSiSourSplitRatio[0]; currentSiSourMidFlow = currentSiSourTotalFlow / rationSum * currentSiSourSplitRatio[1]; currentSiSourOutFlow = currentSiSourTotalFlow / rationSum * currentSiSourSplitRatio[2]; } ((DoubleParam)currentStep[(int)RecipColNo.SiSourceInnerFlow]).Value = Math.Round(currentSiSourInnerFlow, 1); ((DoubleParam)currentStep[(int)RecipColNo.SiSourceMiddleFlow]).Value = Math.Round(currentSiSourMidFlow, 1); ((DoubleParam)currentStep[(int)RecipColNo.SiSourceOuterFlow]).Value = Math.Round(currentSiSourOutFlow, 1); } private static void CalCSourceFlow(RecipeStep currentStep) { var currentC2H4Flow = ((DoubleParam)currentStep[(int)RecipColNo.C2H4Flow]).Value; var currentCSourSplitRatio = new double[3]; if (currentStep[(int)RecipColNo.CSourceSplitRatio] is Sets3RatioParam cSourceRatio) currentCSourSplitRatio = cSourceRatio.Ratio; var currentCSourTotalFlow = ((DoubleParam)currentStep[(int)RecipColNo.CSourceTotalFlow]).Value; var currentC2H4FlowMode = (currentStep[(int)RecipColNo.C2H4FlowMode] as ComboxParam)?.Value; var currentCSourPushFlow = currentCSourTotalFlow - (string.Equals(currentC2H4FlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) ? currentC2H4Flow : 0d); ((DoubleParam)currentStep[(int)RecipColNo.CSourceOuterFlow]).Value = Math.Round(currentCSourPushFlow, 1); double currentCSourInnerFlow = 0; double currentCSourMidFlow = 0; double currentCSourOuterFlow = 0; if (currentCSourSplitRatio.Length == 3) { var ratioSum = currentCSourSplitRatio[0] + currentCSourSplitRatio[1] + currentCSourSplitRatio[2]; currentCSourInnerFlow = currentCSourTotalFlow / ratioSum * currentCSourSplitRatio[0]; currentCSourMidFlow = currentCSourTotalFlow / ratioSum * currentCSourSplitRatio[1]; currentCSourOuterFlow = currentCSourTotalFlow / ratioSum * currentCSourSplitRatio[2]; } ((DoubleParam)currentStep[(int)RecipColNo.CSourceInnerFlow]).Value = Math.Round(currentCSourInnerFlow, 1); ((DoubleParam)currentStep[(int)RecipColNo.CSourceMiddleFlow]).Value = Math.Round(currentCSourMidFlow, 1); ((DoubleParam)currentStep[(int)RecipColNo.CSourceOuterFlow]).Value = Math.Round(currentCSourOuterFlow, 1); } private static void CalDopeFlow(RecipeStep currentStep) { var currentDopeTotalFlow = ((DoubleParam)currentStep[(int)RecipColNo.DopeTotalFlow]).Value; var currentN2ActualFlow = ((DoubleParam)currentStep[(int)RecipColNo.N2ActualFlow]).Value; var currentN2LowFlow = ((DoubleParam)currentStep[(int)RecipColNo.N2LowFlow]).Value; var currentDiluFlowForN2 = ((DoubleParam)currentStep[(int)RecipColNo.DilutFlowForN2]).Value; var currentDilutedN2Flow = (currentN2LowFlow + currentDiluFlowForN2) * currentN2ActualFlow / currentN2LowFlow; ((DoubleParam)currentStep[(int)RecipColNo.DilutedN2Flow]).Value = Math.Round(currentDilutedN2Flow, 1); var currentDopeSplitRatio = new double[3]; if (currentStep[(int)RecipColNo.DopeSplitRatio] is Sets3RatioParam dopRatio) currentDopeSplitRatio = dopRatio.Ratio; //double currentDopePushFlow = currentDopeTotalFlow // - (currentN2FlowMode == FlowMode.Run.ToString() ? currentDilutedN2Flow : 0) // - (currentN2HighFlowMode == FlowMode.Run.ToString() ? currentN2HighFlow : 0) // - (currentTMAFlowMode == FlowMode.Run.ToString() ? TMAFlow : 0); //(currentStep[(int)RecipColNo.DopeOuterFlow] as DoubleParam).Value = currentDopePushFlow.ToString("F1"); double currentDopeInnerFlow = 0; double currentDopeMidFlow = 0; double currentDopeOuterFlow = 0; if (currentDopeSplitRatio.Length == 3) { var ratioSum = currentDopeSplitRatio[0] + currentDopeSplitRatio[1] + currentDopeSplitRatio[2]; currentDopeInnerFlow = currentDopeTotalFlow / ratioSum * currentDopeSplitRatio[0]; currentDopeMidFlow = currentDopeTotalFlow / ratioSum * currentDopeSplitRatio[1]; currentDopeOuterFlow = currentDopeTotalFlow / ratioSum * currentDopeSplitRatio[2]; } ((DoubleParam)currentStep[(int)RecipColNo.DopeInnerFlow]).Value = Math.Round(currentDopeInnerFlow, 1); ((DoubleParam)currentStep[(int)RecipColNo.DopeMiddleFlow]).Value = Math.Round(currentDopeMidFlow, 1); ((DoubleParam)currentStep[(int)RecipColNo.DopeOuterFlow]).Value = Math.Round(currentDopeOuterFlow, 1); } private static void CalShSupplementFlow(RecipeStep currentStep) { var currentSiSourTotalFlow = ((DoubleParam)currentStep[(int)RecipColNo.SiSourceTotalFlow]).Value; var currentCSourTotalFlow = ((DoubleParam)currentStep[(int)RecipColNo.CSourceTotalFlow]).Value; var currentDopeTotalFlow = ((DoubleParam)currentStep[(int)RecipColNo.DopeTotalFlow]).Value; var currentShTotalFlow = ((DoubleParam)currentStep[(int)RecipColNo.SHTotalFlow]).Value; var currentSurroundingFlow = ((DoubleParam)currentStep[(int)RecipColNo.CarrayGasFlow]).Value; var currentSiSourInnerFlow = ((DoubleParam)currentStep[(int)RecipColNo.SiSourceInnerFlow]).Value; var currentSiSourMidFlow = ((DoubleParam)currentStep[(int)RecipColNo.SiSourceMiddleFlow]).Value; var currentCSourInnerFlow = ((DoubleParam)currentStep[(int)RecipColNo.CSourceInnerFlow]).Value; var currentCSourMidFlow = ((DoubleParam)currentStep[(int)RecipColNo.CSourceMiddleFlow]).Value; var currentDopeInnerFlow = ((DoubleParam)currentStep[(int)RecipColNo.DopeInnerFlow]).Value; var currentDopeMidFlow = ((DoubleParam)currentStep[(int)RecipColNo.DopeMiddleFlow]).Value; var currentShTotalFlowSplitRatio = new double[3]; if (currentStep[(int)RecipColNo.SHTotalFlowSplitRatio] is Sets3RatioParam shTotalRatio) currentShTotalFlowSplitRatio = shTotalRatio.Ratio; var currentShSuppTotalFlow = currentShTotalFlow - currentSurroundingFlow - currentSiSourTotalFlow - currentCSourTotalFlow - currentDopeTotalFlow; var sumShTotalFlowSplitRatio = currentShTotalFlowSplitRatio[0] + currentShTotalFlowSplitRatio[1] + currentShTotalFlowSplitRatio[2]; var currentShInnerFlow = (currentShTotalFlow - currentSurroundingFlow) / sumShTotalFlowSplitRatio * currentShTotalFlowSplitRatio[0]; var currentShMidFlow = (currentShTotalFlow - currentSurroundingFlow) / sumShTotalFlowSplitRatio * currentShTotalFlowSplitRatio[1]; var currentShOutterFlow = (currentShTotalFlow - currentSurroundingFlow) / sumShTotalFlowSplitRatio * currentShTotalFlowSplitRatio[2]; var currentInnerSuppFlow = currentShInnerFlow - currentSiSourInnerFlow - currentCSourInnerFlow - currentDopeInnerFlow; var currentMidSuppFlow = currentShMidFlow - currentSiSourMidFlow - currentCSourMidFlow - currentDopeMidFlow; var currentOutterSuppFlow = currentShSuppTotalFlow - currentInnerSuppFlow - currentMidSuppFlow; ((DoubleParam)currentStep[(int)RecipColNo.SHPushTotalFlow]).Value = Math.Round(currentShSuppTotalFlow, 1); ((DoubleParam)currentStep[(int)RecipColNo.SHInnerFlow]).Value = Math.Round(currentShInnerFlow, 1); ((DoubleParam)currentStep[(int)RecipColNo.SHMiddleFlow]).Value = Math.Round(currentShMidFlow, 1); ((DoubleParam)currentStep[(int)RecipColNo.SHOuterFlow]).Value = Math.Round(currentShOutterFlow, 1); ((DoubleParam)currentStep[(int)RecipColNo.InnerPushFlow]).Value = Math.Round(currentInnerSuppFlow, 1); ((DoubleParam)currentStep[(int)RecipColNo.MiddlePushFlow]).Value = Math.Round(currentMidSuppFlow, 1); ((DoubleParam)currentStep[(int)RecipColNo.OuterPushFlow]).Value = Math.Round(currentOutterSuppFlow, 1); } private static void CalVentFlow(RecipeStep currentStep, string selectedChamber) { var currentSiH4Flow = ((DoubleParam)currentStep[(int)RecipColNo.SiH4Flow]).Value; var currentTotalVentFlow = ((DoubleParam)currentStep[(int)RecipColNo.TotalVentFlow]).Value; var currentHClFlow = ((DoubleParam)currentStep[(int)RecipColNo.HCLFlow]).Value; var currentN2HighFlow = ((DoubleParam)currentStep[(int)RecipColNo.N2HighFlow]).Value; var currentC2H4Flow = ((DoubleParam)currentStep[(int)RecipColNo.C2H4Flow]).Value; var currentDilutedN2Flow = ((DoubleParam)currentStep[(int)RecipColNo.DilutedN2Flow]).Value; var currentN2FlowMode = ((ComboxParam)currentStep[(int)RecipColNo.N2FlowMode]).Value; var currentN2HighFlowMode = ((ComboxParam)currentStep[(int)RecipColNo.N2HighFlowMode]).Value; var currentSiH4FlowMode = (currentStep[(int)RecipColNo.SiH4FlowMode] as ComboxParam)?.Value; var currentTcsFlowMode = (currentStep[(int)RecipColNo.TCSFlowMode] as ComboxParam)?.Value; var currentHClFlowMode = (currentStep[(int)RecipColNo.HCLFlowMode] as ComboxParam)?.Value; var currentC2H4FlowMode = (currentStep[(int)RecipColNo.C2H4FlowMode] as ComboxParam)?.Value; var currentTmaFlowMode = ((ComboxParam)currentStep[(int)RecipColNo.TMAFlowMode]).Value; var tcsFlow = GetTcsFlow(currentStep, selectedChamber); var tmaFlow = GetTmaFlow(currentStep, selectedChamber); var currentSiSourTotalFlowForPurge = (string.Equals(currentSiH4FlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) == false ? currentSiH4Flow : 0d) + (string.Equals(currentTcsFlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) == false ? tcsFlow : 0d) + (string.Equals(currentHClFlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) == false ? currentHClFlow : 0d); var currentCSourTotalFlowForPurge = string.Equals(currentC2H4FlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) == false ? currentC2H4Flow : 0d; var n2Flow = (string.Equals(currentN2FlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) == false ? currentDilutedN2Flow : 0d) + (string.Equals(currentN2HighFlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) == false ? currentN2HighFlow : 0d); var currentDopeTotalFlowForPurge = n2Flow + (string.Equals(currentTmaFlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) == false ? tmaFlow : 0d); var currentVentPushFlow = currentTotalVentFlow - currentSiSourTotalFlowForPurge - currentCSourTotalFlowForPurge - currentDopeTotalFlowForPurge; ((DoubleParam)currentStep[(int)RecipColNo.VentPushFlow]).Value = Math.Round(currentVentPushFlow, 1); } private void CalRecipeParameterForRunVent(RecipeStep currentStep) { /*if (!(currentStep.StepTime > 0)) return;*/ CalSiSourceFlow(currentStep); CalCSourceFlow(currentStep); CalDopeFlow(currentStep); CalShSupplementFlow(currentStep); CalVentFlow(currentStep, CurrentRecipe.Module); } [Obsolete("该函数已废除,请使用分步计算函数。")] private void CalRecipeParameterForRunVent(RecipeStep previousStep, IReadOnlyList currentStep, double stepTime) { #region SH Total flow var currentShTotalFlow = ((DoubleParam)currentStep[(int)RecipColNo.SHTotalFlow]).Value; var currentSurroundingFlow = ((DoubleParam)currentStep[(int)RecipColNo.CarrayGasFlow]).Value; var currentShTotalFlowSplitRatio = new double[3]; if (currentStep[(int)RecipColNo.SHTotalFlowSplitRatio] is Sets3RatioParam shTotalRatio) currentShTotalFlowSplitRatio = shTotalRatio.Ratio; #endregion #region Si Source var currentSiH4Flow = ((DoubleParam)currentStep[(int)RecipColNo.SiH4Flow]).Value; var currentTcsBubbleLowFlow = ((DoubleParam)currentStep[(int)RecipColNo.TCSBubbLowFlow]).Value; var currentTcsBubbleHighFlow = ((DoubleParam)currentStep[(int)RecipColNo.TCSBubbHighFlow]).Value; var currentTcsPushFlow = ((DoubleParam)currentStep[(int)RecipColNo.TCSPushFlow]).Value; var currentHClFlow = ((DoubleParam)currentStep[(int)RecipColNo.HCLFlow]).Value; var currentSiSourSplitRatio = new double[3]; if (currentStep[(int)RecipColNo.SiSourceSplitRatio] is Sets3RatioParam siSourceRatio) currentSiSourSplitRatio = siSourceRatio.Ratio; var currentSiSourTotalFlow = ((DoubleParam)currentStep[(int)RecipColNo.SiSourceTotalFlow]).Value; var currentSiH4FlowMode = (currentStep[(int)RecipColNo.SiH4FlowMode] as ComboxParam)?.Value; var currentTcsFlowMode = (currentStep[(int)RecipColNo.TCSFlowMode] as ComboxParam)?.Value; var currentHClFlowMode = (currentStep[(int)RecipColNo.HCLFlowMode] as ComboxParam)?.Value; double tcsFlow = 0d; var tcsEff = (double)QueryDataClient.Instance.Service.GetConfig($"PM.{SelectedChamber}.Efficiency.TCS-Eff"); if (string.Equals(currentTcsFlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) || string.Equals(currentTcsFlowMode, FlowModeParam.FlowModeEnum.Vent.ToString(), StringComparison.OrdinalIgnoreCase)) { tcsFlow = (currentTcsBubbleHighFlow + currentTcsBubbleLowFlow) * tcsEff + currentTcsPushFlow; } else if (string.Equals(currentTcsFlowMode, FlowModeParam.FlowModeEnum.Purge.ToString(), StringComparison.OrdinalIgnoreCase)) { tcsFlow = currentTcsBubbleHighFlow + currentTcsBubbleLowFlow + currentTcsPushFlow; } //double currentSiSourPushFlow = currentSiSourTotalFlow - (currentSiH4FlowMode == FlowMode.Run.ToString() ? currentSiH4Flow : 0) // - (currentTCSFlowMode == FlowMode.Run.ToString() ? TCSFlow : 0) - (currentHClFlowMode == FlowMode.Run.ToString() ? currentHClFlow : 0); //(currentStep[(int)RecipColNo.SiSourceOuterFlow] as DoubleParam).Value = currentSiSourPushFlow.ToString("F1"); double currentSiSourInnerFlow = 0; double currentSiSourMidFlow = 0; double currentSiSourOutFlow = 0; if (currentSiSourSplitRatio.Length == 3) { var rationSum = currentSiSourSplitRatio[0] + currentSiSourSplitRatio[1] + currentSiSourSplitRatio[2]; currentSiSourInnerFlow = currentSiSourTotalFlow / rationSum * currentSiSourSplitRatio[0]; currentSiSourMidFlow = currentSiSourTotalFlow / rationSum * currentSiSourSplitRatio[1]; currentSiSourOutFlow = currentSiSourTotalFlow / rationSum * currentSiSourSplitRatio[2]; } ((DoubleParam)currentStep[(int)RecipColNo.SiSourceInnerFlow]).Value = Math.Round(currentSiSourInnerFlow, 1); ((DoubleParam)currentStep[(int)RecipColNo.SiSourceMiddleFlow]).Value = Math.Round(currentSiSourMidFlow, 1); ((DoubleParam)currentStep[(int)RecipColNo.SiSourceOuterFlow]).Value = Math.Round(currentSiSourOutFlow, 1); #endregion #region C Source var currentC2H4Flow = ((DoubleParam)currentStep[(int)RecipColNo.C2H4Flow]).Value; var currentCSourSplitRatio = new double[3]; if (currentStep[(int)RecipColNo.CSourceSplitRatio] is Sets3RatioParam cSourceRatio) currentCSourSplitRatio = cSourceRatio.Ratio; var currentCSourTotalFlow = ((DoubleParam)currentStep[(int)RecipColNo.CSourceTotalFlow]).Value; var currentC2H4FlowMode = (currentStep[(int)RecipColNo.C2H4FlowMode] as ComboxParam)?.Value; var currentCSourPushFlow = currentCSourTotalFlow - (string.Equals(currentC2H4FlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) ? currentC2H4Flow : 0d); ((DoubleParam)currentStep[(int)RecipColNo.CSourceOuterFlow]).Value = Math.Round(currentCSourPushFlow, 1); double currentCSourInnerFlow = 0; double currentCSourMidFlow = 0; double currentCSourOuterFlow = 0; if (currentCSourSplitRatio.Length == 3) { var ratioSum = currentCSourSplitRatio[0] + currentCSourSplitRatio[1] + currentCSourSplitRatio[2]; currentCSourInnerFlow = currentCSourTotalFlow / ratioSum * currentCSourSplitRatio[0]; currentCSourMidFlow = currentCSourTotalFlow / ratioSum * currentCSourSplitRatio[1]; currentCSourOuterFlow = currentCSourTotalFlow / ratioSum * currentCSourSplitRatio[2]; } ((DoubleParam)currentStep[(int)RecipColNo.CSourceInnerFlow]).Value = Math.Round(currentCSourInnerFlow, 1); ((DoubleParam)currentStep[(int)RecipColNo.CSourceMiddleFlow]).Value = Math.Round(currentCSourMidFlow, 1); ((DoubleParam)currentStep[(int)RecipColNo.CSourceOuterFlow]).Value = Math.Round(currentCSourOuterFlow, 1); #endregion #region Dope var currentDopeTotalFlow = ((DoubleParam)currentStep[(int)RecipColNo.DopeTotalFlow]).Value; var currentN2ActualFlow = ((DoubleParam)currentStep[(int)RecipColNo.N2ActualFlow]).Value; var currentN2LowFlow = ((DoubleParam)currentStep[(int)RecipColNo.N2LowFlow]).Value; var currentDiluFlowForN2 = ((DoubleParam)currentStep[(int)RecipColNo.DilutFlowForN2]).Value; var currentN2HighFlow = ((DoubleParam)currentStep[(int)RecipColNo.N2HighFlow]).Value; var currentTmaBubbFlow = ((DoubleParam)currentStep[(int)RecipColNo.TMABubbFlow]).Value; var currentTmaPushFlow = ((DoubleParam)currentStep[(int)RecipColNo.TMAPushFlow]).Value; var currentDilutedN2Flow = (currentN2LowFlow + currentDiluFlowForN2) * currentN2ActualFlow / currentN2LowFlow; ((DoubleParam)currentStep[(int)RecipColNo.DilutedN2Flow]).Value = Math.Round(currentDilutedN2Flow, 1); var currentDopeSplitRatio = new double[3]; if (currentStep[(int)RecipColNo.DopeSplitRatio] is Sets3RatioParam dopRatio) currentDopeSplitRatio = dopRatio.Ratio; var currentN2FlowMode = ((ComboxParam)currentStep[(int)RecipColNo.N2FlowMode]).Value; var currentN2HighFlowMode = ((ComboxParam)currentStep[(int)RecipColNo.N2HighFlowMode]).Value; var currentTmaFlowMode = ((ComboxParam)currentStep[(int)RecipColNo.TMAFlowMode]).Value; double tmaFlow = 0; var tmaEff = (double)QueryDataClient.Instance.Service.GetConfig($"PM.{SelectedChamber}.Efficiency.TMA-Eff"); if (string.Equals(currentTmaFlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) || string.Equals(currentTmaFlowMode, FlowModeParam.FlowModeEnum.Vent.ToString(), StringComparison.OrdinalIgnoreCase)) tmaFlow = currentTmaBubbFlow * tmaEff + currentTmaPushFlow; else if (string.Equals(currentTmaFlowMode, FlowModeParam.FlowModeEnum.Purge.ToString(), StringComparison.OrdinalIgnoreCase)) tmaFlow = currentTmaBubbFlow + currentTmaPushFlow; //double currentDopePushFlow = currentDopeTotalFlow // - (currentN2FlowMode == FlowMode.Run.ToString() ? currentDilutedN2Flow : 0) // - (currentN2HighFlowMode == FlowMode.Run.ToString() ? currentN2HighFlow : 0) // - (currentTMAFlowMode == FlowMode.Run.ToString() ? TMAFlow : 0); //(currentStep[(int)RecipColNo.DopeOuterFlow] as DoubleParam).Value = currentDopePushFlow.ToString("F1"); double currentDopeInnerFlow = 0; double currentDopeMidFlow = 0; double currentDopeOuterFlow = 0; if (currentDopeSplitRatio.Length == 3) { var ratioSum = currentDopeSplitRatio[0] + currentDopeSplitRatio[1] + currentDopeSplitRatio[2]; currentDopeInnerFlow = currentDopeTotalFlow / ratioSum * currentDopeSplitRatio[0]; currentDopeMidFlow = currentDopeTotalFlow / ratioSum * currentDopeSplitRatio[1]; currentDopeOuterFlow = currentDopeTotalFlow / ratioSum * currentDopeSplitRatio[2]; } (currentStep[(int)RecipColNo.DopeInnerFlow] as DoubleParam).Value = Math.Round(currentDopeInnerFlow, 1); (currentStep[(int)RecipColNo.DopeMiddleFlow] as DoubleParam).Value = Math.Round(currentDopeMidFlow, 1); (currentStep[(int)RecipColNo.DopeOuterFlow] as DoubleParam).Value = Math.Round(currentDopeOuterFlow, 1); #endregion #region SH Supp. Flow var currentShSuppTotalFlow = currentShTotalFlow - currentSurroundingFlow - currentSiSourTotalFlow - currentCSourTotalFlow - currentDopeTotalFlow; var sumShTotalFlowSplitRatio = currentShTotalFlowSplitRatio[0] + currentShTotalFlowSplitRatio[1] + currentShTotalFlowSplitRatio[2]; var currentShInnerFlow = (currentShTotalFlow - currentSurroundingFlow) / sumShTotalFlowSplitRatio * currentShTotalFlowSplitRatio[0]; var currentShMidFlow = (currentShTotalFlow - currentSurroundingFlow) / sumShTotalFlowSplitRatio * currentShTotalFlowSplitRatio[1]; var currentShOutterFlow = (currentShTotalFlow - currentSurroundingFlow) / sumShTotalFlowSplitRatio * currentShTotalFlowSplitRatio[2]; var currentInnerSuppFlow = currentShInnerFlow - currentSiSourInnerFlow - currentCSourInnerFlow - currentDopeInnerFlow; var currentMidSuppFlow = currentShMidFlow - currentSiSourMidFlow - currentCSourMidFlow - currentDopeMidFlow; var currentOutterSuppFlow = currentShSuppTotalFlow - currentInnerSuppFlow - currentMidSuppFlow; (currentStep[(int)RecipColNo.SHPushTotalFlow] as DoubleParam).Value = Math.Round(currentShSuppTotalFlow, 1); (currentStep[(int)RecipColNo.SHInnerFlow] as DoubleParam).Value = Math.Round(currentShInnerFlow, 1); (currentStep[(int)RecipColNo.SHMiddleFlow] as DoubleParam).Value = Math.Round(currentShMidFlow, 1); (currentStep[(int)RecipColNo.SHOuterFlow] as DoubleParam).Value = Math.Round(currentShOutterFlow, 1); (currentStep[(int)RecipColNo.InnerPushFlow] as DoubleParam).Value = Math.Round(currentInnerSuppFlow, 1); (currentStep[(int)RecipColNo.MiddlePushFlow] as DoubleParam).Value = Math.Round(currentMidSuppFlow, 1); (currentStep[(int)RecipColNo.OuterPushFlow] as DoubleParam).Value = Math.Round(currentOutterSuppFlow, 1); #endregion #region Vent Flow var currentTotalVentFlow = (currentStep[(int)RecipColNo.TotalVentFlow] as DoubleParam).Value; var currentSiSourTotalFlowForPurge = (string.Equals(currentSiH4FlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) == false ? currentSiH4Flow : 0d) + (string.Equals(currentTcsFlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) == false ? tcsFlow : 0d) + (string.Equals(currentHClFlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) == false ? currentHClFlow : 0d); var currentCSourTotalFlowForPurge = string.Equals(currentC2H4FlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) == false ? currentC2H4Flow : 0d; var n2Flow = (string.Equals(currentN2FlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) == false ? currentDilutedN2Flow : 0d) + (string.Equals(currentN2HighFlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) == false ? currentN2HighFlow : 0d); var currentDopeTotalFlowForPurge = n2Flow + (string.Equals(currentTmaFlowMode, FlowModeParam.FlowModeEnum.Run.ToString(), StringComparison.OrdinalIgnoreCase) == false ? tmaFlow : 0d); var currentVentPushFlow = currentTotalVentFlow - currentSiSourTotalFlowForPurge - currentCSourTotalFlowForPurge - currentDopeTotalFlowForPurge; (currentStep[(int)RecipColNo.VentPushFlow] as DoubleParam).Value = Math.Round(currentVentPushFlow, 1); #endregion } #endregion public void TabSelectionChanged() { UpdateRecipeFormat(); OnViewLoaded(View); } public void UpdateRecipeFormat() { var chamber = QueryDataClient.Instance.Service.GetConfig("System.Recipe.ChamberModules"); if (chamber == null) { chamber = "PM1"; } Chambers = new ObservableCollection(((string)chamber).Split(',')); if (Chambers.Count > 1) { for (var i = 0; i < Chambers.Count; i++) { var isPmInstall = (bool)QueryDataClient.Instance.Service.GetConfig( $"System.SetUp.Is{Chambers[i].ToString()}Installed"); { if (!isPmInstall) { Chambers.RemoveAt(i); } } } } if (Chambers.Count == 0) { Chambers = new ObservableCollection(new string[] { "PM1" }); } SelectedChamber = Chambers[0]; if (DicColunms.Keys.Contains(CurrentProcessType)) { Columns = DicColunms[CurrentProcessType]; } else { Columns = _columnBuilder.Build($"{CurrentChamberType}\\{CurrentProcessType}", SelectedChamber, true, BaseApp.Instance.UserContext.RoleName); DicColunms[CurrentProcessType] = Columns; } CurrentRecipe = new RecipeData(); CurrentRecipe.RecipeChamberType = _columnBuilder.RecipeChamberType; CurrentRecipe.RecipeVersion = _columnBuilder.RecipeVersion; _editMode = EditMode.None; MultiChamberVisibility = Chambers.Count > 1 ? Visibility.Visible : Visibility.Collapsed; } /// /// Chamber变更时重新加载Recipe。 /// public async void ChamberSelectionChanged() { if (IsChanged) { var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No, DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?"); if (selection == DialogButton.Yes) { CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName; CurrentRecipe.ReviseTime = DateTime.Now; Save(CurrentRecipe, false); } } IsLoading = true; await CurrentRecipe.ChangeChamber(Columns , _columnBuilder.Configs, SelectedChamber, GetLoadingDispatcher()); IsLoading = false; } /// /// 左侧Recipe列表选择改变时,重新加载Recipe。 /// /// public async void TreeSelectChanged(FileNode node) { if (IsChanged) { var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No, DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?"); if (selection == DialogButton.Yes) { CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName; CurrentRecipe.ReviseTime = DateTime.Now; Save(CurrentRecipe, false); } } CurrentFileNode = node; if (node != null && node.IsFile) { await LoadData(node.PrefixPath, node.FullPath); } else { ClearData(); _editMode = EditMode.None; } UpdateView(); } #region folder public void NewFolder() { if (IsChanged) { var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel, DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?"); if (selection == DialogButton.Cancel) return; if (selection == DialogButton.Yes) { CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName; CurrentRecipe.ReviseTime = DateTime.Now; Save(CurrentRecipe, false); } } var dialog = new InputFileNameDialogViewModel("Input New Folder Name"); dialog.FileName = "new folder"; var wm = new WindowManager(); var dialogReturn = wm.ShowDialog(dialog); if (!dialogReturn.HasValue || !dialogReturn.Value) return; var name = dialog.FileName.Trim(); if (string.IsNullOrEmpty(name)) { DialogBox.ShowWarning("Folder name should not be empty"); return; } var prefix = ChamberType[ChamberTypeIndexSelection] + "\\" + ProcessTypeFileList[ProcessTypeIndexSelection].ProcessType; var processType = string.Empty; var newFolder = string.Empty; if (CurrentFileNode != null) { prefix = CurrentFileNode.PrefixPath; var folder = CurrentFileNode.FullPath; if (CurrentFileNode.IsFile) { folder = folder.Substring(0, folder.LastIndexOf("\\") + 1); if (!string.IsNullOrEmpty(folder)) newFolder = folder; } else { newFolder = folder + "\\"; } } newFolder = newFolder + name; if (IsExist(newFolder, false)) { DialogBox.ShowWarning($"Can not create folder {newFolder}, Folder with the same name already exist."); return; } if (newFolder.Length > 200) { DialogBox.ShowWarning($"Can not create folder {newFolder}, Folder name too long, should be less 200."); return; } _recipeProvider.CreateRecipeFolder(prefix, newFolder); ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, newFolder, true); } public void NewFolderRoot() { if (IsChanged) { var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel, DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?"); if (selection == DialogButton.Cancel) return; if (selection == DialogButton.Yes) { CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName; CurrentRecipe.ReviseTime = DateTime.Now; Save(CurrentRecipe, false); } } var dialog = new InputFileNameDialogViewModel("Input New Folder Name"); dialog.FileName = "new folder"; var wm = new WindowManager(); var dialogReturn = wm.ShowDialog(dialog); if (!dialogReturn.HasValue || !dialogReturn.Value) return; var name = dialog.FileName.Trim(); if (string.IsNullOrEmpty(name)) { DialogBox.ShowWarning("Folder name should not be empty"); return; } if (IsExist(name, false)) { DialogBox.ShowWarning($"Can not create folder {name}, Folder with the same name already exist."); return; } if (name.Length > 200) { DialogBox.ShowWarning($"Can not create folder {name}, Folder name too long, should be less 200."); return; } var prefix = ChamberType[ChamberTypeIndexSelection] + "\\" + ProcessTypeFileList[ProcessTypeIndexSelection].ProcessType; _recipeProvider.CreateRecipeFolder(prefix, name); ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, name, true); } public void DeleteFolder() { if (CurrentFileNode == null || CurrentFileNode.IsFile) return; if (CurrentFileNode.Files.Count > 0) { DialogBox.ShowWarning( $"Can not delete non-empty folder, Remove the files or folders under \r\n{CurrentFileNode.FullPath}."); return; } var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No, DialogType.CONFIRM, $"Are you sure you want to delete \r\n {CurrentFileNode.FullPath}?"); if (selection == DialogButton.No) return; var nextFocus = CurrentFileNode.Parent.FullPath; var isFolder = true; if (CurrentFileNode.Parent.Files.Count > 1) { for (var i = 0; i < CurrentFileNode.Parent.Files.Count; i++) { if (CurrentFileNode.Parent.Files[i] == CurrentFileNode) { if (i == 0) { nextFocus = CurrentFileNode.Parent.Files[i + 1].FullPath; isFolder = !CurrentFileNode.Parent.Files[i + 1].IsFile; } else { nextFocus = CurrentFileNode.Parent.Files[i - 1].FullPath; isFolder = !CurrentFileNode.Parent.Files[i - 1].IsFile; } } } } _recipeProvider.DeleteRecipeFolder(CurrentFileNode.PrefixPath, CurrentFileNode.FullPath); ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, nextFocus, isFolder); } public void RenameFolder() { if (CurrentFileNode == null || CurrentFileNode.IsFile) return; var dialog = new InputFileNameDialogViewModel("Input New Folder Name"); dialog.FileName = CurrentFileNode.Name; var wm = new WindowManager(); var dialogReturn = wm.ShowDialog(dialog); if (!dialogReturn.HasValue || !dialogReturn.Value) return; var name = dialog.FileName.Trim(); if (string.IsNullOrEmpty(name)) return; var newFolder = CurrentFileNode.FullPath.Substring(0, CurrentFileNode.FullPath.LastIndexOf("\\") + 1); if (!string.IsNullOrEmpty(newFolder)) newFolder = newFolder + name; else newFolder = name; if (newFolder == CurrentFileNode.FullPath) return; if (IsExist(newFolder, false)) { DialogBox.ShowWarning($"Can not rename to {newFolder}, Folder with the same name already exist."); return; } if (newFolder.Length > 200) { DialogBox.ShowWarning($"Can not create folder {newFolder}, Folder name too long, should be less 200."); return; } _recipeProvider.RenameFolder(CurrentFileNode.PrefixPath, CurrentFileNode.FullPath, newFolder); ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, newFolder, true); } #endregion #region recipe public void NewRecipe() { if (IsChanged) { var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel, DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?"); if (selection == DialogButton.Cancel) return; if (selection == DialogButton.Yes) { CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName; CurrentRecipe.ReviseTime = DateTime.Now; Save(CurrentRecipe, false); } } var dialog = new InputFileNameDialogViewModel("Input New Name"); dialog.FileName = ""; var wm = new WindowManager(); var dialogReturn = wm.ShowDialog(dialog); if (!dialogReturn.HasValue || !dialogReturn.Value) return; var recipeName = dialog.FileName.Trim(); if (string.IsNullOrEmpty(dialog.FileName)) { DialogBox.ShowWarning("Recipe file name should not be empty"); return; } var prefix = CurrentChamberType + "\\" + CurrentProcessType; var processType = string.Empty; if (CurrentFileNode != null) { var folder = CurrentFileNode.FullPath; if (CurrentFileNode.IsFile) { folder = folder.Substring(0, folder.LastIndexOf("\\") + 1); //if (!string.IsNullOrEmpty(folder)) // folder = folder; } else { folder = folder + "\\"; } recipeName = folder + recipeName; } if (IsExist(recipeName, true)) { DialogBox.ShowWarning($"Can not create {recipeName}, Recipe with the same name already exist."); return; } if (recipeName.Length > 200) { DialogBox.ShowWarning($"Can not create folder {recipeName}, Folder name too long, should be less 200."); return; } var recipe = new RecipeData(); recipe.Name = recipeName; recipe.PrefixPath = prefix; recipe.Creator = BaseApp.Instance.UserContext.LoginName; recipe.CreateTime = DateTime.Now; recipe.Revisor = BaseApp.Instance.UserContext.LoginName; recipe.ReviseTime = DateTime.Now; recipe.Description = string.Empty; if (!Save(recipe, true)) return; var types = prefix.Split('\\'); ReloadRecipeFileList(types[0], types[1], recipeName, false); } public void NewRecipeRoot() { if (IsChanged) { var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel, DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?"); if (selection == DialogButton.Cancel) return; if (selection == DialogButton.Yes) { CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName; CurrentRecipe.ReviseTime = DateTime.Now; Save(CurrentRecipe, false); } } var dialog = new InputFileNameDialogViewModel("Input New Recipe Name"); dialog.FileName = "new recipe"; var wm = new WindowManager(); var dialogReturn = wm.ShowDialog(dialog); if (!dialogReturn.HasValue || !dialogReturn.Value) return; var recipeName = dialog.FileName.Trim(); if (string.IsNullOrEmpty(dialog.FileName)) { DialogBox.ShowWarning("Recipe file name should not be empty"); return; } if (IsExist(recipeName, true)) { DialogBox.ShowWarning($"Can not create {recipeName}, Recipe with the same name already exist."); return; } if (recipeName.Length > 200) { DialogBox.ShowWarning($"Can not create folder {recipeName}, Folder name too long, should be less 200."); return; } var recipe = new RecipeData(); recipe.Name = recipeName; recipe.PrefixPath = CurrentChamberType + "\\" + CurrentProcessType; recipe.Creator = BaseApp.Instance.UserContext.LoginName; recipe.CreateTime = DateTime.Now; recipe.Revisor = BaseApp.Instance.UserContext.LoginName; recipe.ReviseTime = DateTime.Now; recipe.Description = string.Empty; if (!Save(recipe, true)) return; ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, recipeName, false); } private void ReloadRecipeFileList(string chamberType, string processType, string selectedFile, bool selectionIsFolder) { var item = ProcessTypeFileList.FirstOrDefault(x => x.ProcessType == processType); if (item == null) { LOG.Write("error reload recipe file list, type = " + processType); } var prefix = $"{ChamberType[ChamberTypeIndexSelection]}\\{item.ProcessType}"; var recipes = _recipeProvider.GetXmlRecipeList(prefix); item.FileListByProcessType = RecipeSequenceTreeBuilder.BuildFileNode(prefix, selectedFile, selectionIsFolder, recipes)[0].Files; item.FilterFileListByProcessType = item.FileListByProcessType; item.InvokePropertyChanged(); } private bool IsExist(string fullPath, bool isFile) { for (var i = 0; i < ProcessTypeFileList.Count; i++) { if (ProcessTypeFileList[i].ProcessType == CurrentProcessType) { if (ProcessTypeFileList[i].FileListByProcessType.Count == 0) return false; return FindFile(fullPath, ProcessTypeFileList[i].FileListByProcessType[0].Parent, isFile); } } return true; } private bool FindFile(string path, FileNode root, bool isFile) { if (root.FullPath == path && !isFile) { return true; } foreach (var node in root.Files) { if (isFile && node.IsFile && node.FullPath == path) return true; if (!node.IsFile && FindFile(path, node, isFile)) return true; } return false; } public void SaveAsRecipe() { if (CurrentFileNode == null || !CurrentFileNode.IsFile) return; if (IsChanged) { var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel, DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?"); if (selection == DialogButton.Cancel) return; if (selection == DialogButton.Yes) { CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName; CurrentRecipe.ReviseTime = DateTime.Now; Save(CurrentRecipe, false); } } var dialog = new InputFileNameDialogViewModel("Input New Recipe Name"); dialog.FileName = CurrentFileNode.Name; var wm = new WindowManager(); var dialogReturn = wm.ShowDialog(dialog); if (!dialogReturn.HasValue || !dialogReturn.Value) return; var recipeName = dialog.FileName.Trim(); if (string.IsNullOrEmpty(dialog.FileName)) { DialogBox.ShowWarning("Recipe file name should not be empty"); return; } var prefix = CurrentChamberType + "\\" + CurrentProcessType; var processType = string.Empty; var folder = CurrentFileNode.FullPath; if (CurrentFileNode.IsFile) { folder = folder.Substring(0, folder.LastIndexOf("\\") + 1); } if (!string.IsNullOrEmpty(folder)) recipeName = folder + "\\" + recipeName; if (CurrentFileNode.FullPath == recipeName) return; if (IsExist(recipeName, true)) { DialogBox.ShowWarning($"Can not copy to {recipeName}, Recipe with the same name already exist."); return; } if (recipeName.Length > 200) { DialogBox.ShowWarning($"Can not create folder {recipeName}, Folder name too long, should be less 200."); return; } CurrentRecipe.Creator = BaseApp.Instance.UserContext.LoginName; CurrentRecipe.CreateTime = DateTime.Now; CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName; CurrentRecipe.ReviseTime = DateTime.Now; CurrentRecipe.Description = CurrentRecipe.Description + ". Renamed from " + CurrentFileNode.Name; _recipeProvider.SaveAsRecipe(prefix, recipeName, CurrentRecipe.GetXmlString()); ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, recipeName, false); } public void RenameRecipe() { if (CurrentFileNode == null || !CurrentFileNode.IsFile) return; if (IsChanged) { var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel, DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?"); if (selection == DialogButton.Cancel) return; if (selection == DialogButton.Yes) { CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName; CurrentRecipe.ReviseTime = DateTime.Now; Save(CurrentRecipe, false); } } var dialog = new InputFileNameDialogViewModel("Input New Recipe Name"); dialog.FileName = CurrentFileNode.Name; var wm = new WindowManager(); var dialogReturn = wm.ShowDialog(dialog); if (!dialogReturn.HasValue || !dialogReturn.Value) return; var recipeName = dialog.FileName.Trim(); if (string.IsNullOrEmpty(dialog.FileName)) { DialogBox.ShowWarning("Recipe file name should not be empty"); return; } var prefix = CurrentChamberType + "\\" + CurrentProcessType; var processType = string.Empty; var newName = CurrentFileNode.FullPath.Substring(0, CurrentFileNode.FullPath.LastIndexOf("\\") + 1); if (!string.IsNullOrEmpty(newName)) newName = newName + recipeName; else newName = recipeName; if (newName == CurrentFileNode.FullPath) return; if (IsExist(newName, true)) { DialogBox.ShowWarning($"Can not rename to {newName}, Recipe with the same name already exist."); return; } if (newName.Length > 200) { DialogBox.ShowWarning($"Can not create folder {newName}, Folder name too long, should be less 200."); return; } _recipeProvider.RenameRecipe(prefix, CurrentFileNode.FullPath, newName); ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, newName, false); } public void DeleteRecipe() { if (CurrentFileNode == null || !CurrentFileNode.IsFile) return; var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No, DialogType.CONFIRM, $"Are you sure you want to delete \r\n {CurrentFileNode.FullPath}?"); if (selection == DialogButton.No) return; var nextFocus = CurrentFileNode.Parent.FullPath; var isFolder = true; if (CurrentFileNode.Parent.Files.Count > 1) { for (var i = 0; i < CurrentFileNode.Parent.Files.Count; i++) { if (CurrentFileNode.Parent.Files[i] == CurrentFileNode) { if (i == 0) { nextFocus = CurrentFileNode.Parent.Files[i + 1].FullPath; isFolder = !CurrentFileNode.Parent.Files[i + 1].IsFile; } else { nextFocus = CurrentFileNode.Parent.Files[i - 1].FullPath; isFolder = !CurrentFileNode.Parent.Files[i - 1].IsFile; } } } } _recipeProvider.DeleteRecipe(CurrentFileNode.PrefixPath, CurrentFileNode.FullPath); ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, nextFocus, isFolder); } public void RefreshRecipe() { ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, "", false); } public async void ReloadRecipe() { if (_editMode == EditMode.Normal || _editMode == EditMode.Edit) { if (CurrentRecipe == null) return; if (CurrentRecipe.IsChanged) { var ret = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No, DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} has changed, discard and reload?"); if (ret == DialogButton.No) return; } await LoadData(CurrentRecipe.PrefixPath, CurrentRecipe.Name); UpdateView(); ValidateEntireRecipe(); // ValidateEntireRecipe()会重新计算比例相关的值,导致IsChanged == true, 刷新一下。 foreach (var step in CurrentRecipe.Steps) step.Save(); } } public void SaveToAll() { ValidateEntireRecipe(); if (!CurrentRecipe.IsCompatibleWithCurrentFormat) { DialogBox.ShowWarning($"Saving failed, {CurrentRecipe.Name} is not a valid recipe file"); return; } //var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No, // DialogType.CONFIRM, // $"Do you want to save to all chambers? \r\n This will replace all the other chambers recipe content"); //if (selection == DialogButton.No) // return; CurrentRecipe.SaveTo(Chambers.ToArray()); Save(CurrentRecipe, false); } public void SaveTo() { ValidateEntireRecipe(); if (!CurrentRecipe.IsCompatibleWithCurrentFormat) { DialogBox.ShowWarning($"Saving failed, {CurrentRecipe.Name} is not a valid recipe file."); return; } var dialog = new SaveToDialogViewModel("Select the chamber to copy to", SelectedChamber, Chambers.ToList()); var wm = new WindowManager(); var dialogReturn = wm.ShowDialog(dialog); if (!dialogReturn.HasValue || !dialogReturn.Value) return; var chambers = new List(); foreach (var dialogChamber in dialog.Chambers) { if (dialogChamber.IsEnabled && dialogChamber.IsChecked) chambers.Add(dialogChamber.Name); } if (chambers.Count == 0) return; CurrentRecipe.SaveTo(chambers.ToArray()); Save(CurrentRecipe, false); } #endregion #region Steps /// /// 展开DataGrid列组。 /// /// public void ExpandColumnsGroup(ExpanderColumn col) { foreach (var subCol in col.ChildColumns) { subCol.UpdateVisibility(); } } /// /// 折叠DataGrid列组。 /// /// public void CollapseColumnsGroup(ExpanderColumn col) { foreach (var subCol in col.ChildColumns) { subCol.Visibility = Visibility.Collapsed; } } /// /// 检查是否所有的参数均有效。 /// /// private bool CheckIfAllCellsValid(out string errorMessage) { errorMessage = string.Empty; if (CurrentProcessType == ProcessTypeFileList[1].ProcessType) { return true; } /*var lstError = new List();*/ _validationResultList.Clear(); for (var a = 0; a < CurrentRecipe.Steps.Count; a++) { var invalidParam = CurrentRecipe.Steps[a] .Where(x => x is IParam p && p.IsValidated == false).ToList(); _validationResultList.AddRange(invalidParam.Cast() .Select(x => new RecipeStepValidationInfo( _currentRecipe.Steps[a], x, x.ValidationError))); } if (_validationResultList.Count <= 0) { ErrorsCount = 0; return true; } ErrorsCount = _validationResultList.Count; var errCnt = 0; var strInfo = new StringBuilder(); foreach (var t in _validationResultList) { strInfo.AppendLine(t.ToString()); errCnt++; if (errCnt > 10) break; } if (errCnt < _validationResultList.Count) strInfo.AppendLine("\r\n"); // MessageBox.Show(strInfo.ToString(), "Warning", MessageBoxButton.OK, MessageBoxImage.Warning); errorMessage = strInfo.ToString(); return false; } public void SaveRecipe() { /*if (Save(CurrentRecipe, false)) MessageBox.Show($"The recipe has been saved successfully!", "Succeeded", MessageBoxButton.OK, MessageBoxImage.Information);*/ Save(CurrentRecipe, false); } public void PopSetting(string controlName, Param paramData) { var stepNum = Convert.ToInt32(((StepParam)paramData.Parent[1]).Value); var dialog = new PublicPopSettingDialogViewModel(); dialog.DisplayName = paramData.DisplayName; var Parameters = new RecipeStep(null); Parameters = CurrentRecipe.PopSettingSteps[controlName][stepNum - 1]; var ControlParameters = new RecipeStep(null); var BrandParameters = new ObservableCollection(); foreach (var item in Parameters) { if (item.Name.Contains("Band")) { var name = item.Name.Replace("Wavelength", "").Replace("Bandwidth", ""); var displayName = item.DisplayName.Replace("Wavelength", "").Replace("Bandwidth", ""); if (BrandParameters.Where(x => x.Name == name).Count() == 0) { BrandParameters.Add(new BandParam() { Name = name, DisplayName = displayName }); } if (item.Name.Contains("Wavelength")) { BrandParameters.First(x => x.Name == name).WavelengthDoubleParam = item; } else if (item.Name.Contains("Bandwidth")) { BrandParameters.First(x => x.Name == name).BandwidthDoubleParam = item; } } else ControlParameters.Add(item); } dialog.Parameters = Parameters; dialog.ControlParameters = ControlParameters; dialog.BandParameters = BrandParameters; var wm = new WindowManager(); var bret = wm.ShowDialog(dialog); if (bret == true) { CurrentRecipe.PopSettingSteps[controlName][stepNum - 1] = dialog.Parameters; } } private bool Save(RecipeData recipe, bool createNew) { if (string.IsNullOrEmpty(recipe.Name)) { MessageBox.Show("Recipe name can't be empty", "Error", MessageBoxButton.OK, MessageBoxImage.Error); return false; } ValidateEntireRecipe(); if (CheckIfAllCellsValid(out var errors) == false) { var mbr = MessageBox.Show($"{errors}\r\n Are you sure to continue to save?", "Warning", MessageBoxButton.YesNo, MessageBoxImage.Warning); if (mbr == MessageBoxResult.No) return false; } var result = false; recipe.Revisor = BaseApp.Instance.UserContext.LoginName; recipe.ReviseTime = DateTime.Now; result = _recipeProvider.WriteRecipeFile(recipe.PrefixPath, recipe.Name, recipe.GetXmlString()); if (result) { recipe.DataSaved(); _editMode = EditMode.Normal; UpdateView(); } else { MessageBox.Show("Save failed!"); } return result; } public void AddStep() { Columns.Clear(); //add时重新从配方中加载数据 Columns = _columnBuilder.Build($"{CurrentChamberType}\\{CurrentProcessType}", SelectedChamber, true, BaseApp.Instance.UserContext.RoleName); CurrentRecipe.Steps.Add(CurrentRecipe.CreateStep(Columns)); if (_editMode != EditMode.New && _editMode != EditMode.ReName) _editMode = EditMode.Edit; ValidateEntireRecipe(); CheckIfAllCellsValid(out _); UpdateView(); } public void InsertStepToLeft() { if (SelectedRecipeSteps.Count != 1) return; var stepNo = SelectedRecipeSteps[0]?.StepNo; if (RecipeStep.ValidateStepNo(stepNo, out var vStepNo) == false) return; var index = vStepNo - RecipeStep.START_INDEX; if (_editMode != EditMode.New && _editMode != EditMode.ReName) _editMode = EditMode.Edit; CurrentRecipe.Steps.Insert(index, CurrentRecipe.CreateStep(Columns)); } public void InsertStepToRight() { if (SelectedRecipeSteps.Count != 1) return; var stepNo = SelectedRecipeSteps[0]?.StepNo; if (RecipeStep.ValidateStepNo(stepNo, out var vStepNo) == false) return; var index = vStepNo - RecipeStep.START_INDEX; if (_editMode != EditMode.New && _editMode != EditMode.ReName) _editMode = EditMode.Edit; if (index < _currentRecipe.Steps.Count - 1) CurrentRecipe.Steps.Insert(index + 1, CurrentRecipe.CreateStep(Columns)); else CurrentRecipe.Steps.Add(CurrentRecipe.CreateStep(Columns)); } private readonly RecipeStepCollection _copySteps = new RecipeStepCollection(); private readonly Dictionary> _popCopySteps = new Dictionary>(); /// /// 拷贝所有选中的Steps。 /// /// public void CopyStep() { _copySteps.Clear(); _popCopySteps.Clear(); if (SelectedRecipeSteps == null || SelectedRecipeSteps.Count <= 0) return; foreach (var step in SelectedRecipeSteps) _copySteps.Add(_currentRecipe.CloneStep(Columns, step)); CurrentRecipe.ValidLoopData(); } /// /// 在左侧粘贴Step。 /// public async void PasteStepToLeft() { await Paste(true); } /// /// 在右侧粘贴Step。 /// public async void PasteStepToRight() { await Paste(false); } /// /// 粘贴Step。 /// 注意:async Task 标记的方法不能直接应用于Caliburn.Message.Attach 方法。 /// /// 是否粘贴到当前选中的Step的左侧。 /// private async Task Paste(bool isToLeft) { if (SelectedRecipeSteps == null || SelectedRecipeSteps.Count != 1) return; if (_copySteps.Count <= 0) return; if (_editMode != EditMode.New && _editMode != EditMode.ReName) _editMode = EditMode.Edit; var stepNo = SelectedRecipeSteps[0].StepNo; if (RecipeStep.ValidateStepNo(stepNo, out var vStepNo) == false) return; foreach (var t in _copySteps) { var cloned = CurrentRecipe.CloneStep(Columns, t); var pos = isToLeft ? vStepNo - RecipeStep.START_INDEX : vStepNo; await Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => { CurrentRecipe.Steps.Insert(pos, cloned); })); } ValidateEntireRecipe(); CurrentRecipe.ValidLoopData(); CheckIfAllCellsValid(out _); UpdateView(); } /// /// 在左侧复制Step。 /// public async void DuplicateStepToLeft() { await Duplicate(true); } /// /// 在右侧复制Step。 /// public async void DuplicateStepToRight() { await Duplicate(false); } /// /// 复制选中的Step。 /// /// 是否复制到选中的Step的左侧。 private async Task Duplicate(bool isToLeft) { if (SelectedRecipeSteps == null || SelectedRecipeSteps.Count != 1) return; CopyStep(); await Paste(isToLeft); } /// /// 删除步骤。 /// public void DeleteStep() { if (_editMode != EditMode.New && _editMode != EditMode.ReName) _editMode = EditMode.Edit; for (var i = SelectedRecipeSteps.Count - 1; i >= 0; i--) { _currentRecipe.Steps.Remove(SelectedRecipeSteps[i]); } CheckIfAllCellsValid(out _); } private void CreateValidationDetailWindow() { _winValidationInfo = new RecipeEditorValidationDetailWindow(_validationResultList, _lastPosOfValidationWin); _winValidationInfo.DataContext = this; _winValidationInfo.Closed += (sender, args) => { _lastPosOfValidationWin = new Point(_winValidationInfo.Left, _winValidationInfo.Top); ResetHighlight(); }; } public void ShowValidationDetailWindow() { ValidateEntireRecipe(); CheckIfAllCellsValid(out _); if (Application.Current.Windows.OfType().Any()) _winValidationInfo.Activate(); else { CreateValidationDetailWindow(); _winValidationInfo.Show(); } } public void ShowHideParamValues() { CurrentRecipe.Steps.IsHideValue = !CurrentRecipe.Steps.IsHideValue; } private TreeViewItem GetParentObjectEx(DependencyObject obj) where TreeViewItem : FrameworkElement { var parent = VisualTreeHelper.GetParent(obj); while (parent != null) { if (parent is TreeViewItem) { return (TreeViewItem)parent; } parent = VisualTreeHelper.GetParent(parent); } return null; } public void TreeRightMouseDown(MouseButtonEventArgs e) { var item = GetParentObjectEx(e.OriginalSource as DependencyObject) as TreeViewItem; if (item != null) { item.Focus(); } } /// /// 在DataGrid中聚焦并高亮指定的参数。 /// /// public void FocusToParam(IParam param) { if (param == null) return; ResetHighlight(); var row = param.RowOwner; var col = param.ColumnOwner; if (row != null && col != null) { ((RecipeEditorView)View).dgCustom.ScrollIntoView(param.Parent, col); param.Highlight(); } } public void ResetHighlight() { _currentRecipe.Steps.ResetHighlight(); } #endregion private void ClearData() { _editMode = EditMode.None; CurrentRecipe.Clear(); CurrentRecipe.Name = string.Empty; CurrentRecipe.Description = string.Empty; } /// /// 根据配置获取Cascade方式呈现Recipe的Dispatcher,或返回null。 /// /// private static Dispatcher GetLoadingDispatcher() { // 判断Step呈现方式 var isCascadeLoading = (bool)QueryDataClient.Instance.Service.GetConfig("System.Recipe.CascadeLoading"); var dispatcher = isCascadeLoading ? Dispatcher.CurrentDispatcher : null; return dispatcher; } /// /// 从配置文件中获取是否加载Recipe后自动隐藏参数值的配置。 /// /// private static bool GetIsHideValueAfterLoading() { try { return false; //return (bool)QueryDataClient.Instance.Service.GetConfig("System.Recipe.HideParamValueAfterLoading"); } catch { // 如果没有找到参数,默认隐藏参数值。 return false; } } private async Task LoadData(string prefixPath, string recipeName) { var dg = ((RecipeEditorView)View).dgCustom; IsLoading = true; CurrentRecipe.Clear(); CurrentRecipe.Steps.Save(); // 重置为已保存状态 ErrorsCount = 0; var recipeContent = _recipeProvider.ReadRecipeFile(prefixPath, recipeName); if (string.IsNullOrEmpty(recipeContent)) { MessageBox.Show($"{prefixPath}\\{recipeName} is empty, please confirm the file is valid.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); return; } CurrentRecipe.RecipeChamberType = _columnBuilder.RecipeChamberType; CurrentRecipe.RecipeVersion = _columnBuilder.RecipeVersion; CurrentRecipe.Steps.IsHideValue = GetIsHideValueAfterLoading(); await CurrentRecipe.InitData(prefixPath, recipeName, recipeContent, Columns, _columnBuilder.Configs, SelectedChamber, GetLoadingDispatcher()); _editMode = EditMode.Normal; IsLoading = false; // 如果当前Recipe非本机Recipe,比例相关的参数可能由于算法不同导致结果不同, // 重新计算一次Recipe,以更新比例计算值,并校验其合法性。 ValidateEntireRecipe(); CheckIfAllCellsValid(out var _); } private void UpdateView() { var isFileSelected = CurrentFileNode != null && CurrentFileNode.IsFile; EnableNew = isFileSelected; EnableReName = isFileSelected; EnableCopy = isFileSelected; EnableDelete = isFileSelected; EnableSave = isFileSelected; EnableStep = isFileSelected; EnableSaveTo = isFileSelected; EnableSaveToAll = isFileSelected; if (_editMode == EditMode.None) { EnableNew = true; EnableReName = false; EnableCopy = false; EnableDelete = false; EnableStep = false; EnableSave = false; EnableReload = true; } NotifyOfPropertyChange(nameof(EnableNew)); NotifyOfPropertyChange(nameof(EnableReName)); NotifyOfPropertyChange(nameof(EnableCopy)); NotifyOfPropertyChange(nameof(EnableDelete)); NotifyOfPropertyChange(nameof(EnableSave)); NotifyOfPropertyChange(nameof(EnableStep)); NotifyOfPropertyChange(nameof(EnableSaveTo)); NotifyOfPropertyChange(nameof(EnableSaveToAll)); NotifyOfPropertyChange(nameof(EnableReload)); NotifyOfPropertyChange(nameof(CurrentRecipe)); } private string _currentCriteria = string.Empty; public string CurrentCriteria { get => _currentCriteria; set { if (value == _currentCriteria) return; _currentCriteria = value; NotifyOfPropertyChange(nameof(CurrentCriteria)); ApplyFilter(); } } private void ApplyFilter() { ProcessTypeFileList[ProcessTypeIndexSelection].FilterFileListByProcessType = new ObservableCollection(ProcessTypeFileList[ProcessTypeIndexSelection].FileListByProcessType .Where(d => d.Name.IndexOf(CurrentCriteria, StringComparison.OrdinalIgnoreCase) >= 0)); } public void ClearFilter() { CurrentCriteria = ""; } /// /// 当主窗口状态Login状态发生变化时处理当前窗口状态。 /// /// public void Handle(UserMode message) { switch (message) { case UserMode.None: break; case UserMode.Normal: break; case UserMode.Lock: case UserMode.Logoff: case UserMode.Exit: case UserMode.Shutdown: case UserMode.Breakdown: _winValidationInfo?.Close(); break; default: // ignore break; } } } }