using System; using System.Windows; using Prism.Commands; using Prism.Events; using Prism.Mvvm; using XP.Common.Localization; using XP.Hardware.MotionControl.Abstractions; using XP.Hardware.MotionControl.Abstractions.Enums; using XP.Hardware.MotionControl.Abstractions.Events; using XP.Hardware.MotionControl.Config; using XP.Hardware.MotionControl.Services; using XP.Hardware.Plc.Abstractions; namespace XP.Hardware.MotionControl.ViewModels { /// /// 运动控制调试 ViewModel(Jog 点动)| Motion Control Debug ViewModel (Jog) /// 提供 Jog 命令、速度滑块和可选轴可见性 | Provides Jog commands, speed slider and optional axis visibility /// public class MotionDebugWindowViewModel : BindableBase { private readonly IMotionControlService _motionControlService; private readonly MotionControlConfig _config; private readonly IPlcService _plcService; private readonly IEventAggregator _eventAggregator; private readonly IMotionSystem _motionSystem; /// PLC 是否已连接 | Whether PLC is connected public bool IsPlcConnected => _plcService.IsConnected; public MotionDebugWindowViewModel( IMotionControlService motionControlService, IMotionSystem motionSystem, MotionControlConfig config, IPlcService plcService, IEventAggregator eventAggregator) { _motionControlService = motionControlService ?? throw new ArgumentNullException(nameof(motionControlService)); _motionSystem = motionSystem ?? throw new ArgumentNullException(nameof(motionSystem)); _config = config ?? throw new ArgumentNullException(nameof(config)); _plcService = plcService ?? throw new ArgumentNullException(nameof(plcService)); _eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator)); var cfg = _config; // 初始化可选轴可见性 | Initialize optional axis visibility StageRotationVisibility = cfg.RotaryAxes.ContainsKey(RotaryAxisId.StageRotation) && cfg.RotaryAxes[RotaryAxisId.StageRotation].Enabled ? Visibility.Visible : Visibility.Collapsed; FixtureRotationVisibility = cfg.RotaryAxes.ContainsKey(RotaryAxisId.FixtureRotation) && cfg.RotaryAxes[RotaryAxisId.FixtureRotation].Enabled ? Visibility.Visible : Visibility.Collapsed; // 初始化命令 | Initialize commands // Jog 命令通过 SafeRun 内部检查 PLC 连接(Interaction.Triggers 不支持 CanExecute 禁用按钮外观) JogPlusCommand = new DelegateCommand(s => SafeRun(() => ExecuteJogPlus(s))); JogMinusCommand = new DelegateCommand(s => SafeRun(() => ExecuteJogMinus(s))); JogStopCommand = new DelegateCommand(s => SafeRun(() => ExecuteJogStop(s), silent: true)); // 门按钮通过 CanExecute 禁用 | Door buttons disabled via CanExecute StopDoorCommand = new DelegateCommand(() => SafeRun(() => _motionControlService.StopDoor()), () => IsPlcConnected); OpenDoorCommand = new DelegateCommand(() => SafeRun(() => _motionControlService.OpenDoor()), () => IsPlcConnected); CloseDoorCommand = new DelegateCommand(() => SafeRun(() => _motionControlService.CloseDoor()), () => IsPlcConnected); // 几何反算命令 | Geometry inverse calculation command ApplyGeometryCommand = new DelegateCommand(() => SafeRun(ExecuteApplyGeometry), () => IsPlcConnected); // 单轴归零命令 | Single axis home command HomeAxisCommand = new DelegateCommand(s => SafeRun(() => ExecuteHomeAxis(s))); // 轴复位命令 | Axis reset command AxisResetCommand = new DelegateCommand(() => SafeRun(ExecuteAxisReset), () => IsPlcConnected); // PLC 连接状态变化时刷新按钮 | Refresh buttons on PLC connection change _plcService.PropertyChanged += (s, e) => { if (e.PropertyName == nameof(IPlcService.IsConnected)) { RaisePropertyChanged(nameof(IsPlcConnected)); StopDoorCommand.RaiseCanExecuteChanged(); OpenDoorCommand.RaiseCanExecuteChanged(); CloseDoorCommand.RaiseCanExecuteChanged(); ApplyGeometryCommand.RaiseCanExecuteChanged(); AxisResetCommand.RaiseCanExecuteChanged(); } }; } /// /// 安全执行:PLC 未连接时弹窗提示(JogStop 除外),异常时静默记录 /// Safe execution: show message when PLC not connected (except JogStop), silently log on exception /// private bool _plcNotConnectedShown = false; private void SafeRun(Action action, bool silent = false) { if (!_plcService.IsConnected) { if (!silent && !_plcNotConnectedShown) { _plcNotConnectedShown = true; var msg = XP.Common.Localization.LocalizationHelper.Get("MC_PlcNotConnected"); System.Windows.MessageBox.Show( string.IsNullOrEmpty(msg) ? "PLC not connected" : msg, XP.Common.Localization.LocalizationHelper.Get("MC_DebugWindow_Title") ?? "Debug", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning); _plcNotConnectedShown = false; } return; } try { action(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[MotionDebug] {ex.Message}"); } } #region 速度百分比 | Speed Percentage private double _speedPercent = 100; /// 速度百分比(1-150%),实际速度 = DefaultVelocity * 百分比 / 100 | Speed percentage (1-150%), actual = DefaultVelocity * pct / 100 public double SpeedPercent { get => _speedPercent; set { SetProperty(ref _speedPercent, value); RaisePropertyChanged(nameof(ActualSpeed)); } } /// 实际速度(只读显示)| Actual speed (read-only display) public double ActualSpeed => _config.DefaultVelocity * _speedPercent / 100.0; #endregion #region 可选轴可见性 | Optional Axis Visibility private Visibility _stageRotationVisibility; public Visibility StageRotationVisibility { get => _stageRotationVisibility; private set => SetProperty(ref _stageRotationVisibility, value); } private Visibility _fixtureRotationVisibility; public Visibility FixtureRotationVisibility { get => _fixtureRotationVisibility; private set => SetProperty(ref _fixtureRotationVisibility, value); } #endregion #region Jog 命令 | Jog Commands public DelegateCommand JogPlusCommand { get; } public DelegateCommand JogMinusCommand { get; } public DelegateCommand JogStopCommand { get; } /// 单轴归零命令 | Single axis home command public DelegateCommand HomeAxisCommand { get; } /// 轴复位命令 | Axis reset command public DelegateCommand AxisResetCommand { get; } /// 停止门命令 | Stop door command public DelegateCommand StopDoorCommand { get; } /// 开门命令(调试模式,不检查联锁)| Open door command (debug, no interlock) public DelegateCommand OpenDoorCommand { get; } /// 关门命令 | Close door command public DelegateCommand CloseDoorCommand { get; } /// 几何反算命令 | Geometry inverse calculation command public DelegateCommand ApplyGeometryCommand { get; } #region 几何反算属性 | Geometry Calculation Properties private double _targetFOD; /// 目标 FOD | Target FOD public double TargetFOD { get => _targetFOD; set => SetProperty(ref _targetFOD, value); } private double _targetFDD; /// 目标 FDD | Target FDD public double TargetFDD { get => _targetFDD; set => SetProperty(ref _targetFDD, value); } /// /// 执行几何反算,弹出确认窗口,确认后通过事件通知 MotionControlViewModel 填入目标值 /// Execute geometry inverse, show confirmation, publish event to fill targets /// private void ExecuteApplyGeometry() { var (sourceZTarget, detectorZTarget, error) = _motionControlService.CalculateGeometryTargets(TargetFOD, TargetFDD); if (error != null) { MessageBox.Show( LocalizationHelper.Get("MC_GeometryCalcFailed", error), LocalizationHelper.Get("MC_Geometry_Title"), MessageBoxButton.OK, MessageBoxImage.Warning); return; } // 获取其他轴的实际值 | Get actual values of other axes var stageX = _motionSystem.GetLinearAxis(AxisId.StageX).ActualPosition; var stageY = _motionSystem.GetLinearAxis(AxisId.StageY).ActualPosition; var detSwing = _motionSystem.GetRotaryAxis(RotaryAxisId.DetectorSwing).ActualAngle; var stageRot = _motionSystem.GetRotaryAxis(RotaryAxisId.StageRotation).ActualAngle; var fixtureRot = _motionSystem.GetRotaryAxis(RotaryAxisId.FixtureRotation).ActualAngle; // 弹出确认窗口 | Show confirmation dialog var confirmMessage = LocalizationHelper.Get("MC_GeometryFillConfirm", TargetFOD, TargetFDD, detSwing, sourceZTarget, detectorZTarget); var result = MessageBox.Show(confirmMessage, LocalizationHelper.Get("MC_Geometry_Title"), MessageBoxButton.OKCancel, MessageBoxImage.Question); if (result != MessageBoxResult.OK) return; // 发布事件,通知 MotionControlViewModel 填入目标值 | Publish event to fill target values _eventAggregator.GetEvent() .Publish(new GeometryApplyRequestData( sourceZTarget, detectorZTarget, stageX, stageY, detSwing, stageRot, fixtureRot)); } #endregion private void ExecuteJogPlus(string axisName) { if (TryParseLinearAxis(axisName, out var la)) _motionControlService.JogStart(la, true); else if (TryParseRotaryAxis(axisName, out var ra)) _motionControlService.JogRotaryStart(ra, true); } private void ExecuteJogMinus(string axisName) { if (TryParseLinearAxis(axisName, out var la)) _motionControlService.JogStart(la, false); else if (TryParseRotaryAxis(axisName, out var ra)) _motionControlService.JogRotaryStart(ra, false); } private void ExecuteJogStop(string axisName) { if (TryParseLinearAxis(axisName, out var la)) _motionControlService.JogStop(la); else if (TryParseRotaryAxis(axisName, out var ra)) _motionControlService.JogRotaryStop(ra); } private static bool TryParseLinearAxis(string name, out AxisId id) => Enum.TryParse(name, out id) && Enum.IsDefined(typeof(AxisId), id); private static bool TryParseRotaryAxis(string name, out RotaryAxisId id) => Enum.TryParse(name, out id) && Enum.IsDefined(typeof(RotaryAxisId), id); /// /// 单轴归零 | Single axis home /// private void ExecuteHomeAxis(string axisName) { if (TryParseLinearAxis(axisName, out var la)) _motionSystem.GetLinearAxis(la).Home(); else if (TryParseRotaryAxis(axisName, out var ra)) _motionSystem.GetRotaryAxis(ra).Home(); } /// /// 轴复位:通过 IAxisReset 发送复位命令 | Axis reset: send reset command via IAxisReset /// private void ExecuteAxisReset() { _motionSystem.AxisReset.Reset(); } #endregion } }