Files
XplorePlane/XP.Hardware.MotionControl/ViewModels/MotionDebugWindowViewModel.cs

267 lines
13 KiB
C#
Raw Permalink 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.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
}
}