Files
XplorePlane/XP.Hardware.MotionControl/ViewModels/AxisControlViewModel.cs
T
2026-04-22 20:48:00 +08:00

897 lines
42 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
/// <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);
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<GeometryUpdatedEvent>().Subscribe(OnGeometryUpdated, ThreadOption.UIThread);
_eventAggregator.GetEvent<AxisStatusChangedEvent>().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;
/// <summary>双轴摇杆 X 轴输出 | Dual-axis joystick X output</summary>
public double DualJoystickOutputX
{
get => _dualJoystickOutputX;
set
{
if (SetProperty(ref _dualJoystickOutputX, value))
HandleDualJoystickOutput();
}
}
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 _isPlcConnected;
/// <summary>PLC 连接状态 | PLC connection status</summary>
public bool IsPlcConnected { get => _isPlcConnected; set => SetProperty(ref _isPlcConnected, value); }
private string _errorMessage;
/// <summary>错误提示信息 | Error message</summary>
public string ErrorMessage { get => _errorMessage; set => SetProperty(ref _errorMessage, value); }
#endregion
#region | Commands
/// <summary>切换使能开关命令 | Toggle enable switch command</summary>
public DelegateCommand ToggleEnableCommand { get; }
/// <summary>保存当前轴位置命令 | Save current axis positions command</summary>
public DelegateCommand SavePositionsCommand { get; }
/// <summary>恢复保存的轴位置命令 | Restore saved axis positions command</summary>
public DelegateCommand RestorePositionsCommand { get; }
#endregion
#region | Event Callbacks
/// <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();
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 _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
/// <summary>
/// 切换摇杆使能状态,并发送使能状态到 PLC | Toggle joystick enable state and send to PLC
/// </summary>
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)
}
/// <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();
_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");
}
#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 JogY→StageY Jog | Left button: X→StageX Jog, Y→StageY Jog
UpdateLinearJog(AxisId.StageX, DualJoystickOutputX);
UpdateLinearJog(AxisId.StageY, DualJoystickOutputY);
break;
case MouseButtonType.Right:
// 右键:X→DetectorSwing JogY→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);
break;
case MouseButtonType.Right:
// 右键:Y→DetectorZ Jog | Right button: Y→DetectorZ Jog
UpdateLinearJog(AxisId.DetectorZ, 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(() =>
{
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;
}
/// <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(() =>
{
var 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>
/// 根据属性名称获取当前绑定值 | 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
}
}