将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。

This commit is contained in:
QI Mingxuan
2026-04-16 17:31:13 +08:00
parent 6ec4c3ddaa
commit 2bd6e566c3
581 changed files with 74600 additions and 222 deletions
@@ -0,0 +1,419 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
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.MotionControl.Views;
using XP.Hardware.Plc.Abstractions;
namespace XP.Hardware.MotionControl.ViewModels
{
/// <summary>
/// 运动控制操作面板 ViewModel | Motion Control Operation Panel ViewModel
/// 提供轴状态、几何参数、安全门状态的可绑定属性和操作命令(不含 Jog 调试)
/// Provides axis status, geometry, safety door properties and commands (excluding Jog debug)
/// </summary>
public class MotionControlViewModel : BindableBase
{
private readonly IMotionControlService _motionControlService;
private readonly IMotionSystem _motionSystem;
private readonly IEventAggregator _eventAggregator;
private readonly MotionControlConfig _config;
private readonly IPlcService _plcService;
private Window _debugWindow;
#region | Constructor
public MotionControlViewModel(
IMotionControlService motionControlService,
IMotionSystem motionSystem,
IEventAggregator eventAggregator,
MotionControlConfig config,
IPlcService plcService)
{
_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));
// 监听 PLC 连接状态变化 | Listen for PLC connection status changes
_plcService.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(IPlcService.IsConnected))
{
RaisePropertyChanged(nameof(IsPlcConnected));
RaiseCommandCanExecuteChanged();
}
};
// 初始化可选轴可见性 | Initialize optional axis visibility
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;
// 初始化命令(通过 SafeRun 包裹,防止 PLC 异常崩溃)| Initialize commands (wrapped with SafeRun to prevent PLC exception crash)
MoveCommand = new DelegateCommand(() => SafeRun(ExecuteMove), () => IsPlcConnected);
StopCommand = new DelegateCommand(() => SafeRun(() => _motionControlService.StopAll()), () => IsPlcConnected);
HomeCommand = new DelegateCommand(() => SafeRun(ExecuteHome), () => IsPlcConnected);
OpenDoorCommand = new DelegateCommand(() => SafeRun(() => _motionControlService.OpenDoor()), () => IsPlcConnected);
CloseDoorCommand = new DelegateCommand(() => SafeRun(() => _motionControlService.CloseDoor()), () => IsPlcConnected);
ApplyGeometryCommand = new DelegateCommand(() => SafeRun(ExecuteApplyGeometry), () => IsPlcConnected);
CopyActualToTargetCommand = new DelegateCommand(ExecuteCopyActualToTarget);
OpenDebugWindowCommand = new DelegateCommand(ExecuteOpenDebugWindow);
// 订阅事件 | Subscribe to events
_eventAggregator.GetEvent<GeometryUpdatedEvent>().Subscribe(OnGeometryUpdated, ThreadOption.UIThread);
_eventAggregator.GetEvent<AxisStatusChangedEvent>().Subscribe(OnAxisStatusChanged, ThreadOption.UIThread);
_eventAggregator.GetEvent<DoorStatusChangedEvent>().Subscribe(OnDoorStatusChanged, ThreadOption.UIThread);
_eventAggregator.GetEvent<DoorInterlockChangedEvent>().Subscribe(OnDoorInterlockChanged, ThreadOption.UIThread);
// 订阅几何反算填入请求事件 | Subscribe to geometry apply request event
_eventAggregator.GetEvent<GeometryApplyRequestEvent>().Subscribe(OnGeometryApplyRequest, ThreadOption.UIThread);
// 初始化时主动刷新一次轴位置和门状态 | Refresh axis positions and door status on initialization
try
{
SourceZActual = _motionSystem.GetLinearAxis(AxisId.SourceZ).ActualPosition;
DetectorZActual = _motionSystem.GetLinearAxis(AxisId.DetectorZ).ActualPosition;
StageXActual = _motionSystem.GetLinearAxis(AxisId.StageX).ActualPosition;
StageYActual = _motionSystem.GetLinearAxis(AxisId.StageY).ActualPosition;
DetSwingActual = _motionSystem.GetRotaryAxis(RotaryAxisId.DetectorSwing).ActualAngle;
StageRotActual = _motionSystem.GetRotaryAxis(RotaryAxisId.StageRotation).ActualAngle;
FixtureRotActual = _motionSystem.GetRotaryAxis(RotaryAxisId.FixtureRotation).ActualAngle;
DoorStatus = _motionSystem.SafetyDoor.Status;
IsInterlocked = _motionSystem.SafetyDoor.IsInterlocked;
}
catch { }
}
#endregion
#region 线 | Linear Axis Properties
private double _sourceZActual;
public double SourceZActual { get => _sourceZActual; set => SetProperty(ref _sourceZActual, value); }
private double _sourceZTarget;
public double SourceZTarget { get => _sourceZTarget; set => SetProperty(ref _sourceZTarget, value); }
private double _detectorZActual;
public double DetectorZActual { get => _detectorZActual; set => SetProperty(ref _detectorZActual, value); }
private double _detectorZTarget;
public double DetectorZTarget { get => _detectorZTarget; set => SetProperty(ref _detectorZTarget, value); }
private double _stageXActual;
public double StageXActual { get => _stageXActual; set => SetProperty(ref _stageXActual, value); }
private double _stageXTarget;
public double StageXTarget { get => _stageXTarget; set => SetProperty(ref _stageXTarget, value); }
private double _stageYActual;
public double StageYActual { get => _stageYActual; set => SetProperty(ref _stageYActual, value); }
private double _stageYTarget;
public double StageYTarget { get => _stageYTarget; set => SetProperty(ref _stageYTarget, value); }
#endregion
#region | Rotary Axis Properties
private double _detSwingActual;
public double DetSwingActual { get => _detSwingActual; set => SetProperty(ref _detSwingActual, value); }
private double _detSwingTarget;
public double DetSwingTarget { get => _detSwingTarget; set => SetProperty(ref _detSwingTarget, value); }
private double _stageRotActual;
public double StageRotActual { get => _stageRotActual; set => SetProperty(ref _stageRotActual, value); }
private double _stageRotTarget;
public double StageRotTarget { get => _stageRotTarget; set => SetProperty(ref _stageRotTarget, value); }
private double _fixtureRotActual;
public double FixtureRotActual { get => _fixtureRotActual; set => SetProperty(ref _fixtureRotActual, value); }
private double _fixtureRotTarget;
public double FixtureRotTarget { get => _fixtureRotTarget; set => SetProperty(ref _fixtureRotTarget, value); }
#endregion
#region | Geometry Properties
private double _fod;
public double FOD { get => _fod; private set => SetProperty(ref _fod, value); }
private double _fdd;
public double FDD { get => _fdd; private set => SetProperty(ref _fdd, value); }
private double _magnification;
public double Magnification { get => _magnification; private set => SetProperty(ref _magnification, value); }
private double _targetFOD;
public double TargetFOD { get => _targetFOD; set => SetProperty(ref _targetFOD, value); }
private double _targetFDD;
public double TargetFDD { get => _targetFDD; set => SetProperty(ref _targetFDD, value); }
private double _targetMagnification;
public double TargetMagnification { get => _targetMagnification; set => SetProperty(ref _targetMagnification, value); }
#endregion
#region | Safety Door and Status Properties
private DoorStatus _doorStatus;
public DoorStatus DoorStatus { get => _doorStatus; private set => SetProperty(ref _doorStatus, value); }
private bool _isInterlocked;
public bool IsInterlocked
{
get => _isInterlocked;
private set
{
SetProperty(ref _isInterlocked, value);
RaisePropertyChanged(nameof(IsNotInterlocked));
RaisePropertyChanged(nameof(InterlockStatusText));
}
}
/// <summary>联锁未激活(用于开门按钮 IsEnabled| Not interlocked (for open door button IsEnabled)</summary>
public bool IsNotInterlocked => !_isInterlocked;
/// <summary>联锁状态文本(用于 UI 显示)| Interlock status text for UI display</summary>
public string InterlockStatusText => _isInterlocked
? XP.Common.Localization.LocalizationHelper.Get("MC_Interlocked")
: XP.Common.Localization.LocalizationHelper.Get("MC_NotInterlocked");
private bool _isMoving;
public bool IsMoving { get => _isMoving; private set => SetProperty(ref _isMoving, value); }
/// <summary>PLC 是否已连接(从 PlcService 读取)| Whether PLC is connected (from PlcService)</summary>
public bool IsPlcConnected => _plcService.IsConnected;
#endregion
#region | Optional Axis Visibility
private Visibility _stageRotationVisibility;
public Visibility StageRotationVisibility { get => _stageRotationVisibility; private set => SetProperty(ref _stageRotationVisibility, value); }
private Visibility _fixtureRotationVisibility;
public Visibility FixtureRotationVisibility { get => _fixtureRotationVisibility; private set => SetProperty(ref _fixtureRotationVisibility, value); }
#endregion
#region | Commands
public DelegateCommand MoveCommand { get; }
public DelegateCommand StopCommand { get; }
public DelegateCommand HomeCommand { get; }
public DelegateCommand OpenDoorCommand { get; }
public DelegateCommand CloseDoorCommand { get; }
public DelegateCommand ApplyGeometryCommand { get; }
/// <summary>一键将实际值填入目标值 | Copy actual values to target values</summary>
public DelegateCommand CopyActualToTargetCommand { get; }
/// <summary>打开调试窗口命令 | Open debug window command</summary>
public DelegateCommand OpenDebugWindowCommand { get; }
#endregion
#region | Command Execution
/// <summary>多轴联动移动(含二次确认)| Multi-axis coordinated move (with confirmation)</summary>
private void ExecuteMove()
{
var result = MessageBox.Show(
XP.Common.Localization.LocalizationHelper.Get("MC_MoveConfirm"),
XP.Common.Localization.LocalizationHelper.Get("MC_Move"),
MessageBoxButton.OKCancel, MessageBoxImage.Question);
if (result != MessageBoxResult.OK) return;
var targets = new Dictionary<AxisId, double>
{
{ AxisId.SourceZ, SourceZTarget },
{ AxisId.DetectorZ, DetectorZTarget },
{ AxisId.StageX, StageXTarget },
{ AxisId.StageY, StageYTarget }
};
_motionControlService.MoveAllToTarget(targets);
_motionControlService.MoveRotaryToTarget(RotaryAxisId.DetectorSwing, DetSwingTarget);
if (StageRotationVisibility == Visibility.Visible)
_motionControlService.MoveRotaryToTarget(RotaryAxisId.StageRotation, StageRotTarget);
if (FixtureRotationVisibility == Visibility.Visible)
_motionControlService.MoveRotaryToTarget(RotaryAxisId.FixtureRotation, FixtureRotTarget);
}
/// <summary>所有轴回零(含二次确认)| Home all axes (with confirmation)</summary>
private void ExecuteHome()
{
var result = MessageBox.Show(
XP.Common.Localization.LocalizationHelper.Get("MC_HomeConfirm"),
XP.Common.Localization.LocalizationHelper.Get("MC_Home"),
MessageBoxButton.OKCancel, MessageBoxImage.Question);
if (result != MessageBoxResult.OK) return;
_motionControlService.HomeAll();
}
/// <summary>
/// 几何反算:计算 SourceZ/DetectorZ 目标值并填入,其他轴用实际值填入
/// Geometry inverse: calculate SourceZ/DetectorZ targets and fill in, other axes use actual values
/// </summary>
private void ExecuteApplyGeometry()
{
var (sourceZTarget, detectorZTarget, error) = _motionControlService.CalculateGeometryTargets(TargetFOD, TargetFDD);
if (error != null)
{
MessageBox.Show(error, XP.Common.Localization.LocalizationHelper.Get("MC_Geometry_Title"),
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 计算得到的轴填入目标值 | Fill calculated axis targets
SourceZTarget = sourceZTarget;
DetectorZTarget = detectorZTarget;
// 不需要移动的轴用实际值填入 | Non-calculated axes use actual values
StageXTarget = StageXActual;
StageYTarget = StageYActual;
DetSwingTarget = DetSwingActual;
StageRotTarget = StageRotActual;
FixtureRotTarget = FixtureRotActual;
}
/// <summary>
/// 一键将所有轴实际值填入目标值 | Copy all actual values to target values
/// </summary>
private void ExecuteCopyActualToTarget()
{
SourceZTarget = SourceZActual;
DetectorZTarget = DetectorZActual;
StageXTarget = StageXActual;
StageYTarget = StageYActual;
DetSwingTarget = DetSwingActual;
StageRotTarget = StageRotActual;
FixtureRotTarget = FixtureRotActual;
}
/// <summary>打开调试窗口 | Open debug window</summary>
private void ExecuteOpenDebugWindow()
{
if (_debugWindow != null && _debugWindow.IsLoaded) { _debugWindow.Activate(); return; }
_debugWindow = new MotionDebugWindow { Owner = Application.Current.MainWindow, ShowInTaskbar = true };
_debugWindow.Closed += (s, e) => _debugWindow = null;
_debugWindow.Show();
}
#endregion
#region | Event Callbacks
private void OnGeometryUpdated(GeometryData data)
{
FOD = data.FOD; FDD = data.FDD; Magnification = data.Magnification;
// 每次轮询都刷新所有轴的实际位置 | Refresh all axis actual positions on every poll
try
{
SourceZActual = _motionSystem.GetLinearAxis(AxisId.SourceZ).ActualPosition;
DetectorZActual = _motionSystem.GetLinearAxis(AxisId.DetectorZ).ActualPosition;
StageXActual = _motionSystem.GetLinearAxis(AxisId.StageX).ActualPosition;
StageYActual = _motionSystem.GetLinearAxis(AxisId.StageY).ActualPosition;
DetSwingActual = _motionSystem.GetRotaryAxis(RotaryAxisId.DetectorSwing).ActualAngle;
StageRotActual = _motionSystem.GetRotaryAxis(RotaryAxisId.StageRotation).ActualAngle;
FixtureRotActual = _motionSystem.GetRotaryAxis(RotaryAxisId.FixtureRotation).ActualAngle;
DoorStatus = _motionSystem.SafetyDoor.Status;
}
catch { /* 轴未初始化时忽略 | Ignore when axes not initialized */ }
}
private void OnAxisStatusChanged(AxisStatusChangedData data)
{
UpdateLinearAxisActual(data.AxisId);
UpdateRotaryAxesActual();
UpdateDoorStatus();
IsMoving = _motionSystem.LinearAxes.Values.Any(a => a.Status == AxisStatus.Moving || a.Status == AxisStatus.Homing)
|| _motionSystem.RotaryAxes.Values.Any(a => a.Status == AxisStatus.Moving || a.Status == AxisStatus.Homing);
}
private void OnDoorStatusChanged(DoorStatus status)
{
DoorStatus = status;
IsInterlocked = _motionSystem.SafetyDoor.IsInterlocked;
}
private void OnDoorInterlockChanged(bool isInterlocked)
{
IsInterlocked = isInterlocked;
}
/// <summary>
/// 收到几何反算填入请求,将计算结果填入目标值 | Receive geometry apply request, fill target values
/// </summary>
private void OnGeometryApplyRequest(GeometryApplyRequestData data)
{
SourceZTarget = data.SourceZTarget;
DetectorZTarget = data.DetectorZTarget;
StageXTarget = data.StageXActual;
StageYTarget = data.StageYActual;
DetSwingTarget = data.DetSwingActual;
StageRotTarget = data.StageRotActual;
FixtureRotTarget = data.FixtureRotActual;
}
#endregion
#region | Status Update Helpers
private void UpdateLinearAxisActual(AxisId axisId)
{
switch (axisId)
{
case AxisId.SourceZ: SourceZActual = _motionSystem.GetLinearAxis(AxisId.SourceZ).ActualPosition; break;
case AxisId.DetectorZ: DetectorZActual = _motionSystem.GetLinearAxis(AxisId.DetectorZ).ActualPosition; break;
case AxisId.StageX: StageXActual = _motionSystem.GetLinearAxis(AxisId.StageX).ActualPosition; break;
case AxisId.StageY: StageYActual = _motionSystem.GetLinearAxis(AxisId.StageY).ActualPosition; break;
}
}
private void UpdateRotaryAxesActual()
{
DetSwingActual = _motionSystem.GetRotaryAxis(RotaryAxisId.DetectorSwing).ActualAngle;
StageRotActual = _motionSystem.GetRotaryAxis(RotaryAxisId.StageRotation).ActualAngle;
FixtureRotActual = _motionSystem.GetRotaryAxis(RotaryAxisId.FixtureRotation).ActualAngle;
}
private void UpdateDoorStatus()
{
DoorStatus = _motionSystem.SafetyDoor.Status;
IsInterlocked = _motionSystem.SafetyDoor.IsInterlocked;
}
#endregion
#region | Exception Protection
/// <summary>
/// 安全执行:捕获 PLC 异常避免 UI 崩溃 | Safe execution: catches PLC exceptions to prevent UI crash
/// </summary>
private void SafeRun(Action action)
{
try { action(); }
catch (Exception ex)
{
try { System.Windows.MessageBox.Show(ex.Message, "MotionControl", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning); }
catch { /* 防止 MessageBox 本身异常 | Prevent MessageBox itself from throwing */ }
}
}
/// <summary>
/// 刷新所有命令的 CanExecute 状态 | Refresh CanExecute state of all commands
/// </summary>
private void RaiseCommandCanExecuteChanged()
{
MoveCommand.RaiseCanExecuteChanged();
StopCommand.RaiseCanExecuteChanged();
HomeCommand.RaiseCanExecuteChanged();
OpenDoorCommand.RaiseCanExecuteChanged();
CloseDoorCommand.RaiseCanExecuteChanged();
ApplyGeometryCommand.RaiseCanExecuteChanged();
CopyActualToTargetCommand.RaiseCanExecuteChanged();
}
#endregion
}
}
@@ -0,0 +1,266 @@
using System;
using System.Windows;
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using XP.Common.Localization;
using XP.Hardware.MotionControl.Abstractions;
using XP.Hardware.MotionControl.Abstractions.Enums;
using XP.Hardware.MotionControl.Abstractions.Events;
using XP.Hardware.MotionControl.Config;
using XP.Hardware.MotionControl.Services;
using XP.Hardware.Plc.Abstractions;
namespace XP.Hardware.MotionControl.ViewModels
{
/// <summary>
/// 运动控制调试 ViewModelJog 点动)| Motion Control Debug ViewModel (Jog)
/// 提供 Jog 命令、速度滑块和可选轴可见性 | Provides Jog commands, speed slider and optional axis visibility
/// </summary>
public class MotionDebugWindowViewModel : BindableBase
{
private readonly IMotionControlService _motionControlService;
private readonly MotionControlConfig _config;
private readonly IPlcService _plcService;
private readonly IEventAggregator _eventAggregator;
private readonly IMotionSystem _motionSystem;
/// <summary>PLC 是否已连接 | Whether PLC is connected</summary>
public bool IsPlcConnected => _plcService.IsConnected;
public MotionDebugWindowViewModel(
IMotionControlService motionControlService,
IMotionSystem motionSystem,
MotionControlConfig config,
IPlcService plcService,
IEventAggregator eventAggregator)
{
_motionControlService = motionControlService ?? throw new ArgumentNullException(nameof(motionControlService));
_motionSystem = motionSystem ?? throw new ArgumentNullException(nameof(motionSystem));
_config = config ?? throw new ArgumentNullException(nameof(config));
_plcService = plcService ?? throw new ArgumentNullException(nameof(plcService));
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
var cfg = _config;
// 初始化可选轴可见性 | Initialize optional axis visibility
StageRotationVisibility = cfg.RotaryAxes.ContainsKey(RotaryAxisId.StageRotation)
&& cfg.RotaryAxes[RotaryAxisId.StageRotation].Enabled
? Visibility.Visible : Visibility.Collapsed;
FixtureRotationVisibility = cfg.RotaryAxes.ContainsKey(RotaryAxisId.FixtureRotation)
&& cfg.RotaryAxes[RotaryAxisId.FixtureRotation].Enabled
? Visibility.Visible : Visibility.Collapsed;
// 初始化命令 | Initialize commands
// Jog 命令通过 SafeRun 内部检查 PLC 连接(Interaction.Triggers 不支持 CanExecute 禁用按钮外观)
JogPlusCommand = new DelegateCommand<string>(s => SafeRun(() => ExecuteJogPlus(s)));
JogMinusCommand = new DelegateCommand<string>(s => SafeRun(() => ExecuteJogMinus(s)));
JogStopCommand = new DelegateCommand<string>(s => SafeRun(() => ExecuteJogStop(s), silent: true));
// 门按钮通过 CanExecute 禁用 | Door buttons disabled via CanExecute
StopDoorCommand = new DelegateCommand(() => SafeRun(() => _motionControlService.StopDoor()), () => IsPlcConnected);
OpenDoorCommand = new DelegateCommand(() => SafeRun(() => _motionControlService.OpenDoor()), () => IsPlcConnected);
CloseDoorCommand = new DelegateCommand(() => SafeRun(() => _motionControlService.CloseDoor()), () => IsPlcConnected);
// 几何反算命令 | Geometry inverse calculation command
ApplyGeometryCommand = new DelegateCommand(() => SafeRun(ExecuteApplyGeometry), () => IsPlcConnected);
// 单轴归零命令 | Single axis home command
HomeAxisCommand = new DelegateCommand<string>(s => SafeRun(() => ExecuteHomeAxis(s)));
// 轴复位命令 | Axis reset command
AxisResetCommand = new DelegateCommand(() => SafeRun(ExecuteAxisReset), () => IsPlcConnected);
// PLC 连接状态变化时刷新按钮 | Refresh buttons on PLC connection change
_plcService.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(IPlcService.IsConnected))
{
RaisePropertyChanged(nameof(IsPlcConnected));
StopDoorCommand.RaiseCanExecuteChanged();
OpenDoorCommand.RaiseCanExecuteChanged();
CloseDoorCommand.RaiseCanExecuteChanged();
ApplyGeometryCommand.RaiseCanExecuteChanged();
AxisResetCommand.RaiseCanExecuteChanged();
}
};
}
/// <summary>
/// 安全执行:PLC 未连接时弹窗提示(JogStop 除外),异常时静默记录
/// Safe execution: show message when PLC not connected (except JogStop), silently log on exception
/// </summary>
private bool _plcNotConnectedShown = false;
private void SafeRun(Action action, bool silent = false)
{
if (!_plcService.IsConnected)
{
if (!silent && !_plcNotConnectedShown)
{
_plcNotConnectedShown = true;
var msg = XP.Common.Localization.LocalizationHelper.Get("MC_PlcNotConnected");
System.Windows.MessageBox.Show(
string.IsNullOrEmpty(msg) ? "PLC not connected" : msg,
XP.Common.Localization.LocalizationHelper.Get("MC_DebugWindow_Title") ?? "Debug",
System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning);
_plcNotConnectedShown = false;
}
return;
}
try { action(); }
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[MotionDebug] {ex.Message}");
}
}
#region | Speed Percentage
private double _speedPercent = 100;
/// <summary>速度百分比(1-150%),实际速度 = DefaultVelocity * 百分比 / 100 | Speed percentage (1-150%), actual = DefaultVelocity * pct / 100</summary>
public double SpeedPercent
{
get => _speedPercent;
set { SetProperty(ref _speedPercent, value); RaisePropertyChanged(nameof(ActualSpeed)); }
}
/// <summary>实际速度(只读显示)| Actual speed (read-only display)</summary>
public double ActualSpeed => _config.DefaultVelocity * _speedPercent / 100.0;
#endregion
#region | Optional Axis Visibility
private Visibility _stageRotationVisibility;
public Visibility StageRotationVisibility
{
get => _stageRotationVisibility;
private set => SetProperty(ref _stageRotationVisibility, value);
}
private Visibility _fixtureRotationVisibility;
public Visibility FixtureRotationVisibility
{
get => _fixtureRotationVisibility;
private set => SetProperty(ref _fixtureRotationVisibility, value);
}
#endregion
#region Jog | Jog Commands
public DelegateCommand<string> JogPlusCommand { get; }
public DelegateCommand<string> JogMinusCommand { get; }
public DelegateCommand<string> JogStopCommand { get; }
/// <summary>单轴归零命令 | Single axis home command</summary>
public DelegateCommand<string> HomeAxisCommand { get; }
/// <summary>轴复位命令 | Axis reset command</summary>
public DelegateCommand AxisResetCommand { get; }
/// <summary>停止门命令 | Stop door command</summary>
public DelegateCommand StopDoorCommand { get; }
/// <summary>开门命令(调试模式,不检查联锁)| Open door command (debug, no interlock)</summary>
public DelegateCommand OpenDoorCommand { get; }
/// <summary>关门命令 | Close door command</summary>
public DelegateCommand CloseDoorCommand { get; }
/// <summary>几何反算命令 | Geometry inverse calculation command</summary>
public DelegateCommand ApplyGeometryCommand { get; }
#region | Geometry Calculation Properties
private double _targetFOD;
/// <summary>目标 FOD | Target FOD</summary>
public double TargetFOD { get => _targetFOD; set => SetProperty(ref _targetFOD, value); }
private double _targetFDD;
/// <summary>目标 FDD | Target FDD</summary>
public double TargetFDD { get => _targetFDD; set => SetProperty(ref _targetFDD, value); }
/// <summary>
/// 执行几何反算,弹出确认窗口,确认后通过事件通知 MotionControlViewModel 填入目标值
/// Execute geometry inverse, show confirmation, publish event to fill targets
/// </summary>
private void ExecuteApplyGeometry()
{
var (sourceZTarget, detectorZTarget, error) = _motionControlService.CalculateGeometryTargets(TargetFOD, TargetFDD);
if (error != null)
{
MessageBox.Show(
LocalizationHelper.Get("MC_GeometryCalcFailed", error),
LocalizationHelper.Get("MC_Geometry_Title"),
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 获取其他轴的实际值 | Get actual values of other axes
var stageX = _motionSystem.GetLinearAxis(AxisId.StageX).ActualPosition;
var stageY = _motionSystem.GetLinearAxis(AxisId.StageY).ActualPosition;
var detSwing = _motionSystem.GetRotaryAxis(RotaryAxisId.DetectorSwing).ActualAngle;
var stageRot = _motionSystem.GetRotaryAxis(RotaryAxisId.StageRotation).ActualAngle;
var fixtureRot = _motionSystem.GetRotaryAxis(RotaryAxisId.FixtureRotation).ActualAngle;
// 弹出确认窗口 | Show confirmation dialog
var confirmMessage = LocalizationHelper.Get("MC_GeometryFillConfirm",
TargetFOD, TargetFDD, detSwing, sourceZTarget, detectorZTarget);
var result = MessageBox.Show(confirmMessage,
LocalizationHelper.Get("MC_Geometry_Title"),
MessageBoxButton.OKCancel, MessageBoxImage.Question);
if (result != MessageBoxResult.OK) return;
// 发布事件,通知 MotionControlViewModel 填入目标值 | Publish event to fill target values
_eventAggregator.GetEvent<GeometryApplyRequestEvent>()
.Publish(new GeometryApplyRequestData(
sourceZTarget, detectorZTarget,
stageX, stageY, detSwing, stageRot, fixtureRot));
}
#endregion
private void ExecuteJogPlus(string axisName)
{
if (TryParseLinearAxis(axisName, out var la))
_motionControlService.JogStart(la, true);
else if (TryParseRotaryAxis(axisName, out var ra))
_motionControlService.JogRotaryStart(ra, true);
}
private void ExecuteJogMinus(string axisName)
{
if (TryParseLinearAxis(axisName, out var la))
_motionControlService.JogStart(la, false);
else if (TryParseRotaryAxis(axisName, out var ra))
_motionControlService.JogRotaryStart(ra, false);
}
private void ExecuteJogStop(string axisName)
{
if (TryParseLinearAxis(axisName, out var la))
_motionControlService.JogStop(la);
else if (TryParseRotaryAxis(axisName, out var ra))
_motionControlService.JogRotaryStop(ra);
}
private static bool TryParseLinearAxis(string name, out AxisId id) =>
Enum.TryParse(name, out id) && Enum.IsDefined(typeof(AxisId), id);
private static bool TryParseRotaryAxis(string name, out RotaryAxisId id) =>
Enum.TryParse(name, out id) && Enum.IsDefined(typeof(RotaryAxisId), id);
/// <summary>
/// 单轴归零 | Single axis home
/// </summary>
private void ExecuteHomeAxis(string axisName)
{
if (TryParseLinearAxis(axisName, out var la))
_motionSystem.GetLinearAxis(la).Home();
else if (TryParseRotaryAxis(axisName, out var ra))
_motionSystem.GetRotaryAxis(ra).Home();
}
/// <summary>
/// 轴复位:通过 IAxisReset 发送复位命令 | Axis reset: send reset command via IAxisReset
/// </summary>
private void ExecuteAxisReset()
{
_motionSystem.AxisReset.Reset();
}
#endregion
}
}