447 lines
16 KiB
C#
447 lines
16 KiB
C#
|
|
using MainShell.Common;
|
|||
|
|
using MainShell.Hardware;
|
|||
|
|
using MainShell.DeviceMaintance.Model;
|
|||
|
|
using MainShell.Log;
|
|||
|
|
using MainShell.Motion;
|
|||
|
|
using MaxwellFramework.Core.Interfaces;
|
|||
|
|
using Stylet;
|
|||
|
|
using StyletIoC;
|
|||
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Threading;
|
|||
|
|
using System.Threading.Tasks;
|
|||
|
|
using MwFramework.Device;
|
|||
|
|
|
|||
|
|
namespace MainShell.DeviceMaintance.ViewModel
|
|||
|
|
{
|
|||
|
|
public class LaserCompensationViewModel : DeviceMaintanceBaseViewModel, IPage
|
|||
|
|
{
|
|||
|
|
private const string ProjectPageName = "激光补偿";
|
|||
|
|
private const int MinimumMoveTimeoutMilliseconds = 15000;
|
|||
|
|
private const double MoveTimeoutScaleFactor = 3d;
|
|||
|
|
private const int MoveTimeoutExtraMilliseconds = 2000;
|
|||
|
|
|
|||
|
|
private readonly SafeAxisMotion _safeAxisMotion;
|
|||
|
|
private readonly AxisSpeedManager _axisSpeedManager;
|
|||
|
|
private readonly HardwareManager _hardwareManager;
|
|||
|
|
private CancellationTokenSource _cancellationTokenSource;
|
|||
|
|
private bool _uiEnable = true;
|
|||
|
|
private bool _isRunning;
|
|||
|
|
private string _statusMessage;
|
|||
|
|
private int _currentLoopIndex;
|
|||
|
|
private int _currentPointIndex;
|
|||
|
|
private double _currentTargetPosition;
|
|||
|
|
private string _selectedAxisName;
|
|||
|
|
|
|||
|
|
public LaserCompensationViewModel(SafeAxisMotion safeAxisMotion, AxisSpeedManager axisSpeedManager, HardwareManager hardwareManager)
|
|||
|
|
{
|
|||
|
|
_safeAxisMotion = safeAxisMotion;
|
|||
|
|
_axisSpeedManager = axisSpeedManager;
|
|||
|
|
_hardwareManager = hardwareManager;
|
|||
|
|
Setting = new LaserCompensationSetting();
|
|||
|
|
Setting.LoadFromFile();
|
|||
|
|
AxisNames = new System.Collections.ObjectModel.ObservableCollection<string>(BuildAxisNames());
|
|||
|
|
if (!string.IsNullOrWhiteSpace(Setting.AxisName) && AxisNames.Contains(Setting.AxisName))
|
|||
|
|
{
|
|||
|
|
SelectedAxisName = Setting.AxisName;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
SelectedAxisName = AxisNames.FirstOrDefault();
|
|||
|
|
}
|
|||
|
|
StatusMessage = "待机";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public string Name => "LaserCompensationMaint";
|
|||
|
|
|
|||
|
|
public LaserCompensationSetting Setting { get; }
|
|||
|
|
|
|||
|
|
public System.Collections.ObjectModel.ObservableCollection<string> AxisNames { get; }
|
|||
|
|
|
|||
|
|
public string SelectedAxisName
|
|||
|
|
{
|
|||
|
|
get { return _selectedAxisName; }
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (SetAndNotify(ref _selectedAxisName, value))
|
|||
|
|
{
|
|||
|
|
Setting.AxisName = value;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool UiEnable
|
|||
|
|
{
|
|||
|
|
get { return _uiEnable; }
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (SetAndNotify(ref _uiEnable, value))
|
|||
|
|
{
|
|||
|
|
OnUiStateChanged(value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool IsRunning
|
|||
|
|
{
|
|||
|
|
get { return _isRunning; }
|
|||
|
|
set { SetAndNotify(ref _isRunning, value); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public string StatusMessage
|
|||
|
|
{
|
|||
|
|
get { return _statusMessage; }
|
|||
|
|
set { SetAndNotify(ref _statusMessage, value); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int CurrentLoopIndex
|
|||
|
|
{
|
|||
|
|
get { return _currentLoopIndex; }
|
|||
|
|
set { SetAndNotify(ref _currentLoopIndex, value); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int CurrentPointIndex
|
|||
|
|
{
|
|||
|
|
get { return _currentPointIndex; }
|
|||
|
|
set { SetAndNotify(ref _currentPointIndex, value); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public double CurrentTargetPosition
|
|||
|
|
{
|
|||
|
|
get { return _currentTargetPosition; }
|
|||
|
|
set { SetAndNotify(ref _currentTargetPosition, value); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public async Task StartAsync()
|
|||
|
|
{
|
|||
|
|
if (IsRunning)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
string validationMessage = ValidateSetting();
|
|||
|
|
if (!string.IsNullOrWhiteSpace(validationMessage))
|
|||
|
|
{
|
|||
|
|
StatusMessage = validationMessage;
|
|||
|
|
LocalizedMessageBox.Show(MessageKey.ParamInvalid, MessageKey.TitleError, System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_cancellationTokenSource?.Dispose();
|
|||
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|||
|
|
|
|||
|
|
UiEnable = false;
|
|||
|
|
IsRunning = true;
|
|||
|
|
CurrentLoopIndex = 0;
|
|||
|
|
CurrentPointIndex = 0;
|
|||
|
|
CurrentTargetPosition = 0d;
|
|||
|
|
Setting.AxisName = SelectedAxisName;
|
|||
|
|
StatusMessage = "激光补偿运行中";
|
|||
|
|
|
|||
|
|
IoC.Get<IProjectManager>().EnablePageAndDisableOther(ProjectPageName);
|
|||
|
|
BuildStartLogMessage().LogInfo();
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
CancellationToken cancellationToken = _cancellationTokenSource.Token;
|
|||
|
|
await ExecuteCompensationAsync(cancellationToken).ConfigureAwait(true);
|
|||
|
|
|
|||
|
|
StatusMessage = "激光补偿执行完成";
|
|||
|
|
BuildFinishLogMessage("完成").LogInfo();
|
|||
|
|
}
|
|||
|
|
catch (OperationCanceledException)
|
|||
|
|
{
|
|||
|
|
StatusMessage = "激光补偿已停止";
|
|||
|
|
BuildFinishLogMessage("已停止").LogInfo();
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
StatusMessage = $"激光补偿异常:{ex.Message}";
|
|||
|
|
LogManager.LogSysError(ex, true);
|
|||
|
|
}
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
IsRunning = false;
|
|||
|
|
UiEnable = true;
|
|||
|
|
_cancellationTokenSource?.Dispose();
|
|||
|
|
_cancellationTokenSource = null;
|
|||
|
|
IoC.Get<IProjectManager>().SwitchState();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Stop()
|
|||
|
|
{
|
|||
|
|
if (!IsRunning)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
StatusMessage = "正在停止激光补偿";
|
|||
|
|
"激光补偿收到停止请求".LogInfo();
|
|||
|
|
_cancellationTokenSource?.Cancel();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Save()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
Setting.SaveToFile();
|
|||
|
|
StatusMessage = "参数已保存";
|
|||
|
|
"激光补偿参数已保存".LogInfo();
|
|||
|
|
LocalizedMessageBox.Show(MessageKey.CommonSaveSucceeded, MessageKey.TitleInfo, System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Information);
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
StatusMessage = $"参数保存失败:{ex.Message}";
|
|||
|
|
LogManager.LogSysError(ex, true);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async Task ExecuteCompensationAsync(CancellationToken cancellationToken)
|
|||
|
|
{
|
|||
|
|
IReadOnlyList<double> trajectory = BuildTrajectory();
|
|||
|
|
|
|||
|
|
ApplyAxisSpeedIfPossible();
|
|||
|
|
|
|||
|
|
for (int workIndex = 0; workIndex < Setting.WorkCount; workIndex++)
|
|||
|
|
{
|
|||
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|||
|
|
CurrentLoopIndex = workIndex + 1;
|
|||
|
|
$"激光补偿开始第{CurrentLoopIndex}次循环,总点数={trajectory.Count}".LogInfo();
|
|||
|
|
|
|||
|
|
for (int pointIndex = 0; pointIndex < trajectory.Count; pointIndex++)
|
|||
|
|
{
|
|||
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|||
|
|
|
|||
|
|
double targetPosition = trajectory[pointIndex];
|
|||
|
|
CurrentPointIndex = pointIndex + 1;
|
|||
|
|
CurrentTargetPosition = targetPosition;
|
|||
|
|
StatusMessage = $"循环 {CurrentLoopIndex}/{Setting.WorkCount},点位 {CurrentPointIndex}/{trajectory.Count}";
|
|||
|
|
|
|||
|
|
int timeoutMilliseconds = CalculateMoveTimeoutMilliseconds(targetPosition, trajectory, pointIndex);
|
|||
|
|
$"激光补偿移动开始:轴={Setting.AxisName},循环={CurrentLoopIndex},点位={CurrentPointIndex},目标={targetPosition:F4},超时={timeoutMilliseconds}ms".LogInfo();
|
|||
|
|
|
|||
|
|
MotionResult motionResult = await _safeAxisMotion.MoveAbsAsync(
|
|||
|
|
Setting.AxisName,
|
|||
|
|
targetPosition,
|
|||
|
|
timeoutMilliseconds,
|
|||
|
|
cancellationToken,
|
|||
|
|
alarmId: null).ConfigureAwait(false);
|
|||
|
|
|
|||
|
|
motionResult.EnsureSuccess();
|
|||
|
|
|
|||
|
|
$"激光补偿移动完成:轴={Setting.AxisName},循环={CurrentLoopIndex},点位={CurrentPointIndex},目标={targetPosition:F4},到位={motionResult.EndPosition:F4}".LogInfo();
|
|||
|
|
|
|||
|
|
if (Setting.DwellTime > 0d)
|
|||
|
|
{
|
|||
|
|
int dwellMilliseconds = (int)Math.Ceiling(Setting.DwellTime * 1000d);
|
|||
|
|
$"激光补偿延时采样:{dwellMilliseconds}ms".LogInfo();
|
|||
|
|
await Task.Delay(dwellMilliseconds, cancellationToken).ConfigureAwait(false);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void ApplyAxisSpeedIfPossible()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (!string.IsNullOrWhiteSpace(Setting.AxisName) && Setting.Speed > 0d)
|
|||
|
|
{
|
|||
|
|
_axisSpeedManager.SetAxisSpeed(Setting.AxisName, Setting.Speed);
|
|||
|
|
$"激光补偿轴速度已设置:轴={Setting.AxisName},Speed={Setting.Speed:F4}".LogInfo();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
$"激光补偿轴速度设置失败:{ex}".LogSysError();
|
|||
|
|
throw;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private IReadOnlyList<double> BuildTrajectory()
|
|||
|
|
{
|
|||
|
|
List<double> measurePoints = Enumerable.Range(0, Setting.StepsCount)
|
|||
|
|
.Select(index => Setting.StartPos + index * Setting.Steps)
|
|||
|
|
.ToList();
|
|||
|
|
|
|||
|
|
double readyPosition = Setting.StartPos - Setting.JumpPos;
|
|||
|
|
double overshootPosition = measurePoints[measurePoints.Count - 1] + Setting.JumpPos;
|
|||
|
|
|
|||
|
|
List<double> trajectory = new List<double>(measurePoints.Count * 2 + 3)
|
|||
|
|
{
|
|||
|
|
readyPosition
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
trajectory.AddRange(measurePoints);
|
|||
|
|
trajectory.Add(overshootPosition);
|
|||
|
|
trajectory.AddRange(measurePoints.AsEnumerable().Reverse());
|
|||
|
|
trajectory.Add(readyPosition);
|
|||
|
|
|
|||
|
|
return trajectory;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private int CalculateMoveTimeoutMilliseconds(double targetPosition, IReadOnlyList<double> trajectory, int pointIndex)
|
|||
|
|
{
|
|||
|
|
IAxis axis = _hardwareManager.GetAxisByName(Setting.AxisName);
|
|||
|
|
double startPosition = GetMoveStartPosition(axis, trajectory, pointIndex, targetPosition);
|
|||
|
|
double distance = Math.Abs(targetPosition - startPosition);
|
|||
|
|
double speed = ResolvePositiveMotionParameter(axis != null ? axis.Param.Velocity : 0d, Math.Abs(Setting.Speed));
|
|||
|
|
if (speed <= 0d)
|
|||
|
|
{
|
|||
|
|
return MinimumMoveTimeoutMilliseconds;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
double acceleration = ResolvePositiveMotionParameter(axis != null ? axis.Param.Accelerate : 0d, 0d);
|
|||
|
|
double deceleration = ResolvePositiveMotionParameter(axis != null ? axis.Param.Decelerate : 0d, acceleration);
|
|||
|
|
double expectedMilliseconds = CalculateExpectedMoveMilliseconds(distance, speed, acceleration, deceleration);
|
|||
|
|
|
|||
|
|
return Math.Max(MinimumMoveTimeoutMilliseconds, (int)Math.Ceiling(expectedMilliseconds * MoveTimeoutScaleFactor + MoveTimeoutExtraMilliseconds));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static double GetMoveStartPosition(IAxis axis, IReadOnlyList<double> trajectory, int pointIndex, double targetPosition)
|
|||
|
|
{
|
|||
|
|
if (pointIndex > 0)
|
|||
|
|
{
|
|||
|
|
return trajectory[pointIndex - 1];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (axis != null)
|
|||
|
|
{
|
|||
|
|
return axis.State != null ? axis.State.ActualPos : axis.GetPositionImmediate();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return targetPosition;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static double ResolvePositiveMotionParameter(double primaryValue, double fallbackValue)
|
|||
|
|
{
|
|||
|
|
if (!double.IsNaN(primaryValue) && !double.IsInfinity(primaryValue) && primaryValue > 0d)
|
|||
|
|
{
|
|||
|
|
return primaryValue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!double.IsNaN(fallbackValue) && !double.IsInfinity(fallbackValue) && fallbackValue > 0d)
|
|||
|
|
{
|
|||
|
|
return fallbackValue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return 0d;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static double CalculateExpectedMoveMilliseconds(double distance, double speed, double acceleration, double deceleration)
|
|||
|
|
{
|
|||
|
|
if (distance <= 0d)
|
|||
|
|
{
|
|||
|
|
return 0d;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (speed <= 0d)
|
|||
|
|
{
|
|||
|
|
return 0d;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (acceleration <= 0d || deceleration <= 0d)
|
|||
|
|
{
|
|||
|
|
return distance / speed * 1000d;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
double accelerateDistance = speed * speed / (2d * acceleration);
|
|||
|
|
double decelerateDistance = speed * speed / (2d * deceleration);
|
|||
|
|
double criticalDistance = accelerateDistance + decelerateDistance;
|
|||
|
|
double expectedSeconds;
|
|||
|
|
|
|||
|
|
if (distance >= criticalDistance)
|
|||
|
|
{
|
|||
|
|
expectedSeconds = speed / acceleration + speed / deceleration + (distance - criticalDistance) / speed;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
double peakSpeed = Math.Sqrt(2d * distance * acceleration * deceleration / (acceleration + deceleration));
|
|||
|
|
expectedSeconds = peakSpeed / acceleration + peakSpeed / deceleration;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return expectedSeconds * 1000d;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private string ValidateSetting()
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrWhiteSpace(Setting.AxisName))
|
|||
|
|
{
|
|||
|
|
return "轴名称不能为空";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!AxisNames.Contains(Setting.AxisName))
|
|||
|
|
{
|
|||
|
|
return "请选择有效的轴名称";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (Setting.StepsCount <= 0)
|
|||
|
|
{
|
|||
|
|
return "步距次数必须大于0";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (Setting.WorkCount <= 0)
|
|||
|
|
{
|
|||
|
|
return "循环次数必须大于0";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (Math.Abs(Setting.Steps) <= 0d)
|
|||
|
|
{
|
|||
|
|
return "步距必须非0";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (Setting.JumpPos < 0d)
|
|||
|
|
{
|
|||
|
|
return "跃程不能小于0";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (Setting.Speed <= 0d)
|
|||
|
|
{
|
|||
|
|
return "速度必须大于0";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (Setting.DwellTime < 0d)
|
|||
|
|
{
|
|||
|
|
return "延时时间不能小于0";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private string BuildStartLogMessage()
|
|||
|
|
{
|
|||
|
|
return $"激光补偿启动:轴={Setting.AxisName},StartPos={Setting.StartPos:F4},Steps={Setting.Steps:F4},StepsCount={Setting.StepsCount},JumpPos={Setting.JumpPos:F4},WorkCount={Setting.WorkCount},Speed={Setting.Speed:F4},DwellTime={Setting.DwellTime:F3}s";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private string BuildFinishLogMessage(string state)
|
|||
|
|
{
|
|||
|
|
return $"激光补偿结束:状态={state},轴={Setting.AxisName},已执行循环={CurrentLoopIndex},最后点位={CurrentPointIndex},最后目标={CurrentTargetPosition:F4}";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private IEnumerable<string> BuildAxisNames()
|
|||
|
|
{
|
|||
|
|
return new[]
|
|||
|
|
{
|
|||
|
|
GetAxisName(_hardwareManager?.Axis_PHS_X1, MainShell.Hardware.AxisName.Axis_PHS_X1),
|
|||
|
|
GetAxisName(_hardwareManager?.Axis_PHS_X2, MainShell.Hardware.AxisName.Axis_PHS_X2),
|
|||
|
|
GetAxisName(_hardwareManager?.Axis_PHS_Y1, MainShell.Hardware.AxisName.Axis_PHS_Y1),
|
|||
|
|
GetAxisName(_hardwareManager?.Axis_WS_X3, MainShell.Hardware.AxisName.Axis_WS_X3),
|
|||
|
|
GetAxisName(_hardwareManager?.Axis_Stage_Y3, MainShell.Hardware.AxisName.Axis_Stage_Y3),
|
|||
|
|
GetAxisName(_hardwareManager?.Axis_WS_R, MainShell.Hardware.AxisName.Axis_WS_R),
|
|||
|
|
}.Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToArray();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static string GetAxisName(IAxis axis, string fallbackName)
|
|||
|
|
{
|
|||
|
|
if (axis == null || string.IsNullOrWhiteSpace(axis.Name))
|
|||
|
|
{
|
|||
|
|
return fallbackName;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return axis.Name;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|