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 { 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 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 SelectedAxes => SelectedPIDProfile != null ? SelectedPIDProfile.Axes : null; public ObservableCollection SelectedFilteringParameters => SelectedPIDProfile != null ? SelectedPIDProfile.FilteringParameters : null; private ObservableCollection _axisOptions = new ObservableCollection(); public ObservableCollection 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(); } if (ProcessRecipe.ProcessCompensationParameters == null) { ProcessRecipe.ProcessCompensationParameters = new ObservableCollection(); } 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(); } 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 usedAxisIndices) { if (AxisOptions == null || AxisOptions.Count == 0) { return null; } HashSet usedAxisSet = new HashSet(); 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 BuildAxisOptions() { ObservableCollection axisOptions = new ObservableCollection(); 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; } } }