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
{
///
/// 运动控制调试 ViewModel(Jog 点动)| Motion Control Debug ViewModel (Jog)
/// 提供 Jog 命令、速度滑块和可选轴可见性 | Provides Jog commands, speed slider and optional axis visibility
///
public class MotionDebugWindowViewModel : BindableBase
{
private readonly IMotionControlService _motionControlService;
private readonly MotionControlConfig _config;
private readonly IPlcService _plcService;
private readonly IEventAggregator _eventAggregator;
private readonly IMotionSystem _motionSystem;
/// PLC 是否已连接 | Whether PLC is connected
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(s => SafeRun(() => ExecuteJogPlus(s)));
JogMinusCommand = new DelegateCommand(s => SafeRun(() => ExecuteJogMinus(s)));
JogStopCommand = new DelegateCommand(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(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();
}
};
}
///
/// 安全执行:PLC 未连接时弹窗提示(JogStop 除外),异常时静默记录
/// Safe execution: show message when PLC not connected (except JogStop), silently log on exception
///
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;
/// 速度百分比(1-150%),实际速度 = DefaultVelocity * 百分比 / 100 | Speed percentage (1-150%), actual = DefaultVelocity * pct / 100
public double SpeedPercent
{
get => _speedPercent;
set { SetProperty(ref _speedPercent, value); RaisePropertyChanged(nameof(ActualSpeed)); }
}
/// 实际速度(只读显示)| Actual speed (read-only display)
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 JogPlusCommand { get; }
public DelegateCommand JogMinusCommand { get; }
public DelegateCommand JogStopCommand { get; }
/// 单轴归零命令 | Single axis home command
public DelegateCommand HomeAxisCommand { get; }
/// 轴复位命令 | Axis reset command
public DelegateCommand AxisResetCommand { get; }
/// 停止门命令 | Stop door command
public DelegateCommand StopDoorCommand { get; }
/// 开门命令(调试模式,不检查联锁)| Open door command (debug, no interlock)
public DelegateCommand OpenDoorCommand { get; }
/// 关门命令 | Close door command
public DelegateCommand CloseDoorCommand { get; }
/// 几何反算命令 | Geometry inverse calculation command
public DelegateCommand ApplyGeometryCommand { get; }
#region 几何反算属性 | Geometry Calculation Properties
private double _targetFOD;
/// 目标 FOD | Target FOD
public double TargetFOD { get => _targetFOD; set => SetProperty(ref _targetFOD, value); }
private double _targetFDD;
/// 目标 FDD | Target FDD
public double TargetFDD { get => _targetFDD; set => SetProperty(ref _targetFDD, value); }
///
/// 执行几何反算,弹出确认窗口,确认后通过事件通知 MotionControlViewModel 填入目标值
/// Execute geometry inverse, show confirmation, publish event to fill targets
///
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()
.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);
///
/// 单轴归零 | Single axis home
///
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();
}
///
/// 轴复位:通过 IAxisReset 发送复位命令 | Axis reset: send reset command via IAxisReset
///
private void ExecuteAxisReset()
{
_motionSystem.AxisReset.Reset();
}
#endregion
}
}