将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。
This commit is contained in:
@@ -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>
|
||||
/// 运动控制调试 ViewModel(Jog 点动)| 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user