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
+ }
+}