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
{
///
/// 运动控制操作面板 ViewModel | Motion Control Operation Panel ViewModel
/// 提供轴状态、几何参数、安全门状态的可绑定属性和操作命令(不含 Jog 调试)
/// Provides axis status, geometry, safety door properties and commands (excluding Jog debug)
///
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().Subscribe(OnGeometryUpdated, ThreadOption.UIThread);
_eventAggregator.GetEvent().Subscribe(OnAxisStatusChanged, ThreadOption.UIThread);
_eventAggregator.GetEvent().Subscribe(OnDoorStatusChanged, ThreadOption.UIThread);
_eventAggregator.GetEvent().Subscribe(OnDoorInterlockChanged, ThreadOption.UIThread);
// 订阅几何反算填入请求事件 | Subscribe to geometry apply request event
_eventAggregator.GetEvent().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));
}
}
/// 联锁未激活(用于开门按钮 IsEnabled)| Not interlocked (for open door button IsEnabled)
public bool IsNotInterlocked => !_isInterlocked;
/// 联锁状态文本(用于 UI 显示)| Interlock status text for UI display
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); }
/// PLC 是否已连接(从 PlcService 读取)| Whether PLC is connected (from PlcService)
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; }
/// 一键将实际值填入目标值 | Copy actual values to target values
public DelegateCommand CopyActualToTargetCommand { get; }
/// 打开调试窗口命令 | Open debug window command
public DelegateCommand OpenDebugWindowCommand { get; }
#endregion
#region 命令执行 | Command Execution
/// 多轴联动移动(含二次确认)| Multi-axis coordinated move (with confirmation)
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.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);
}
/// 所有轴回零(含二次确认)| Home all axes (with confirmation)
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();
}
///
/// 几何反算:计算 SourceZ/DetectorZ 目标值并填入,其他轴用实际值填入
/// Geometry inverse: calculate SourceZ/DetectorZ targets and fill in, other axes use actual values
///
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;
}
///
/// 一键将所有轴实际值填入目标值 | Copy all actual values to target values
///
private void ExecuteCopyActualToTarget()
{
SourceZTarget = SourceZActual;
DetectorZTarget = DetectorZActual;
StageXTarget = StageXActual;
StageYTarget = StageYActual;
DetSwingTarget = DetSwingActual;
StageRotTarget = StageRotActual;
FixtureRotTarget = FixtureRotActual;
}
/// 打开调试窗口 | Open debug window
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;
}
///
/// 收到几何反算填入请求,将计算结果填入目标值 | Receive geometry apply request, fill target values
///
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
///
/// 安全执行:捕获 PLC 异常避免 UI 崩溃 | Safe execution: catches PLC exceptions to prevent UI crash
///
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 */ }
}
}
///
/// 刷新所有命令的 CanExecute 状态 | Refresh CanExecute state of all commands
///
private void RaiseCommandCanExecuteChanged()
{
MoveCommand.RaiseCanExecuteChanged();
StopCommand.RaiseCanExecuteChanged();
HomeCommand.RaiseCanExecuteChanged();
OpenDoorCommand.RaiseCanExecuteChanged();
CloseDoorCommand.RaiseCanExecuteChanged();
ApplyGeometryCommand.RaiseCanExecuteChanged();
CopyActualToTargetCommand.RaiseCanExecuteChanged();
}
#endregion
}
}