diff --git a/XP.Hardware.Detector/bin/Debug/net8.0-windows7.0/bin/Debug/net8.0-windows7.0/xisl.dll b/XP.Hardware.Detector/bin/Debug/net8.0-windows7.0/bin/Debug/net8.0-windows7.0/xisl.dll deleted file mode 100644 index 9141737..0000000 Binary files a/XP.Hardware.Detector/bin/Debug/net8.0-windows7.0/bin/Debug/net8.0-windows7.0/xisl.dll and /dev/null differ diff --git a/XP.Hardware.MotionControl/Abstractions/ILinearAxis.cs b/XP.Hardware.MotionControl/Abstractions/ILinearAxis.cs index 9a023e7..30a9d7f 100644 --- a/XP.Hardware.MotionControl/Abstractions/ILinearAxis.cs +++ b/XP.Hardware.MotionControl/Abstractions/ILinearAxis.cs @@ -45,6 +45,11 @@ namespace XP.Hardware.MotionControl.Abstractions /// 操作结果 | Operation result MotionResult Stop(); + /// 设置 Jog 速度 | Set jog speed + /// 速度百分比(0~100)| Speed percentage (0~100) + /// 操作结果 | Operation result + MotionResult SetJogSpeed(double speedPercent); + /// 从 PLC 更新状态 | Update status from PLC void UpdateStatus(); } diff --git a/XP.Hardware.MotionControl/Abstractions/IRotaryAxis.cs b/XP.Hardware.MotionControl/Abstractions/IRotaryAxis.cs index 492252a..0af5991 100644 --- a/XP.Hardware.MotionControl/Abstractions/IRotaryAxis.cs +++ b/XP.Hardware.MotionControl/Abstractions/IRotaryAxis.cs @@ -42,6 +42,11 @@ namespace XP.Hardware.MotionControl.Abstractions /// 操作结果 | Operation result MotionResult Stop(); + /// 设置 Jog 速度 | Set jog speed + /// 速度百分比(0~100)| Speed percentage (0~100) + /// 操作结果 | Operation result + MotionResult SetJogSpeed(double speedPercent); + /// 从 PLC 更新状态 | Update status from PLC void UpdateStatus(); } diff --git a/XP.Hardware.MotionControl/Abstractions/LinearAxisBase.cs b/XP.Hardware.MotionControl/Abstractions/LinearAxisBase.cs index 55cf49d..e073062 100644 --- a/XP.Hardware.MotionControl/Abstractions/LinearAxisBase.cs +++ b/XP.Hardware.MotionControl/Abstractions/LinearAxisBase.cs @@ -70,6 +70,9 @@ namespace XP.Hardware.MotionControl.Abstractions /// public abstract MotionResult Stop(); + /// + public abstract MotionResult SetJogSpeed(double speedPercent); + /// public abstract void UpdateStatus(); } diff --git a/XP.Hardware.MotionControl/Abstractions/RotaryAxisBase.cs b/XP.Hardware.MotionControl/Abstractions/RotaryAxisBase.cs index c215dd7..0eca7e0 100644 --- a/XP.Hardware.MotionControl/Abstractions/RotaryAxisBase.cs +++ b/XP.Hardware.MotionControl/Abstractions/RotaryAxisBase.cs @@ -67,6 +67,9 @@ namespace XP.Hardware.MotionControl.Abstractions /// public abstract MotionResult Stop(); + /// + public abstract MotionResult SetJogSpeed(double speedPercent); + /// public abstract void UpdateStatus(); } diff --git a/XP.Hardware.MotionControl/Implementations/PlcLinearAxis.cs b/XP.Hardware.MotionControl/Implementations/PlcLinearAxis.cs index 928c67e..a2a6916 100644 --- a/XP.Hardware.MotionControl/Implementations/PlcLinearAxis.cs +++ b/XP.Hardware.MotionControl/Implementations/PlcLinearAxis.cs @@ -95,6 +95,13 @@ namespace XP.Hardware.MotionControl.Implementations return MotionResult.Ok(); } + /// + public override MotionResult SetJogSpeed(double speedPercent) + { + _signalService.EnqueueWrite(_speedSignal, (float)speedPercent); + return MotionResult.Ok(); + } + /// public override void UpdateStatus() { diff --git a/XP.Hardware.MotionControl/Implementations/PlcRotaryAxis.cs b/XP.Hardware.MotionControl/Implementations/PlcRotaryAxis.cs index 7b44aed..4643cac 100644 --- a/XP.Hardware.MotionControl/Implementations/PlcRotaryAxis.cs +++ b/XP.Hardware.MotionControl/Implementations/PlcRotaryAxis.cs @@ -91,6 +91,14 @@ namespace XP.Hardware.MotionControl.Implementations return MotionResult.Ok(); } + /// + public override MotionResult SetJogSpeed(double speedPercent) + { + if (!_enabled) return MotionResult.Fail($"旋转轴 {_axisId} 已禁用,拒绝设置 Jog 速度命令"); + _signalService.EnqueueWrite(_speedSignal, (float)speedPercent); + return MotionResult.Ok(); + } + /// public override void UpdateStatus() { diff --git a/XP.Hardware.MotionControl/Resources/Resources.en-US.resx b/XP.Hardware.MotionControl/Resources/Resources.en-US.resx index d7ebc1c..b38b168 100644 --- a/XP.Hardware.MotionControl/Resources/Resources.en-US.resx +++ b/XP.Hardware.MotionControl/Resources/Resources.en-US.resx @@ -258,4 +258,47 @@ SourceZ → {3:F2}mm DetectorZ → {4:F2}mm Confirm to filll move matrix? + + + Axis Positions + + + Stage X + + + Stage Y + + + Source Z + + + Detector Z + + + Detector Swing + + + Stage Rotation + + + Fixture Rotation + + + Safety Parameters + + + Safety Height + + + Calibration Value + + + Enable + + + Save + + + Restore + \ No newline at end of file diff --git a/XP.Hardware.MotionControl/Resources/Resources.resx b/XP.Hardware.MotionControl/Resources/Resources.resx index 4b5e395..e0b9114 100644 --- a/XP.Hardware.MotionControl/Resources/Resources.resx +++ b/XP.Hardware.MotionControl/Resources/Resources.resx @@ -301,4 +301,61 @@ DetectorZ → {4:F2}mm 确认填入信息? | Confirm to filll move matrix? 几何反算确认提示 | Geometry inverse confirmation message + + + 轴位置 + 轴位置分组标题 | Axis positions group title + + + 载物台 X + 载物台 X 轴标签 | Stage X axis label + + + 载物台 Y + 载物台 Y 轴标签 | Stage Y axis label + + + 射线源 Z + 射线源 Z 轴标签 | Source Z axis label + + + 探测器 Z + 探测器 Z 轴标签 | Detector Z axis label + + + 探测器摆动 + 探测器摆动旋转轴标签 | Detector swing rotary axis label + + + 载物台旋转 + 载物台旋转轴标签 | Stage rotation axis label + + + 夹具旋转 + 夹具旋转轴标签 | Fixture rotation axis label + + + 安全参数 + 安全参数分组标题 | Safety parameters group title + + + 安全高度 + 探测器安全高度限定值标签 | Safety height label + + + 校准计算值 + 校准自动计算值标签 | Calibration value label + + + 使能 + 使能开关标签 | Enable toggle label + + + 保存 + 保存按钮文本 | Save button text + + + 恢复 + 恢复按钮文本 | Restore button text + \ No newline at end of file diff --git a/XP.Hardware.MotionControl/Resources/Resources.zh-CN.resx b/XP.Hardware.MotionControl/Resources/Resources.zh-CN.resx index dfd95c8..6b40bd2 100644 --- a/XP.Hardware.MotionControl/Resources/Resources.zh-CN.resx +++ b/XP.Hardware.MotionControl/Resources/Resources.zh-CN.resx @@ -258,4 +258,47 @@ SourceZ → {3:F2}mm DetectorZ → {4:F2}mm 确认填入目标值信息? + + + 轴位置 + + + 载物台 X + + + 载物台 Y + + + 射线源 Z + + + 探测器 Z + + + 探测器摆动 + + + 载物台旋转 + + + 夹具旋转 + + + 安全参数 + + + 安全高度 + + + 校准计算值 + + + 使能 + + + 保存 + + + 恢复 + \ No newline at end of file diff --git a/XP.Hardware.MotionControl/Resources/Resources.zh-TW.resx b/XP.Hardware.MotionControl/Resources/Resources.zh-TW.resx index 4c73e48..a888ffb 100644 --- a/XP.Hardware.MotionControl/Resources/Resources.zh-TW.resx +++ b/XP.Hardware.MotionControl/Resources/Resources.zh-TW.resx @@ -258,4 +258,47 @@ SourceZ → {3:F2}mm DetectorZ → {4:F2}mm 確填入目標值資料? + + + 軸位置 + + + 載物台 X + + + 載物台 Y + + + 射線源 Z + + + 探測器 Z + + + 探測器擺動 + + + 載物台旋轉 + + + 夾具旋轉 + + + 安全參數 + + + 安全高度 + + + 校準計算值 + + + 使能 + + + 保存 + + + 恢復 + \ No newline at end of file diff --git a/XP.Hardware.MotionControl/Services/IMotionControlService.cs b/XP.Hardware.MotionControl/Services/IMotionControlService.cs index f84f137..30dcc76 100644 --- a/XP.Hardware.MotionControl/Services/IMotionControlService.cs +++ b/XP.Hardware.MotionControl/Services/IMotionControlService.cs @@ -106,6 +106,24 @@ namespace XP.Hardware.MotionControl.Services /// 操作结果 | Operation result MotionResult JogRotaryStop(RotaryAxisId axisId); + /// + /// 设置直线轴 Jog 速度 | Set linear axis jog speed + /// 根据速度百分比计算实际速度并写入 PLC | Calculates actual speed from percentage and writes to PLC + /// + /// 直线轴标识 | Linear axis identifier + /// 速度百分比(0~100)| Speed percentage (0~100) + /// 操作结果 | Operation result + MotionResult SetJogSpeed(AxisId axisId, double speedPercent); + + /// + /// 设置旋转轴 Jog 速度 | Set rotary axis jog speed + /// 根据速度百分比计算实际速度并写入 PLC | Calculates actual speed from percentage and writes to PLC + /// + /// 旋转轴标识 | Rotary axis identifier + /// 速度百分比(0~100)| Speed percentage (0~100) + /// 操作结果 | Operation result + MotionResult SetJogRotarySpeed(RotaryAxisId axisId, double speedPercent); + #endregion #region 安全门控制 | Safety Door Control diff --git a/XP.Hardware.MotionControl/Services/MotionControlService.cs b/XP.Hardware.MotionControl/Services/MotionControlService.cs index ecf36e7..56b5c70 100644 --- a/XP.Hardware.MotionControl/Services/MotionControlService.cs +++ b/XP.Hardware.MotionControl/Services/MotionControlService.cs @@ -488,6 +488,60 @@ namespace XP.Hardware.MotionControl.Services return result; } + /// + /// 设置直线轴 Jog 速度 | Set linear axis jog speed + /// 根据速度百分比计算实际速度并写入 PLC | Calculates actual speed from percentage and writes to PLC + /// + public MotionResult SetJogSpeed(AxisId axisId, double speedPercent) + { + var axis = _motionSystem.GetLinearAxis(axisId); + var actualSpeed = _config.DefaultVelocity * speedPercent / 100.0; + var result = axis.SetJogSpeed(actualSpeed); + + if (result.Success) + { + _logger.Debug("直线轴 {AxisId} Jog 速度已设置,百分比={SpeedPercent}%,实际速度={ActualSpeed} | Linear axis {AxisId} jog speed set, percent={SpeedPercent}%, actual={ActualSpeed}", + axisId, speedPercent, actualSpeed); + } + else + { + _logger.Warn("直线轴 {AxisId} 设置 Jog 速度被拒绝:{Reason} | Linear axis {AxisId} set jog speed rejected: {Reason}", axisId, result.ErrorMessage); + } + + return result; + } + + /// + /// 设置旋转轴 Jog 速度 | Set rotary axis jog speed + /// 根据速度百分比计算实际速度并写入 PLC | Calculates actual speed from percentage and writes to PLC + /// + public MotionResult SetJogRotarySpeed(RotaryAxisId axisId, double speedPercent) + { + var axis = _motionSystem.GetRotaryAxis(axisId); + + // 禁用轴检查 | Disabled axis check + if (!axis.Enabled) + { + _logger.Warn("旋转轴 {AxisId} 已禁用,拒绝设置 Jog 速度命令 | Rotary axis {AxisId} is disabled, set jog speed rejected", axisId); + return MotionResult.Fail($"旋转轴 {axisId} 已禁用 | Rotary axis {axisId} is disabled"); + } + + var actualSpeed = _config.DefaultVelocity * speedPercent / 100.0; + var result = axis.SetJogSpeed(actualSpeed); + + if (result.Success) + { + _logger.Debug("旋转轴 {AxisId} Jog 速度已设置,百分比={SpeedPercent}%,实际速度={ActualSpeed} | Rotary axis {AxisId} jog speed set, percent={SpeedPercent}%, actual={ActualSpeed}", + axisId, speedPercent, actualSpeed); + } + else + { + _logger.Warn("旋转轴 {AxisId} 设置 Jog 速度被拒绝:{Reason} | Rotary axis {AxisId} set jog speed rejected: {Reason}", axisId, result.ErrorMessage); + } + + return result; + } + #endregion #region 安全门控制 | Safety Door Control diff --git a/XP.Hardware.MotionControl/ViewModels/AxisControlViewModel.cs b/XP.Hardware.MotionControl/ViewModels/AxisControlViewModel.cs new file mode 100644 index 0000000..94ebafd --- /dev/null +++ b/XP.Hardware.MotionControl/ViewModels/AxisControlViewModel.cs @@ -0,0 +1,896 @@ +using System; +using System.Collections.Generic; +using System.Windows; +using Prism.Commands; +using Prism.Events; +using Prism.Mvvm; +using XP.Common.Controls; +using XP.Common.Logging.Interfaces; +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; +using XP.Common.Localization; + +namespace XP.Hardware.MotionControl.ViewModels +{ + /// + /// 轴控制面板 ViewModel | Axis Control Panel ViewModel + /// 集成摇杆、轴位置输入框、安全参数和使能控制 | Integrates joystick, axis position inputs, safety parameters and enable control + /// + public class AxisControlViewModel : BindableBase + { + private readonly IMotionControlService _motionControlService; + private readonly IMotionSystem _motionSystem; + private readonly IEventAggregator _eventAggregator; + private readonly MotionControlConfig _config; + private readonly IPlcService _plcService; + private readonly ILoggerService _logger; + + #region 内部状态跟踪 | Internal State Tracking + + /// 直线轴 Jog 活跃状态 | Linear axis jog active states + private readonly Dictionary _linearJogActive = new(); + + /// 旋转轴 Jog 活跃状态 | Rotary axis jog active states + private readonly Dictionary _rotaryJogActive = new(); + + /// 输入框编辑冻结标志 | Input box editing freeze flags + private readonly Dictionary _editingFlags = new(); + + /// 保存的轴位置数据 | Saved axis position data + private SavedPositions _savedPositions; + + #endregion + + #region 构造函数 | Constructor + + public AxisControlViewModel( + IMotionControlService motionControlService, + IMotionSystem motionSystem, + IEventAggregator eventAggregator, + MotionControlConfig config, + IPlcService plcService, + ILoggerService logger) + { + _motionControlService = motionControlService ?? throw new ArgumentNullException(nameof(motionControlService)); + _motionSystem = motionSystem ?? throw new ArgumentNullException(nameof(motionSystem)); + _eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator)); + _config = config ?? throw new ArgumentNullException(nameof(config)); + _plcService = plcService ?? throw new ArgumentNullException(nameof(plcService)); + _logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule(); + + // 监听 PLC 连接状态变化 | Listen for PLC connection status changes + _plcService.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(IPlcService.IsConnected)) + OnPlcConnectionChanged(); + }; + + // 初始化旋转轴输入框可见性 | Initialize rotary axis input box visibility + DetectorSwingVisibility = _config.RotaryAxes.ContainsKey(RotaryAxisId.DetectorSwing) + && _config.RotaryAxes[RotaryAxisId.DetectorSwing].Enabled + ? Visibility.Visible : Visibility.Collapsed; + StageRotationVisibility = _config.RotaryAxes.ContainsKey(RotaryAxisId.StageRotation) + && _config.RotaryAxes[RotaryAxisId.StageRotation].Enabled + ? Visibility.Visible : Visibility.Collapsed; + FixtureRotationVisibility = _config.RotaryAxes.ContainsKey(RotaryAxisId.FixtureRotation) + && _config.RotaryAxes[RotaryAxisId.FixtureRotation].Enabled + ? Visibility.Visible : Visibility.Collapsed; + + // 初始化命令 | Initialize commands + ToggleEnableCommand = new DelegateCommand(ExecuteToggleEnable, () => IsPlcConnected); + SavePositionsCommand = new DelegateCommand(ExecuteSavePositions); + RestorePositionsCommand = new DelegateCommand(ExecuteRestorePositions, () => _savedPositions != null && IsPlcConnected); + + // 初始化 Jog 状态跟踪字典 | Initialize jog state tracking dictionaries + foreach (AxisId axisId in Enum.GetValues(typeof(AxisId))) + _linearJogActive[axisId] = false; + foreach (RotaryAxisId axisId in Enum.GetValues(typeof(RotaryAxisId))) + _rotaryJogActive[axisId] = false; + + // 初始化 PLC 连接状态 | Initialize PLC connection status + _isPlcConnected = _plcService.IsConnected; + + // 订阅事件 | Subscribe to events + _eventAggregator.GetEvent().Subscribe(OnGeometryUpdated, ThreadOption.UIThread); + _eventAggregator.GetEvent().Subscribe(OnAxisStatusChanged, ThreadOption.UIThread); + + // 初始化时主动刷新一次轴位置 | Refresh axis positions on initialization + try + { + StageXPosition = _motionSystem.GetLinearAxis(AxisId.StageX).ActualPosition; + StageYPosition = _motionSystem.GetLinearAxis(AxisId.StageY).ActualPosition; + SourceZPosition = _motionSystem.GetLinearAxis(AxisId.SourceZ).ActualPosition; + DetectorZPosition = _motionSystem.GetLinearAxis(AxisId.DetectorZ).ActualPosition; + DetectorSwingAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.DetectorSwing).ActualAngle; + StageRotationAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.StageRotation).ActualAngle; + FixtureRotationAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.FixtureRotation).ActualAngle; + } + catch { /* 轴未初始化时忽略 | Ignore when axes not initialized */ } + } + + #endregion + + #region 摇杆输出属性 | Joystick Output Properties + + private double _dualJoystickOutputX; + /// 双轴摇杆 X 轴输出 | Dual-axis joystick X output + public double DualJoystickOutputX + { + get => _dualJoystickOutputX; + set + { + if (SetProperty(ref _dualJoystickOutputX, value)) + HandleDualJoystickOutput(); + } + } + + private double _dualJoystickOutputY; + /// 双轴摇杆 Y 轴输出 | Dual-axis joystick Y output + public double DualJoystickOutputY + { + get => _dualJoystickOutputY; + set + { + if (SetProperty(ref _dualJoystickOutputY, value)) + HandleDualJoystickOutput(); + } + } + + private MouseButtonType _dualJoystickActiveButton; + /// 双轴摇杆当前激活按键 | Dual-axis joystick active mouse button + public MouseButtonType DualJoystickActiveButton + { + get => _dualJoystickActiveButton; + set + { + var oldValue = _dualJoystickActiveButton; + if (SetProperty(ref _dualJoystickActiveButton, value)) + { + // 按键释放时停止所有双轴摇杆关联轴 | Stop all dual joystick axes when button released + if (value == MouseButtonType.None && oldValue != MouseButtonType.None) + StopDualJoystickAxes(); + } + } + } + + private double _singleJoystickOutputY; + /// 单轴摇杆 Y 轴输出 | Single-axis joystick Y output + public double SingleJoystickOutputY + { + get => _singleJoystickOutputY; + set + { + if (SetProperty(ref _singleJoystickOutputY, value)) + HandleSingleJoystickOutput(); + } + } + + private MouseButtonType _singleJoystickActiveButton; + /// 单轴摇杆当前激活按键 | Single-axis joystick active mouse button + public MouseButtonType SingleJoystickActiveButton + { + get => _singleJoystickActiveButton; + set + { + var oldValue = _singleJoystickActiveButton; + if (SetProperty(ref _singleJoystickActiveButton, value)) + { + // 按键释放时停止所有单轴摇杆关联轴 | Stop all single joystick axes when button released + if (value == MouseButtonType.None && oldValue != MouseButtonType.None) + StopSingleJoystickAxes(); + } + } + } + + #endregion + + #region 轴位置属性 | Axis Position Properties + + private double _stageXPosition; + /// 载物台 X 轴位置 | Stage X axis position + public double StageXPosition { get => _stageXPosition; set => SetProperty(ref _stageXPosition, value); } + + private double _stageYPosition; + /// 载物台 Y 轴位置 | Stage Y axis position + public double StageYPosition { get => _stageYPosition; set => SetProperty(ref _stageYPosition, value); } + + private double _sourceZPosition; + /// 射线源 Z 轴位置 | Source Z axis position + public double SourceZPosition { get => _sourceZPosition; set => SetProperty(ref _sourceZPosition, value); } + + private double _detectorZPosition; + /// 探测器 Z 轴位置 | Detector Z axis position + public double DetectorZPosition { get => _detectorZPosition; set => SetProperty(ref _detectorZPosition, value); } + + private double _detectorSwingAngle; + /// 探测器摆动角度 | Detector swing angle + public double DetectorSwingAngle { get => _detectorSwingAngle; set => SetProperty(ref _detectorSwingAngle, value); } + + private double _stageRotationAngle; + /// 载物台旋转角度 | Stage rotation angle + public double StageRotationAngle { get => _stageRotationAngle; set => SetProperty(ref _stageRotationAngle, value); } + + private double _fixtureRotationAngle; + /// 夹具旋转角度 | Fixture rotation angle + public double FixtureRotationAngle { get => _fixtureRotationAngle; set => SetProperty(ref _fixtureRotationAngle, value); } + + #endregion + + #region 旋转轴可见性 | Rotary Axis Visibility + + private Visibility _detectorSwingVisibility; + /// 探测器摆动输入框可见性 | Detector swing input box visibility + public Visibility DetectorSwingVisibility { get => _detectorSwingVisibility; private set => SetProperty(ref _detectorSwingVisibility, value); } + + private Visibility _stageRotationVisibility; + /// 载物台旋转输入框可见性 | Stage rotation input box visibility + public Visibility StageRotationVisibility { get => _stageRotationVisibility; private set => SetProperty(ref _stageRotationVisibility, value); } + + private Visibility _fixtureRotationVisibility; + /// 夹具旋转输入框可见性 | Fixture rotation input box visibility + public Visibility FixtureRotationVisibility { get => _fixtureRotationVisibility; private set => SetProperty(ref _fixtureRotationVisibility, value); } + + #endregion + + #region 安全参数属性 | Safety Parameter Properties + + private double _safetyHeight; + /// 探测器安全高度限定值 | Detector safety height limit + public double SafetyHeight { get => _safetyHeight; set => SetProperty(ref _safetyHeight, value); } + + private double _calibrationValue; + /// 校准自动计算值 | Calibration auto-calculated value + public double CalibrationValue { get => _calibrationValue; set => SetProperty(ref _calibrationValue, value); } + + #endregion + + #region 使能与状态属性 | Enable and Status Properties + + private bool _isJoystickEnabled = true; + /// 摇杆使能状态 | Joystick enable state + public bool IsJoystickEnabled { get => _isJoystickEnabled; set => SetProperty(ref _isJoystickEnabled, value); } + + private bool _isPlcConnected; + /// PLC 连接状态 | PLC connection status + public bool IsPlcConnected { get => _isPlcConnected; set => SetProperty(ref _isPlcConnected, value); } + + private string _errorMessage; + /// 错误提示信息 | Error message + public string ErrorMessage { get => _errorMessage; set => SetProperty(ref _errorMessage, value); } + + #endregion + + #region 命令 | Commands + + /// 切换使能开关命令 | Toggle enable switch command + public DelegateCommand ToggleEnableCommand { get; } + + /// 保存当前轴位置命令 | Save current axis positions command + public DelegateCommand SavePositionsCommand { get; } + + /// 恢复保存的轴位置命令 | Restore saved axis positions command + public DelegateCommand RestorePositionsCommand { get; } + + #endregion + + #region 事件回调 | Event Callbacks + + /// + /// 几何参数更新回调,刷新轴实际位置 | Geometry updated callback, refresh axis actual positions + /// + private void OnGeometryUpdated(GeometryData data) + { + try + { + if (!IsEditing(nameof(StageXPosition))) + StageXPosition = _motionSystem.GetLinearAxis(AxisId.StageX).ActualPosition; + if (!IsEditing(nameof(StageYPosition))) + StageYPosition = _motionSystem.GetLinearAxis(AxisId.StageY).ActualPosition; + if (!IsEditing(nameof(SourceZPosition))) + SourceZPosition = _motionSystem.GetLinearAxis(AxisId.SourceZ).ActualPosition; + if (!IsEditing(nameof(DetectorZPosition))) + DetectorZPosition = _motionSystem.GetLinearAxis(AxisId.DetectorZ).ActualPosition; + if (!IsEditing(nameof(DetectorSwingAngle))) + DetectorSwingAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.DetectorSwing).ActualAngle; + if (!IsEditing(nameof(StageRotationAngle))) + StageRotationAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.StageRotation).ActualAngle; + if (!IsEditing(nameof(FixtureRotationAngle))) + FixtureRotationAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.FixtureRotation).ActualAngle; + } + catch { /* 轴未初始化时忽略 | Ignore when axes not initialized */ } + } + + /// + /// 轴状态变化回调,更新对应轴位置 | Axis status changed callback, update corresponding axis position + /// + private void OnAxisStatusChanged(AxisStatusChangedData data) + { + try + { + switch (data.AxisId) + { + case AxisId.StageX: + if (!IsEditing(nameof(StageXPosition))) + StageXPosition = _motionSystem.GetLinearAxis(AxisId.StageX).ActualPosition; + break; + case AxisId.StageY: + if (!IsEditing(nameof(StageYPosition))) + StageYPosition = _motionSystem.GetLinearAxis(AxisId.StageY).ActualPosition; + break; + case AxisId.SourceZ: + if (!IsEditing(nameof(SourceZPosition))) + SourceZPosition = _motionSystem.GetLinearAxis(AxisId.SourceZ).ActualPosition; + break; + case AxisId.DetectorZ: + if (!IsEditing(nameof(DetectorZPosition))) + DetectorZPosition = _motionSystem.GetLinearAxis(AxisId.DetectorZ).ActualPosition; + break; + } + } + catch { /* 轴未初始化时忽略 | Ignore when axes not initialized */ } + } + + #endregion + + #region 编辑状态辅助 | Editing State Helpers + + /// + /// 检查指定输入框是否正在编辑 | Check if specified input box is being edited + /// + /// 属性名称 | Property name + /// 是否正在编辑 | Whether editing + private bool IsEditing(string propertyName) + { + return _editingFlags.TryGetValue(propertyName, out var editing) && editing; + } + + #endregion + + #region 异常保护 | Exception Protection + + /// + /// 安全执行:捕获异常避免 UI 崩溃 | Safe execution: catches exceptions to prevent UI crash + /// + private void SafeRun(Action action) + { + try { action(); } + catch (Exception ex) + { + _logger.Error(ex, "运动控制操作异常:{Message} | Motion control operation error: {Message}", ex.Message); + ErrorMessage = ex.Message; + } + } + + #endregion + + #region 命令状态刷新 | Command State Refresh + + /// + /// 刷新所有命令的 CanExecute 状态 | Refresh CanExecute state of all commands + /// + private void RaiseCommandCanExecuteChanged() + { + ToggleEnableCommand.RaiseCanExecuteChanged(); + SavePositionsCommand.RaiseCanExecuteChanged(); + RestorePositionsCommand.RaiseCanExecuteChanged(); + } + + /// + /// PLC 连接状态变化处理:断开时停止所有 Jog 并禁用操作,重连时清除错误 + /// PLC connection state change handler: stop all jog and disable operations on disconnect, clear error on reconnect + /// + private void OnPlcConnectionChanged() + { + IsPlcConnected = _plcService.IsConnected; + RaiseCommandCanExecuteChanged(); + + if (!_plcService.IsConnected) + { + // PLC 断开:停止所有活跃的 Jog 操作 | PLC disconnected: stop all active jog operations + foreach (var axisId in _linearJogActive.Keys) + { + if (_linearJogActive[axisId]) + { + _linearJogActive[axisId] = false; + _logger.Debug("PLC 断开,直线轴 Jog 已标记停止:{AxisId} | PLC disconnected, linear axis jog marked stopped: {AxisId}", axisId); + } + } + foreach (var axisId in _rotaryJogActive.Keys) + { + if (_rotaryJogActive[axisId]) + { + _rotaryJogActive[axisId] = false; + _logger.Debug("PLC 断开,旋转轴 Jog 已标记停止:{AxisId} | PLC disconnected, rotary axis jog marked stopped: {AxisId}", axisId); + } + } + + // 禁用摇杆 | Disable joystick + IsJoystickEnabled = false; + + // 显示连接断开提示 | Show disconnection message + ErrorMessage = LocalizationHelper.Get("MC_PlcNotConnected"); + + _logger.Warn("PLC 连接断开,已停止所有 Jog 并禁用运动控制 | PLC disconnected, all jog stopped and motion control disabled"); + } + else + { + // PLC 重连:清除错误信息(不自动启用摇杆,需用户手动开启) + // PLC reconnected: clear error message (don't auto-enable joystick, user must manually enable) + ErrorMessage = null; + + _logger.Info("PLC 连接已恢复 | PLC connection restored"); + } + } + + #endregion + + #region 使能开关与保存/恢复命令 | Enable Toggle and Save/Restore Commands + + /// + /// 切换摇杆使能状态,并发送使能状态到 PLC | Toggle joystick enable state and send to PLC + /// + private void ExecuteToggleEnable() + { + IsJoystickEnabled = !IsJoystickEnabled; + _logger.Info("摇杆使能状态切换:{Enabled} | Joystick enable toggled: {Enabled}", IsJoystickEnabled); + // TODO: 发送使能状态到 PLC(根据实际 PLC 信号定义)| Send enable state to PLC (based on actual PLC signal definition) + } + + /// + /// 保存当前 6 个轴位置到内部变量 | Save current 6 axis positions to internal variable + /// + private void ExecuteSavePositions() + { + _savedPositions = new SavedPositions + { + StageX = StageXPosition, + StageY = StageYPosition, + SourceZ = SourceZPosition, + DetectorZ = DetectorZPosition, + DetectorSwing = DetectorSwingAngle, + StageRotation = StageRotationAngle, + FixtureRotation = FixtureRotationAngle + }; + RestorePositionsCommand.RaiseCanExecuteChanged(); + _logger.Info("轴位置已保存 | Axis positions saved"); + } + + /// + /// 从保存的数据恢复到输入框,并发送移动命令 | Restore saved data to input boxes and send move commands + /// + private void ExecuteRestorePositions() + { + if (_savedPositions == null) return; + + // 恢复到输入框 | Restore to input boxes + StageXPosition = _savedPositions.StageX; + StageYPosition = _savedPositions.StageY; + SourceZPosition = _savedPositions.SourceZ; + DetectorZPosition = _savedPositions.DetectorZ; + DetectorSwingAngle = _savedPositions.DetectorSwing; + StageRotationAngle = _savedPositions.StageRotation; + FixtureRotationAngle = _savedPositions.FixtureRotation; + + // 发送移动命令 | Send move commands + SafeRun(() => + { + _motionControlService.MoveToTarget(AxisId.StageX, _savedPositions.StageX); + _motionControlService.MoveToTarget(AxisId.StageY, _savedPositions.StageY); + _motionControlService.MoveToTarget(AxisId.SourceZ, _savedPositions.SourceZ); + _motionControlService.MoveToTarget(AxisId.DetectorZ, _savedPositions.DetectorZ); + _motionControlService.MoveRotaryToTarget(RotaryAxisId.DetectorSwing, _savedPositions.DetectorSwing); + _motionControlService.MoveRotaryToTarget(RotaryAxisId.StageRotation, _savedPositions.StageRotation); + _motionControlService.MoveRotaryToTarget(RotaryAxisId.FixtureRotation, _savedPositions.FixtureRotation); + }); + _logger.Info("轴位置已恢复并发送移动命令 | Axis positions restored and move commands sent"); + } + + #endregion + + #region 摇杆 Jog 映射逻辑 | Joystick Jog Mapping Logic + + /// + /// 处理双轴摇杆输出变化,根据当前激活按键映射到对应轴的 Jog 操作 + /// Handle dual joystick output changes, map to corresponding axis jog based on active button + /// + private void HandleDualJoystickOutput() + { + switch (DualJoystickActiveButton) + { + case MouseButtonType.Left: + // 左键:X→StageX Jog,Y→StageY Jog | Left button: X→StageX Jog, Y→StageY Jog + UpdateLinearJog(AxisId.StageX, DualJoystickOutputX); + UpdateLinearJog(AxisId.StageY, DualJoystickOutputY); + break; + + case MouseButtonType.Right: + // 右键:X→DetectorSwing Jog,Y→StageRotation 或 FixtureRotation Jog + // Right button: X→DetectorSwing Jog, Y→StageRotation or FixtureRotation Jog + UpdateRotaryJog(RotaryAxisId.DetectorSwing, DualJoystickOutputX); + var rotationAxisId = GetEnabledRotationAxisId(); + if (rotationAxisId.HasValue) + UpdateRotaryJog(rotationAxisId.Value, DualJoystickOutputY); + break; + } + } + + /// + /// 处理单轴摇杆输出变化,根据当前激活按键映射到对应轴的 Jog 操作 + /// Handle single joystick output changes, map to corresponding axis jog based on active button + /// + private void HandleSingleJoystickOutput() + { + switch (SingleJoystickActiveButton) + { + case MouseButtonType.Left: + // 左键:Y→SourceZ Jog | Left button: Y→SourceZ Jog + UpdateLinearJog(AxisId.SourceZ, SingleJoystickOutputY); + break; + + case MouseButtonType.Right: + // 右键:Y→DetectorZ Jog | Right button: Y→DetectorZ Jog + UpdateLinearJog(AxisId.DetectorZ, SingleJoystickOutputY); + break; + } + } + + /// + /// 更新直线轴 Jog 状态:启动、更新速度或停止 + /// Update linear axis jog state: start, update speed, or stop + /// + /// 直线轴标识 | Linear axis identifier + /// 摇杆输出值(-1.0 ~ 1.0)| Joystick output value (-1.0 ~ 1.0) + private void UpdateLinearJog(AxisId axisId, double output) + { + if (output != 0) + { + var speedPercent = Math.Abs(output) * 100; + var positive = output > 0; + + if (!_linearJogActive[axisId]) + { + // 从零变为非零:先设速度再启动 Jog | Zero to non-zero: set speed then start jog + SafeRun(() => + { + _motionControlService.SetJogSpeed(axisId, speedPercent); + _motionControlService.JogStart(axisId, positive); + _linearJogActive[axisId] = true; + _logger.Debug("直线轴 Jog 启动:{AxisId},方向={Direction},速度={Speed}% | Linear axis jog started: {AxisId}, direction={Direction}, speed={Speed}%", + axisId, positive ? "正向" : "反向", speedPercent); + }); + } + else + { + // 已在 Jog 中:仅更新速度 | Already jogging: update speed only + SafeRun(() => + { + _motionControlService.SetJogSpeed(axisId, speedPercent); + _logger.Debug("直线轴 Jog 速度更新:{AxisId},速度={Speed}% | Linear axis jog speed updated: {AxisId}, speed={Speed}%", + axisId, speedPercent); + }); + } + } + else + { + // 从非零变为零:停止 Jog | Non-zero to zero: stop jog + if (_linearJogActive[axisId]) + { + SafeRun(() => + { + _motionControlService.JogStop(axisId); + _linearJogActive[axisId] = false; + _logger.Debug("直线轴 Jog 停止:{AxisId} | Linear axis jog stopped: {AxisId}", axisId); + }); + } + } + } + + /// + /// 更新旋转轴 Jog 状态:启动、更新速度或停止 + /// Update rotary axis jog state: start, update speed, or stop + /// + /// 旋转轴标识 | Rotary axis identifier + /// 摇杆输出值(-1.0 ~ 1.0)| Joystick output value (-1.0 ~ 1.0) + private void UpdateRotaryJog(RotaryAxisId axisId, double output) + { + if (output != 0) + { + var speedPercent = Math.Abs(output) * 100; + var positive = output > 0; + + if (!_rotaryJogActive[axisId]) + { + // 从零变为非零:先设速度再启动 Jog | Zero to non-zero: set speed then start jog + SafeRun(() => + { + _motionControlService.SetJogRotarySpeed(axisId, speedPercent); + _motionControlService.JogRotaryStart(axisId, positive); + _rotaryJogActive[axisId] = true; + _logger.Debug("旋转轴 Jog 启动:{AxisId},方向={Direction},速度={Speed}% | Rotary axis jog started: {AxisId}, direction={Direction}, speed={Speed}%", + axisId, positive ? "正向" : "反向", speedPercent); + }); + } + else + { + // 已在 Jog 中:仅更新速度 | Already jogging: update speed only + SafeRun(() => + { + _motionControlService.SetJogRotarySpeed(axisId, speedPercent); + _logger.Debug("旋转轴 Jog 速度更新:{AxisId},速度={Speed}% | Rotary axis jog speed updated: {AxisId}, speed={Speed}%", + axisId, speedPercent); + }); + } + } + else + { + // 从非零变为零:停止 Jog | Non-zero to zero: stop jog + if (_rotaryJogActive[axisId]) + { + SafeRun(() => + { + _motionControlService.JogRotaryStop(axisId); + _rotaryJogActive[axisId] = false; + _logger.Debug("旋转轴 Jog 停止:{AxisId} | Rotary axis jog stopped: {AxisId}", axisId); + }); + } + } + } + + /// + /// 停止双轴摇杆控制的所有轴 Jog | Stop all axes controlled by dual joystick + /// + private void StopDualJoystickAxes() + { + // 左键关联轴:StageX、StageY | Left button axes: StageX, StageY + UpdateLinearJog(AxisId.StageX, 0); + UpdateLinearJog(AxisId.StageY, 0); + + // 右键关联轴:DetectorSwing、StageRotation/FixtureRotation | Right button axes: DetectorSwing, StageRotation/FixtureRotation + UpdateRotaryJog(RotaryAxisId.DetectorSwing, 0); + var rotationAxisId = GetEnabledRotationAxisId(); + if (rotationAxisId.HasValue) + UpdateRotaryJog(rotationAxisId.Value, 0); + } + + /// + /// 停止单轴摇杆控制的所有轴 Jog | Stop all axes controlled by single joystick + /// + private void StopSingleJoystickAxes() + { + // 左键关联轴:SourceZ | Left button axis: SourceZ + UpdateLinearJog(AxisId.SourceZ, 0); + + // 右键关联轴:DetectorZ | Right button axis: DetectorZ + UpdateLinearJog(AxisId.DetectorZ, 0); + } + + /// + /// 获取当前启用的旋转轴标识(StageRotation 或 FixtureRotation 二选一) + /// Get the currently enabled rotation axis ID (StageRotation or FixtureRotation, one of two) + /// + /// 启用的旋转轴标识,若均未启用则返回 null | Enabled rotary axis ID, or null if none enabled + private RotaryAxisId? GetEnabledRotationAxisId() + { + if (_config.RotaryAxes.ContainsKey(RotaryAxisId.StageRotation) + && _config.RotaryAxes[RotaryAxisId.StageRotation].Enabled) + return RotaryAxisId.StageRotation; + + if (_config.RotaryAxes.ContainsKey(RotaryAxisId.FixtureRotation) + && _config.RotaryAxes[RotaryAxisId.FixtureRotation].Enabled) + return RotaryAxisId.FixtureRotation; + + return null; + } + + #endregion + + #region 输入框编辑与目标位置 | Input Box Editing and Target Position + + /// + /// 设置指定输入框的编辑状态 | Set editing state for specified input box + /// GotFocus 时设为 true 冻结实时更新,LostFocus 时设为 false 恢复更新 + /// Set to true on GotFocus to freeze live updates, false on LostFocus to resume + /// + /// 属性名称 | Property name + /// 是否正在编辑 | Whether editing + public void SetEditing(string propertyName, bool isEditing) + { + _editingFlags[propertyName] = isEditing; + } + + /// + /// 确认输入框编辑,发送目标位置移动命令 | Confirm input box edit, send target position move command + /// Enter 键触发,调用 MoveToTarget/MoveRotaryToTarget 后恢复实时更新 + /// Triggered by Enter key, calls MoveToTarget/MoveRotaryToTarget then resumes live updates + /// + /// 属性名称 | Property name + public void ConfirmPosition(string propertyName) + { + var value = GetPropertyValue(propertyName); + SafeRun(() => + { + var result = SendMoveCommand(propertyName, value); + if (result.Success) + _logger.Info("目标位置已发送:{Property}={Value} | Target position sent: {Property}={Value}", propertyName, value); + else + _logger.Warn("目标位置发送失败:{Property}={Value},原因={Reason} | Target position send failed: {Property}={Value}, reason={Reason}", propertyName, value, result.ErrorMessage); + }); + _editingFlags[propertyName] = false; + } + + /// + /// 取消输入框编辑,恢复实时更新并显示当前实际值 | Cancel input box edit, resume live updates and show actual value + /// Escape 键或 LostFocus 触发 | Triggered by Escape key or LostFocus + /// + /// 属性名称 | Property name + public void CancelEditing(string propertyName) + { + _editingFlags[propertyName] = false; + RestoreActualValue(propertyName); + } + + /// + /// 步进移动:上下箭头改变数值并直接发送移动命令,不进入编辑冻结 + /// Step move: arrow keys change value and send move command directly, without entering editing freeze + /// + /// 属性名称 | Property name + /// 步进增量(默认 ±0.1)| Step delta (default ±0.1) + public void StepPosition(string propertyName, double delta) + { + var currentValue = GetPropertyValue(propertyName); + var newValue = currentValue + delta; + SetPropertyValue(propertyName, newValue); + SafeRun(() => + { + var result = SendMoveCommand(propertyName, newValue); + if (!result.Success) + _logger.Warn("步进移动失败:{Property},原因={Reason} | Step move failed: {Property}, reason={Reason}", propertyName, result.ErrorMessage); + }); + } + + /// + /// 根据属性名称发送对应轴的移动命令 | Send move command for corresponding axis based on property name + /// + /// 属性名称 | Property name + /// 目标值 | Target value + /// 操作结果 | Operation result + private MotionResult SendMoveCommand(string propertyName, double value) + { + switch (propertyName) + { + case nameof(StageXPosition): + return _motionControlService.MoveToTarget(AxisId.StageX, value); + case nameof(StageYPosition): + return _motionControlService.MoveToTarget(AxisId.StageY, value); + case nameof(SourceZPosition): + return _motionControlService.MoveToTarget(AxisId.SourceZ, value); + case nameof(DetectorZPosition): + return _motionControlService.MoveToTarget(AxisId.DetectorZ, value); + case nameof(DetectorSwingAngle): + return _motionControlService.MoveRotaryToTarget(RotaryAxisId.DetectorSwing, value); + case nameof(StageRotationAngle): + return _motionControlService.MoveRotaryToTarget(RotaryAxisId.StageRotation, value); + case nameof(FixtureRotationAngle): + return _motionControlService.MoveRotaryToTarget(RotaryAxisId.FixtureRotation, value); + default: + return MotionResult.Fail($"未知的属性名称:{propertyName} | Unknown property name: {propertyName}"); + } + } + + /// + /// 根据属性名称获取当前绑定值 | Get current bound value by property name + /// + private double GetPropertyValue(string propertyName) + { + switch (propertyName) + { + case nameof(StageXPosition): return StageXPosition; + case nameof(StageYPosition): return StageYPosition; + case nameof(SourceZPosition): return SourceZPosition; + case nameof(DetectorZPosition): return DetectorZPosition; + case nameof(DetectorSwingAngle): return DetectorSwingAngle; + case nameof(StageRotationAngle): return StageRotationAngle; + case nameof(FixtureRotationAngle): return FixtureRotationAngle; + default: return 0; + } + } + + /// + /// 根据属性名称设置绑定值 | Set bound value by property name + /// + private void SetPropertyValue(string propertyName, double value) + { + switch (propertyName) + { + case nameof(StageXPosition): StageXPosition = value; break; + case nameof(StageYPosition): StageYPosition = value; break; + case nameof(SourceZPosition): SourceZPosition = value; break; + case nameof(DetectorZPosition): DetectorZPosition = value; break; + case nameof(DetectorSwingAngle): DetectorSwingAngle = value; break; + case nameof(StageRotationAngle): StageRotationAngle = value; break; + case nameof(FixtureRotationAngle): FixtureRotationAngle = value; break; + } + } + + /// + /// 恢复输入框为轴的当前实际值 | Restore input box to axis actual value + /// + private void RestoreActualValue(string propertyName) + { + try + { + switch (propertyName) + { + case nameof(StageXPosition): + StageXPosition = _motionSystem.GetLinearAxis(AxisId.StageX).ActualPosition; + break; + case nameof(StageYPosition): + StageYPosition = _motionSystem.GetLinearAxis(AxisId.StageY).ActualPosition; + break; + case nameof(SourceZPosition): + SourceZPosition = _motionSystem.GetLinearAxis(AxisId.SourceZ).ActualPosition; + break; + case nameof(DetectorZPosition): + DetectorZPosition = _motionSystem.GetLinearAxis(AxisId.DetectorZ).ActualPosition; + break; + case nameof(DetectorSwingAngle): + DetectorSwingAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.DetectorSwing).ActualAngle; + break; + case nameof(StageRotationAngle): + StageRotationAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.StageRotation).ActualAngle; + break; + case nameof(FixtureRotationAngle): + FixtureRotationAngle = _motionSystem.GetRotaryAxis(RotaryAxisId.FixtureRotation).ActualAngle; + break; + } + } + catch { /* 轴未初始化时忽略 | Ignore when axes not initialized */ } + } + + #endregion + + #region 安全参数逻辑 | Safety Parameter Logic + + /// + /// 确认探测器安全高度限定值 | Confirm detector safety height limit value + /// Enter 键触发,保存当前值 | Triggered by Enter key, saves current value + /// + public void ConfirmSafetyHeight() + { + _logger.Info("探测器安全高度限定值已保存:{Value} | Detector safety height limit saved: {Value}", SafetyHeight); + } + + /// + /// 确认校准自动计算值 | Confirm calibration auto-calculated value + /// Enter 键触发,保存当前值 | Triggered by Enter key, saves current value + /// + public void ConfirmCalibrationValue() + { + _logger.Info("校准自动计算值已保存:{Value} | Calibration auto-calculated value saved: {Value}", CalibrationValue); + } + + #endregion + + #region 内部数据类 | Internal Data Classes + + /// + /// 保存的轴位置数据 | Saved axis position data + /// + private class SavedPositions + { + public double StageX { get; set; } + public double StageY { get; set; } + public double SourceZ { get; set; } + public double DetectorZ { get; set; } + public double DetectorSwing { get; set; } + public double StageRotation { get; set; } + public double FixtureRotation { get; set; } + } + + #endregion + } +} diff --git a/XP.Hardware.MotionControl/Views/AxisControlView.xaml b/XP.Hardware.MotionControl/Views/AxisControlView.xaml new file mode 100644 index 0000000..07023a9 --- /dev/null +++ b/XP.Hardware.MotionControl/Views/AxisControlView.xaml @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/XP.Hardware.MotionControl/Views/AxisControlView.xaml.cs b/XP.Hardware.MotionControl/Views/AxisControlView.xaml.cs new file mode 100644 index 0000000..58a421b --- /dev/null +++ b/XP.Hardware.MotionControl/Views/AxisControlView.xaml.cs @@ -0,0 +1,234 @@ +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using XP.Common.Controls; +using XP.Hardware.MotionControl.ViewModels; + +namespace XP.Hardware.MotionControl.Views +{ + /// + /// 轴控制面板视图 | Axis Control Panel View + /// 集成摇杆、轴位置输入框、安全参数和使能控制的 UserControl + /// UserControl integrating joysticks, axis position inputs, safety parameters and enable control + /// + public partial class AxisControlView : UserControl + { + public AxisControlView() + { + InitializeComponent(); + + // 监听摇杆只读依赖属性变化,推送到 ViewModel | Listen to joystick read-only DP changes and push to ViewModel + var dualOutputXDesc = DependencyPropertyDescriptor.FromProperty(VirtualJoystick.OutputXProperty, typeof(VirtualJoystick)); + var dualOutputYDesc = DependencyPropertyDescriptor.FromProperty(VirtualJoystick.OutputYProperty, typeof(VirtualJoystick)); + var dualButtonDesc = DependencyPropertyDescriptor.FromProperty(VirtualJoystick.ActiveMouseButtonProperty, typeof(VirtualJoystick)); + var singleOutputYDesc = DependencyPropertyDescriptor.FromProperty(VirtualJoystick.OutputYProperty, typeof(VirtualJoystick)); + var singleButtonDesc = DependencyPropertyDescriptor.FromProperty(VirtualJoystick.ActiveMouseButtonProperty, typeof(VirtualJoystick)); + + dualOutputXDesc?.AddValueChanged(DualJoystick, (s, e) => + { + if (ViewModel != null) ViewModel.DualJoystickOutputX = DualJoystick.OutputX; + }); + dualOutputYDesc?.AddValueChanged(DualJoystick, (s, e) => + { + if (ViewModel != null) ViewModel.DualJoystickOutputY = DualJoystick.OutputY; + }); + dualButtonDesc?.AddValueChanged(DualJoystick, (s, e) => + { + if (ViewModel != null) ViewModel.DualJoystickActiveButton = DualJoystick.ActiveMouseButton; + }); + singleOutputYDesc?.AddValueChanged(SingleJoystick, (s, e) => + { + if (ViewModel != null) ViewModel.SingleJoystickOutputY = SingleJoystick.OutputY; + }); + singleButtonDesc?.AddValueChanged(SingleJoystick, (s, e) => + { + if (ViewModel != null) ViewModel.SingleJoystickActiveButton = SingleJoystick.ActiveMouseButton; + }); + } + + /// + /// 获取当前 ViewModel 实例 | Get current ViewModel instance + /// + private AxisControlViewModel ViewModel => DataContext as AxisControlViewModel; + + #region Stage X 事件处理 | Stage X Event Handlers + + private void NumStageX_GotFocus(object sender, RoutedEventArgs e) + { + ViewModel?.SetEditing(nameof(AxisControlViewModel.StageXPosition), true); + } + + private void NumStageX_LostFocus(object sender, RoutedEventArgs e) + { + ViewModel?.CancelEditing(nameof(AxisControlViewModel.StageXPosition)); + } + + private void NumStageX_KeyDown(object sender, KeyEventArgs e) + { + HandleAxisKeyDown(nameof(AxisControlViewModel.StageXPosition), e); + } + + #endregion + + #region Stage Y 事件处理 | Stage Y Event Handlers + + private void NumStageY_GotFocus(object sender, RoutedEventArgs e) + { + ViewModel?.SetEditing(nameof(AxisControlViewModel.StageYPosition), true); + } + + private void NumStageY_LostFocus(object sender, RoutedEventArgs e) + { + ViewModel?.CancelEditing(nameof(AxisControlViewModel.StageYPosition)); + } + + private void NumStageY_KeyDown(object sender, KeyEventArgs e) + { + HandleAxisKeyDown(nameof(AxisControlViewModel.StageYPosition), e); + } + + #endregion + + #region Source Z 事件处理 | Source Z Event Handlers + + private void NumSourceZ_GotFocus(object sender, RoutedEventArgs e) + { + ViewModel?.SetEditing(nameof(AxisControlViewModel.SourceZPosition), true); + } + + private void NumSourceZ_LostFocus(object sender, RoutedEventArgs e) + { + ViewModel?.CancelEditing(nameof(AxisControlViewModel.SourceZPosition)); + } + + private void NumSourceZ_KeyDown(object sender, KeyEventArgs e) + { + HandleAxisKeyDown(nameof(AxisControlViewModel.SourceZPosition), e); + } + + #endregion + + #region Detector Z 事件处理 | Detector Z Event Handlers + + private void NumDetectorZ_GotFocus(object sender, RoutedEventArgs e) + { + ViewModel?.SetEditing(nameof(AxisControlViewModel.DetectorZPosition), true); + } + + private void NumDetectorZ_LostFocus(object sender, RoutedEventArgs e) + { + ViewModel?.CancelEditing(nameof(AxisControlViewModel.DetectorZPosition)); + } + + private void NumDetectorZ_KeyDown(object sender, KeyEventArgs e) + { + HandleAxisKeyDown(nameof(AxisControlViewModel.DetectorZPosition), e); + } + + #endregion + + #region Detector Swing 事件处理 | Detector Swing Event Handlers + + private void NumDetSwing_GotFocus(object sender, RoutedEventArgs e) + { + ViewModel?.SetEditing(nameof(AxisControlViewModel.DetectorSwingAngle), true); + } + + private void NumDetSwing_LostFocus(object sender, RoutedEventArgs e) + { + ViewModel?.CancelEditing(nameof(AxisControlViewModel.DetectorSwingAngle)); + } + + private void NumDetSwing_KeyDown(object sender, KeyEventArgs e) + { + HandleAxisKeyDown(nameof(AxisControlViewModel.DetectorSwingAngle), e); + } + + #endregion + + #region Stage Rotation 事件处理 | Stage Rotation Event Handlers + + private void NumStageRot_GotFocus(object sender, RoutedEventArgs e) + { + ViewModel?.SetEditing(nameof(AxisControlViewModel.StageRotationAngle), true); + } + + private void NumStageRot_LostFocus(object sender, RoutedEventArgs e) + { + ViewModel?.CancelEditing(nameof(AxisControlViewModel.StageRotationAngle)); + } + + private void NumStageRot_KeyDown(object sender, KeyEventArgs e) + { + HandleAxisKeyDown(nameof(AxisControlViewModel.StageRotationAngle), e); + } + + #endregion + + #region Fixture Rotation 事件处理 | Fixture Rotation Event Handlers + + private void NumFixtureRot_GotFocus(object sender, RoutedEventArgs e) + { + ViewModel?.SetEditing(nameof(AxisControlViewModel.FixtureRotationAngle), true); + } + + private void NumFixtureRot_LostFocus(object sender, RoutedEventArgs e) + { + ViewModel?.CancelEditing(nameof(AxisControlViewModel.FixtureRotationAngle)); + } + + private void NumFixtureRot_KeyDown(object sender, KeyEventArgs e) + { + HandleAxisKeyDown(nameof(AxisControlViewModel.FixtureRotationAngle), e); + } + + #endregion + + #region 安全参数键盘事件 | Safety Parameter Key Events + + private void NumSafetyHeight_KeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter) + { + ViewModel?.ConfirmSafetyHeight(); + e.Handled = true; + } + } + + private void NumCalibration_KeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter) + { + ViewModel?.ConfirmCalibrationValue(); + e.Handled = true; + } + } + + #endregion + + #region 通用键盘处理 | Common Key Handler + + /// + /// 轴输入框通用键盘处理:Enter 确认,Escape 取消 | Common axis input key handler: Enter to confirm, Escape to cancel + /// + private void HandleAxisKeyDown(string propertyName, KeyEventArgs e) + { + if (ViewModel == null) return; + + switch (e.Key) + { + case Key.Enter: + ViewModel.ConfirmPosition(propertyName); + e.Handled = true; + break; + case Key.Escape: + ViewModel.CancelEditing(propertyName); + e.Handled = true; + break; + } + } + + #endregion + } +}