1135 lines
53 KiB
C#
1135 lines
53 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Windows;
|
||
using Prism.Commands;
|
||
using Prism.Events;
|
||
using Prism.Mvvm;
|
||
using XP.Common.Controls.Joystick;
|
||
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
|
||
{
|
||
/// <summary>
|
||
/// 轴控制面板 ViewModel | Axis Control Panel ViewModel
|
||
/// 集成摇杆、轴位置输入框、安全参数和使能控制 | Integrates joystick, axis position inputs, safety parameters and enable control
|
||
/// </summary>
|
||
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
|
||
|
||
/// <summary>直线轴 Jog 活跃状态 | Linear axis jog active states</summary>
|
||
private readonly Dictionary<AxisId, bool> _linearJogActive = new();
|
||
|
||
/// <summary>旋转轴 Jog 活跃状态 | Rotary axis jog active states</summary>
|
||
private readonly Dictionary<RotaryAxisId, bool> _rotaryJogActive = new();
|
||
|
||
/// <summary>输入框编辑冻结标志 | Input box editing freeze flags</summary>
|
||
private readonly Dictionary<string, bool> _editingFlags = new();
|
||
|
||
/// <summary>保存的轴位置数据 | Saved axis position data</summary>
|
||
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<AxisControlViewModel>();
|
||
|
||
// 监听 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);
|
||
ToggleSwapMouseButtonsCommand = new DelegateCommand(ExecuteToggleSwapMouseButtons);
|
||
SavePositionsCommand = new DelegateCommand(ExecuteSavePositions, () => IsPlcConnected);
|
||
RestorePositionsCommand = new DelegateCommand(ExecuteRestorePositions, () => _savedPositions != null && IsPlcConnected);
|
||
// 射线源与探测器Z轴联动命令 | Source-Detector Z-axis linkage command
|
||
SZDZLockCommand = new DelegateCommand(() => SafeRun(ExecuteSZDZLock), () => 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<GeometryUpdatedEvent>().Subscribe(OnGeometryUpdated, ThreadOption.UIThread);
|
||
_eventAggregator.GetEvent<AxisStatusChangedEvent>().Subscribe(OnAxisStatusChanged, ThreadOption.UIThread);
|
||
_eventAggregator.GetEvent<JoystickActiveEvent>().Subscribe(OnJoystickActiveChanged, 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;
|
||
/// <summary>双轴摇杆 X 轴输出 | Dual-axis joystick X output</summary>
|
||
public double DualJoystickOutputX
|
||
{
|
||
get => _dualJoystickOutputX;
|
||
set => SetProperty(ref _dualJoystickOutputX, value);
|
||
}
|
||
|
||
private double _dualJoystickOutputY;
|
||
/// <summary>双轴摇杆 Y 轴输出 | Dual-axis joystick Y output</summary>
|
||
public double DualJoystickOutputY
|
||
{
|
||
get => _dualJoystickOutputY;
|
||
set
|
||
{
|
||
if (SetProperty(ref _dualJoystickOutputY, value))
|
||
HandleDualJoystickOutput();
|
||
}
|
||
}
|
||
|
||
private MouseButtonType _dualJoystickActiveButton;
|
||
/// <summary>双轴摇杆当前激活按键 | Dual-axis joystick active mouse button</summary>
|
||
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;
|
||
/// <summary>单轴摇杆 Y 轴输出 | Single-axis joystick Y output</summary>
|
||
public double SingleJoystickOutputY
|
||
{
|
||
get => _singleJoystickOutputY;
|
||
set
|
||
{
|
||
if (SetProperty(ref _singleJoystickOutputY, value))
|
||
HandleSingleJoystickOutput();
|
||
}
|
||
}
|
||
|
||
private MouseButtonType _singleJoystickActiveButton;
|
||
/// <summary>单轴摇杆当前激活按键 | Single-axis joystick active mouse button</summary>
|
||
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;
|
||
/// <summary>载物台 X 轴位置 | Stage X axis position</summary>
|
||
public double StageXPosition { get => _stageXPosition; set => SetProperty(ref _stageXPosition, value); }
|
||
|
||
private double _stageYPosition;
|
||
/// <summary>载物台 Y 轴位置 | Stage Y axis position</summary>
|
||
public double StageYPosition { get => _stageYPosition; set => SetProperty(ref _stageYPosition, value); }
|
||
|
||
private double _sourceZPosition;
|
||
/// <summary>射线源 Z 轴位置 | Source Z axis position</summary>
|
||
public double SourceZPosition { get => _sourceZPosition; set => SetProperty(ref _sourceZPosition, value); }
|
||
|
||
private double _detectorZPosition;
|
||
/// <summary>探测器 Z 轴位置 | Detector Z axis position</summary>
|
||
public double DetectorZPosition { get => _detectorZPosition; set => SetProperty(ref _detectorZPosition, value); }
|
||
|
||
private double _detectorSwingAngle;
|
||
/// <summary>探测器摆动角度 | Detector swing angle</summary>
|
||
public double DetectorSwingAngle { get => _detectorSwingAngle; set => SetProperty(ref _detectorSwingAngle, value); }
|
||
|
||
private double _stageRotationAngle;
|
||
/// <summary>载物台旋转角度 | Stage rotation angle</summary>
|
||
public double StageRotationAngle { get => _stageRotationAngle; set => SetProperty(ref _stageRotationAngle, value); }
|
||
|
||
private double _fixtureRotationAngle;
|
||
/// <summary>夹具旋转角度 | Fixture rotation angle</summary>
|
||
public double FixtureRotationAngle { get => _fixtureRotationAngle; set => SetProperty(ref _fixtureRotationAngle, value); }
|
||
|
||
#endregion
|
||
|
||
#region 旋转轴可见性 | Rotary Axis Visibility
|
||
|
||
private Visibility _detectorSwingVisibility;
|
||
/// <summary>探测器摆动输入框可见性 | Detector swing input box visibility</summary>
|
||
public Visibility DetectorSwingVisibility { get => _detectorSwingVisibility; private set => SetProperty(ref _detectorSwingVisibility, value); }
|
||
|
||
private Visibility _stageRotationVisibility;
|
||
/// <summary>载物台旋转输入框可见性 | Stage rotation input box visibility</summary>
|
||
public Visibility StageRotationVisibility { get => _stageRotationVisibility; private set => SetProperty(ref _stageRotationVisibility, value); }
|
||
|
||
private Visibility _fixtureRotationVisibility;
|
||
/// <summary>夹具旋转输入框可见性 | Fixture rotation input box visibility</summary>
|
||
public Visibility FixtureRotationVisibility { get => _fixtureRotationVisibility; private set => SetProperty(ref _fixtureRotationVisibility, value); }
|
||
|
||
#endregion
|
||
|
||
#region 安全参数属性 | Safety Parameter Properties
|
||
|
||
private double _safetyHeight;
|
||
/// <summary>探测器安全高度限定值 | Detector safety height limit</summary>
|
||
public double SafetyHeight { get => _safetyHeight; set => SetProperty(ref _safetyHeight, value); }
|
||
|
||
private double _calibrationValue;
|
||
/// <summary>校准自动计算值 | Calibration auto-calculated value</summary>
|
||
public double CalibrationValue { get => _calibrationValue; set => SetProperty(ref _calibrationValue, value); }
|
||
|
||
#endregion
|
||
|
||
#region 使能与状态属性 | Enable and Status Properties
|
||
|
||
private bool _isJoystickEnabled = true;
|
||
/// <summary>摇杆使能状态 | Joystick enable state</summary>
|
||
public bool IsJoystickEnabled { get => _isJoystickEnabled; set => SetProperty(ref _isJoystickEnabled, value); }
|
||
|
||
private bool _swapMouseButtons;
|
||
/// <summary>是否交换摇杆左右键功能 | Whether to swap left and right joystick button functions</summary>
|
||
public bool SwapMouseButtons { get => _swapMouseButtons; set => SetProperty(ref _swapMouseButtons, value); }
|
||
|
||
private bool _isPlcConnected;
|
||
/// <summary>PLC 连接状态 | PLC connection status</summary>
|
||
public bool IsPlcConnected { get => _isPlcConnected; set => SetProperty(ref _isPlcConnected, value); }
|
||
|
||
private bool _isJoystickActive;
|
||
/// <summary>实体摇杆是否激活 | Whether physical joystick is active</summary>
|
||
public bool IsJoystickActive { get => _isJoystickActive; set => SetProperty(ref _isJoystickActive, value); }
|
||
|
||
private string _errorMessage;
|
||
/// <summary>错误提示信息 | Error message</summary>
|
||
public string ErrorMessage { get => _errorMessage; set => SetProperty(ref _errorMessage, value); }
|
||
|
||
// 射线源与探测器Z轴联动状态 | Source-Detector Z-axis linkage state
|
||
private bool _szdzLock;
|
||
public bool SZDZLock { get => _szdzLock; set => SetProperty(ref _szdzLock, value); }
|
||
|
||
#endregion
|
||
|
||
#region 保存位置状态 | Saved Positions State
|
||
|
||
/// <summary>是否有保存的位置数据 | Whether saved position data exists</summary>
|
||
public bool HasSavedPositions => _savedPositions != null;
|
||
|
||
#endregion
|
||
|
||
#region 命令 | Commands
|
||
|
||
/// <summary>切换使能开关命令 | Toggle enable switch command</summary>
|
||
public DelegateCommand ToggleEnableCommand { get; }
|
||
|
||
/// <summary>切换摇杆左右键功能命令 | Toggle joystick button swap command</summary>
|
||
public DelegateCommand ToggleSwapMouseButtonsCommand { get; }
|
||
|
||
/// <summary>保存当前轴位置命令 | Save current axis positions command</summary>
|
||
public DelegateCommand SavePositionsCommand { get; }
|
||
|
||
/// <summary>恢复保存的轴位置命令 | Restore saved axis positions command</summary>
|
||
public DelegateCommand RestorePositionsCommand { get; }
|
||
|
||
/// <summary>射线源与探测器Z轴锁定移动命令 | Source-Detector Z-axis lock move command</summary>
|
||
public DelegateCommand SZDZLockCommand { get; }
|
||
#endregion
|
||
|
||
#region 事件回调 | Event Callbacks
|
||
|
||
/// <summary>
|
||
/// 实体摇杆激活状态变化回调 | Physical joystick active status changed callback
|
||
/// </summary>
|
||
/// <param name="isActive">实体摇杆是否激活 | Whether physical joystick is active</param>
|
||
private void OnJoystickActiveChanged(bool isActive)
|
||
{
|
||
IsJoystickActive = isActive;
|
||
_logger.Debug("实体摇杆状态更新:{IsActive} | Physical joystick status updated: {IsActive}", isActive);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 几何参数更新回调,刷新轴实际位置 | Geometry updated callback, refresh axis actual positions
|
||
/// </summary>
|
||
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 */ }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 轴状态变化回调,更新对应轴位置 | Axis status changed callback, update corresponding axis position
|
||
/// </summary>
|
||
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
|
||
|
||
/// <summary>
|
||
/// 检查指定输入框是否正在编辑 | Check if specified input box is being edited
|
||
/// </summary>
|
||
/// <param name="propertyName">属性名称 | Property name</param>
|
||
/// <returns>是否正在编辑 | Whether editing</returns>
|
||
private bool IsEditing(string propertyName)
|
||
{
|
||
return _editingFlags.TryGetValue(propertyName, out var editing) && editing;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 异常保护 | Exception Protection
|
||
|
||
/// <summary>
|
||
/// 安全执行:捕获异常避免 UI 崩溃 | Safe execution: catches exceptions to prevent UI crash
|
||
/// </summary>
|
||
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
|
||
|
||
/// <summary>
|
||
/// 刷新所有命令的 CanExecute 状态 | Refresh CanExecute state of all commands
|
||
/// </summary>
|
||
private void RaiseCommandCanExecuteChanged()
|
||
{
|
||
ToggleEnableCommand.RaiseCanExecuteChanged();
|
||
ToggleSwapMouseButtonsCommand.RaiseCanExecuteChanged();
|
||
SavePositionsCommand.RaiseCanExecuteChanged();
|
||
RestorePositionsCommand.RaiseCanExecuteChanged();
|
||
}
|
||
|
||
/// <summary>
|
||
/// PLC 连接状态变化处理:断开时停止所有 Jog 并禁用操作,重连时清除错误
|
||
/// PLC connection state change handler: stop all jog and disable operations on disconnect, clear error on reconnect
|
||
/// </summary>
|
||
private void OnPlcConnectionChanged()
|
||
{
|
||
IsPlcConnected = _plcService.IsConnected;
|
||
RaiseCommandCanExecuteChanged();
|
||
|
||
if (!_plcService.IsConnected)
|
||
{
|
||
// PLC 断开:停止所有活跃的 Jog 操作 | PLC disconnected: stop all active jog operations
|
||
foreach (var axisId in new List<AxisId>(_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 new List<RotaryAxisId>(_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
|
||
|
||
/// <summary>
|
||
/// 切换摇杆使能状态,并发送使能状态到 PLC | Toggle joystick enable state and send to PLC
|
||
/// </summary>
|
||
private void ExecuteToggleEnable()
|
||
{
|
||
IsJoystickEnabled = !IsJoystickEnabled;
|
||
_logger.Info("摇杆使能状态切换:{Enabled} | Joystick enable toggled: {Enabled}", IsJoystickEnabled);
|
||
// 发送使能状态到 PLC(根据实际 PLC 信号定义)| Send enable state to PLC (based on actual PLC signal definition)
|
||
try
|
||
{
|
||
// 设置虚拟摇杆使能 | Set virtual joystick enable
|
||
var result = _motionControlService.SetVirtualJoystickEnable(IsJoystickEnabled);
|
||
|
||
if (!result.Success)
|
||
{
|
||
// 写入失败,回滚状态 | Write failed, rollback state
|
||
IsJoystickEnabled = !IsJoystickEnabled;
|
||
MessageBox.Show(result.ErrorMessage, XP.Common.Localization.LocalizationHelper.Get("MC_Error"),
|
||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 异常时回滚状态 | Rollback state on exception
|
||
IsJoystickEnabled = !IsJoystickEnabled;
|
||
MessageBox.Show(ex.Message, XP.Common.Localization.LocalizationHelper.Get("MC_Error"),
|
||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换左右键交换状态 | Toggle swap mouse buttons state
|
||
/// </summary>
|
||
private void ExecuteToggleSwapMouseButtons()
|
||
{
|
||
SwapMouseButtons = !SwapMouseButtons;
|
||
_logger.Info("摇杆左右键功能交换:{Enabled} | Joystick button swap toggled: {Enabled}", SwapMouseButtons);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存当前 6 个轴位置到内部变量 | Save current 6 axis positions to internal variable
|
||
/// </summary>
|
||
private void ExecuteSavePositions()
|
||
{
|
||
_savedPositions = new SavedPositions
|
||
{
|
||
StageX = StageXPosition,
|
||
StageY = StageYPosition,
|
||
SourceZ = SourceZPosition,
|
||
DetectorZ = DetectorZPosition,
|
||
DetectorSwing = DetectorSwingAngle,
|
||
StageRotation = StageRotationAngle,
|
||
FixtureRotation = FixtureRotationAngle
|
||
};
|
||
RestorePositionsCommand.RaiseCanExecuteChanged();
|
||
RaisePropertyChanged(nameof(HasSavedPositions));
|
||
_logger.Info("轴位置已保存 | Axis positions saved");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从保存的数据恢复到输入框,并发送移动命令 | Restore saved data to input boxes and send move commands
|
||
/// </summary>
|
||
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");
|
||
}
|
||
|
||
/// <summary>射线源与探测器Z轴锁定移动 | Source-Detector Z-axis lock move</summary>
|
||
private void ExecuteSZDZLock()
|
||
{
|
||
try
|
||
{
|
||
// 切换联动状态 | Toggle linkage state
|
||
SZDZLock = !SZDZLock;
|
||
|
||
// 调用服务设置联动 | Call service to set linkage
|
||
var result = _motionControlService.SetSourceDetectorZLinkage(SZDZLock);
|
||
|
||
if (!result.Success)
|
||
{
|
||
// 如果设置失败,恢复状态 | If set failed, restore state
|
||
SZDZLock = !SZDZLock;
|
||
MessageBox.Show(result.ErrorMessage, XP.Common.Localization.LocalizationHelper.Get("MC_Error"),
|
||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 异常时恢复状态 | Restore state on exception
|
||
SZDZLock = !SZDZLock;
|
||
MessageBox.Show(ex.Message, XP.Common.Localization.LocalizationHelper.Get("MC_Error"),
|
||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 同步联动状态到 PLC(供视图加载时调用)| Sync linkage state to PLC (called when view loads)
|
||
/// </summary>
|
||
/// <returns>操作结果 | Operation result</returns>
|
||
public MotionResult SetSZDZLinkageToPlc()
|
||
{
|
||
return _motionControlService.SetSourceDetectorZLinkage(SZDZLock);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取日志服务实例 | Get logger service instance
|
||
/// </summary>
|
||
public ILoggerService Logger => _logger;
|
||
#endregion
|
||
|
||
#region 摇杆 Jog 映射逻辑 | Joystick Jog Mapping Logic
|
||
|
||
/// <summary>
|
||
/// 处理双轴摇杆输出变化,根据当前激活按键映射到对应轴的 Jog 操作
|
||
/// Handle dual joystick output changes, map to corresponding axis jog based on active button
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理单轴摇杆输出变化,根据当前激活按键映射到对应轴的 Jog 操作
|
||
/// Handle single joystick output changes, map to corresponding axis jog based on active button
|
||
/// </summary>
|
||
private void HandleSingleJoystickOutput()
|
||
{
|
||
switch (SingleJoystickActiveButton)
|
||
{
|
||
case MouseButtonType.Left:
|
||
// 左键:Y→SourceZ Jog | Left button: Y→SourceZ Jog
|
||
UpdateLinearJog(AxisId.SourceZ, SingleJoystickOutputY);
|
||
if (_szdzLock)
|
||
{
|
||
UpdateLinearJog(AxisId.DetectorZ, SingleJoystickOutputY);
|
||
}
|
||
break;
|
||
|
||
case MouseButtonType.Right:
|
||
// 右键:Y→DetectorZ Jog | Right button: Y→DetectorZ Jog
|
||
UpdateLinearJog(AxisId.DetectorZ, SingleJoystickOutputY);
|
||
if (_szdzLock)
|
||
{
|
||
UpdateLinearJog(AxisId.SourceZ, SingleJoystickOutputY);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新直线轴 Jog 状态:启动、更新速度或停止
|
||
/// Update linear axis jog state: start, update speed, or stop
|
||
/// </summary>
|
||
/// <param name="axisId">直线轴标识 | Linear axis identifier</param>
|
||
/// <param name="output">摇杆输出值(-1.0 ~ 1.0)| Joystick output value (-1.0 ~ 1.0)</param>
|
||
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);
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新旋转轴 Jog 状态:启动、更新速度或停止
|
||
/// Update rotary axis jog state: start, update speed, or stop
|
||
/// </summary>
|
||
/// <param name="axisId">旋转轴标识 | Rotary axis identifier</param>
|
||
/// <param name="output">摇杆输出值(-1.0 ~ 1.0)| Joystick output value (-1.0 ~ 1.0)</param>
|
||
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);
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止双轴摇杆控制的所有轴 Jog | Stop all axes controlled by dual joystick
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止单轴摇杆控制的所有轴 Jog | Stop all axes controlled by single joystick
|
||
/// </summary>
|
||
private void StopSingleJoystickAxes()
|
||
{
|
||
// 左键关联轴:SourceZ | Left button axis: SourceZ
|
||
UpdateLinearJog(AxisId.SourceZ, 0);
|
||
|
||
// 右键关联轴:DetectorZ | Right button axis: DetectorZ
|
||
UpdateLinearJog(AxisId.DetectorZ, 0);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前启用的旋转轴标识(StageRotation 或 FixtureRotation 二选一)
|
||
/// Get the currently enabled rotation axis ID (StageRotation or FixtureRotation, one of two)
|
||
/// </summary>
|
||
/// <returns>启用的旋转轴标识,若均未启用则返回 null | Enabled rotary axis ID, or null if none enabled</returns>
|
||
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
|
||
|
||
/// <summary>
|
||
/// 设置指定输入框的编辑状态 | 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
|
||
/// </summary>
|
||
/// <param name="propertyName">属性名称 | Property name</param>
|
||
/// <param name="isEditing">是否正在编辑 | Whether editing</param>
|
||
public void SetEditing(string propertyName, bool isEditing)
|
||
{
|
||
_editingFlags[propertyName] = isEditing;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确认输入框编辑,发送目标位置移动命令 | Confirm input box edit, send target position move command
|
||
/// Enter 键触发,调用 MoveToTarget/MoveRotaryToTarget 后恢复实时更新
|
||
/// Triggered by Enter key, calls MoveToTarget/MoveRotaryToTarget then resumes live updates
|
||
/// </summary>
|
||
/// <param name="propertyName">属性名称 | Property name</param>
|
||
public void ConfirmPosition(string propertyName)
|
||
{
|
||
var value = GetPropertyValue(propertyName);
|
||
SafeRun(() =>
|
||
{
|
||
MotionResult result;
|
||
// 如果射线源与探测器Z轴联动,且操作的是SourceZ或DetectorZ,则同时发送两个轴
|
||
if (SZDZLock && (propertyName == nameof(SourceZPosition) || propertyName == nameof(DetectorZPosition)))
|
||
{
|
||
result = SendSourceDetectorZMoveCommand(value, propertyName);
|
||
}
|
||
else
|
||
{
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 取消输入框编辑,恢复实时更新并显示当前实际值 | Cancel input box edit, resume live updates and show actual value
|
||
/// Escape 键或 LostFocus 触发 | Triggered by Escape key or LostFocus
|
||
/// </summary>
|
||
/// <param name="propertyName">属性名称 | Property name</param>
|
||
public void CancelEditing(string propertyName)
|
||
{
|
||
_editingFlags[propertyName] = false;
|
||
RestoreActualValue(propertyName);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 步进移动:上下箭头改变数值并直接发送移动命令,不进入编辑冻结
|
||
/// Step move: arrow keys change value and send move command directly, without entering editing freeze
|
||
/// </summary>
|
||
/// <param name="propertyName">属性名称 | Property name</param>
|
||
/// <param name="delta">步进增量(默认 ±0.1)| Step delta (default ±0.1)</param>
|
||
public void StepPosition(string propertyName, double delta)
|
||
{
|
||
var currentValue = GetPropertyValue(propertyName);
|
||
var newValue = currentValue + delta;
|
||
SetPropertyValue(propertyName, newValue);
|
||
SafeRun(() =>
|
||
{
|
||
MotionResult result;
|
||
// 如果射线源与探测器Z轴联动,且操作的是SourceZ或DetectorZ,则同时步进两个轴
|
||
if (SZDZLock && (propertyName == nameof(SourceZPosition) || propertyName == nameof(DetectorZPosition)))
|
||
{
|
||
result = SendSourceDetectorZMoveCommand(newValue, propertyName);
|
||
}
|
||
else
|
||
{
|
||
result = SendMoveCommand(propertyName, newValue);
|
||
}
|
||
if (!result.Success)
|
||
_logger.Warn("步进移动失败:{Property},原因={Reason} | Step move failed: {Property}, reason={Reason}", propertyName, result.ErrorMessage);
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据属性名称发送对应轴的移动命令 | Send move command for corresponding axis based on property name
|
||
/// </summary>
|
||
/// <param name="propertyName">属性名称 | Property name</param>
|
||
/// <param name="value">目标值 | Target value</param>
|
||
/// <returns>操作结果 | Operation result</returns>
|
||
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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送射线源与探测器Z轴联动移动命令 | Send Source-Detector Z-axis linkage move command
|
||
/// 当SZDZLock=true时,保持两个轴的位移量相同
|
||
/// When SZDZLock=true, keeps the same displacement for both axes
|
||
/// </summary>
|
||
/// <param name="targetValue">目标位置 | Target position</param>
|
||
/// <returns>操作结果 | Operation result</returns>
|
||
private MotionResult SendSourceDetectorZMoveCommand(double targetValue, string sourcePropertyName = null)
|
||
{
|
||
// 1. 检查联动是否在配置中启用 | Check if linkage is enabled in config
|
||
if (!_config.SourceDetectorZLinkage.Enabled)
|
||
{
|
||
_logger.Warn("射线源与探测器Z轴联动未在配置中启用 | Source-Detector Z-axis linkage not enabled in config");
|
||
return MotionResult.Fail("射线源与探测器Z轴联动未启用 | Source-Detector Z-axis linkage not enabled");
|
||
}
|
||
|
||
// 2. 获取两个轴并检查是否都在空闲状态 | Get both axes and check if both are idle
|
||
var sourceZAxis = _motionSystem.GetLinearAxis(AxisId.SourceZ);
|
||
var detectorZAxis = _motionSystem.GetLinearAxis(AxisId.DetectorZ);
|
||
|
||
if (sourceZAxis.Status == AxisStatus.Moving)
|
||
{
|
||
_logger.Warn("射线源Z轴正在运动中,拒绝联动移动命令 | Source Z-axis is moving, linkage move command rejected");
|
||
return MotionResult.Fail("射线源Z轴正在运动中,无法联动移动 | Source Z-axis is moving, linkage move rejected");
|
||
}
|
||
|
||
if (detectorZAxis.Status == AxisStatus.Moving)
|
||
{
|
||
_logger.Warn("探测器Z轴正在运动中,拒绝联动移动命令 | Detector Z-axis is moving, linkage move command rejected");
|
||
return MotionResult.Fail("探测器Z轴正在运动中,无法联动移动 | Detector Z-axis is moving, linkage move rejected");
|
||
}
|
||
|
||
// 3. 根据编辑来源计算位移量和目标位置 | Calculate displacement and targets based on edit source
|
||
var currentSourceZ = sourceZAxis.ActualPosition;
|
||
var currentDetectorZ = detectorZAxis.ActualPosition;
|
||
|
||
double targetSourceZ;
|
||
double targetDetectorZ;
|
||
|
||
if (sourcePropertyName == nameof(DetectorZPosition))
|
||
{
|
||
// 用户编辑的是探测器Z:以探测器Z为基准计算位移 | User edited DetectorZ: calculate delta from DetectorZ
|
||
var delta = targetValue - currentDetectorZ;
|
||
targetDetectorZ = targetValue;
|
||
targetSourceZ = currentSourceZ + delta;
|
||
}
|
||
else
|
||
{
|
||
// 用户编辑的是射线源Z(默认):以射线源Z为基准计算位移 | User edited SourceZ (default): calculate delta from SourceZ
|
||
var delta = targetValue - currentSourceZ;
|
||
targetSourceZ = targetValue;
|
||
targetDetectorZ = currentDetectorZ + delta;
|
||
}
|
||
|
||
// 4. 边界检查(使用配置中的 Min/Max)| Boundary check (use Min/Max from config)
|
||
if (_config.LinearAxes.TryGetValue(AxisId.SourceZ, out var sourceConfig) &&
|
||
_config.LinearAxes.TryGetValue(AxisId.DetectorZ, out var detectorConfig))
|
||
{
|
||
var errors = new List<string>();
|
||
|
||
if (targetSourceZ < sourceConfig.Min || targetSourceZ > sourceConfig.Max)
|
||
{
|
||
errors.Add($"射线源Z轴目标位置 {targetSourceZ} 超出范围 [{sourceConfig.Min}, {sourceConfig.Max}] | " +
|
||
$"Source Z-axis target {targetSourceZ} out of range [{sourceConfig.Min}, {sourceConfig.Max}]");
|
||
}
|
||
|
||
if (targetDetectorZ < detectorConfig.Min || targetDetectorZ > detectorConfig.Max)
|
||
{
|
||
errors.Add($"探测器Z轴目标位置 {targetDetectorZ} 超出范围 [{detectorConfig.Min}, {detectorConfig.Max}] | " +
|
||
$"Detector Z-axis target {targetDetectorZ} out of range [{detectorConfig.Min}, {detectorConfig.Max}]");
|
||
}
|
||
|
||
if (errors.Count > 0)
|
||
{
|
||
var allErrors = string.Join("; ", errors);
|
||
_logger.Warn("联动移动边界检查失败:{Errors} | Linkage move boundary check failed: {Errors}", allErrors);
|
||
return MotionResult.Fail(allErrors);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
_logger.Error(null, "未找到射线源Z或探测器Z轴配置 | Source Z or Detector Z axis config not found");
|
||
return MotionResult.Fail("轴配置缺失 | Axis config missing");
|
||
}
|
||
|
||
// 5. 所有检查通过,同时发送两个轴的移动命令 | All checks passed, send move commands to both axes simultaneously
|
||
var sourceResult = sourceZAxis.MoveToTarget(targetSourceZ, _config.DefaultVelocity);
|
||
var detectorResult = detectorZAxis.MoveToTarget(targetDetectorZ, _config.DefaultVelocity);
|
||
|
||
// 6. 返回结果(任一失败都记录日志)| Return result (log if any fails)
|
||
if (!sourceResult.Success)
|
||
{
|
||
_logger.Warn("射线源Z轴移动失败:{Reason} | Source Z-axis move failed: {Reason}", sourceResult.ErrorMessage);
|
||
return sourceResult;
|
||
}
|
||
|
||
if (!detectorResult.Success)
|
||
{
|
||
_logger.Warn("探测器Z轴移动失败:{Reason} | Detector Z-axis move failed: {Reason}", detectorResult.ErrorMessage);
|
||
return detectorResult;
|
||
}
|
||
|
||
_logger.Info("射线源与探测器Z轴联动移动成功 | Source-Detector Z-axis linkage move successful");
|
||
return sourceResult;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据属性名称获取当前绑定值 | Get current bound value by property name
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据属性名称设置绑定值 | Set bound value by property name
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 恢复输入框为轴的当前实际值 | Restore input box to axis actual value
|
||
/// </summary>
|
||
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
|
||
|
||
/// <summary>
|
||
/// 确认探测器安全高度限定值 | Confirm detector safety height limit value
|
||
/// Enter 键触发,保存当前值 | Triggered by Enter key, saves current value
|
||
/// </summary>
|
||
public void ConfirmSafetyHeight()
|
||
{
|
||
_logger.Info("探测器安全高度限定值已保存:{Value} | Detector safety height limit saved: {Value}", SafetyHeight);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确认校准自动计算值 | Confirm calibration auto-calculated value
|
||
/// Enter 键触发,保存当前值 | Triggered by Enter key, saves current value
|
||
/// </summary>
|
||
public void ConfirmCalibrationValue()
|
||
{
|
||
_logger.Info("校准自动计算值已保存:{Value} | Calibration auto-calculated value saved: {Value}", CalibrationValue);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 内部数据类 | Internal Data Classes
|
||
|
||
/// <summary>
|
||
/// 保存的轴位置数据 | Saved axis position data
|
||
/// </summary>
|
||
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
|
||
}
|
||
}
|