更新4/28

This commit is contained in:
GukSang.Jin
2026-04-28 18:39:05 +08:00
parent b8cdf9ac9a
commit 52795a3b34

View File

@@ -51,6 +51,8 @@ public sealed class MainViewModel : ObservableObject, IDisposable
private const ushort RegisterHorizontalDisplacement = 360; private const ushort RegisterHorizontalDisplacement = 360;
private const ushort RegisterLiftSpeed = 310; private const ushort RegisterLiftSpeed = 310;
private const ushort RegisterLiftDisplacement = 320; private const ushort RegisterLiftDisplacement = 320;
private static readonly TimeSpan RealtimeChartRefreshInterval = TimeSpan.FromMilliseconds(250);
private const double RealtimeChartDisplacementStepMm = 0.5;
private readonly DispatcherTimer _timer; private readonly DispatcherTimer _timer;
private readonly DispatcherTimer _deviceReconnectTimer; private readonly DispatcherTimer _deviceReconnectTimer;
@@ -125,6 +127,11 @@ public sealed class MainViewModel : ObservableObject, IDisposable
private ushort? _pendingTableMotionStopCoil; private ushort? _pendingTableMotionStopCoil;
private MachineRuntimeState _machineRuntimeState = MachineRuntimeState.Idle; private MachineRuntimeState _machineRuntimeState = MachineRuntimeState.Idle;
private string _plcCommandSummary = "等待 PLC 控制指令。"; private string _plcCommandSummary = "等待 PLC 控制指令。";
private double _runningPeakForceN;
private double _kineticForceSum;
private int _kineticSampleCount;
private double _lastRealtimeChartDisplacementMm = double.NaN;
private DateTime _lastRealtimeChartRefreshAt = DateTime.MinValue;
public MainViewModel() public MainViewModel()
{ {
@@ -758,6 +765,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
_peakLineSamples.Clear(); _peakLineSamples.Clear();
_averageLineSamples.Clear(); _averageLineSamples.Clear();
_currentPointSample.Clear(); _currentPointSample.Clear();
ResetRealtimeSamplingMetrics();
CurrentForceN = 0; CurrentForceN = 0;
CurrentDisplacementMm = 0; CurrentDisplacementMm = 0;
CurrentPeakForceN = 0; CurrentPeakForceN = 0;
@@ -930,7 +938,6 @@ public sealed class MainViewModel : ObservableObject, IDisposable
return; return;
} }
_forceSamples.Add(new ObservablePoint(frame.DisplacementMm, CurrentForceN));
_currentRunSamples.Add(new RawSampleRecord _currentRunSamples.Add(new RawSampleRecord
{ {
RunId = _activeRunId, RunId = _activeRunId,
@@ -940,8 +947,9 @@ public sealed class MainViewModel : ObservableObject, IDisposable
ForceN = CurrentForceN, ForceN = CurrentForceN,
SpeedMmPerMin = frame.SpeedMmPerMin SpeedMmPerMin = frame.SpeedMmPerMin
}); });
AddRealtimeChartSample(frame.DisplacementMm, CurrentForceN);
UpdateCurrentPoint(frame.DisplacementMm, CurrentForceN); UpdateCurrentPoint(frame.DisplacementMm, CurrentForceN);
UpdatePreviewResult(); UpdatePreviewResult(frame.DisplacementMm, CurrentForceN);
StaticCoefficient1 = frame.StaticCoefficient1; StaticCoefficient1 = frame.StaticCoefficient1;
KineticCoefficient1 = frame.KineticCoefficient1; KineticCoefficient1 = frame.KineticCoefficient1;
StandardDeviation1 = frame.StandardDeviation1; StandardDeviation1 = frame.StandardDeviation1;
@@ -952,19 +960,16 @@ public sealed class MainViewModel : ObservableObject, IDisposable
CurrentStaticCoefficient = ResolveRepresentativeValue(activeReplicateCount, StaticCoefficient1, StaticCoefficient2); CurrentStaticCoefficient = ResolveRepresentativeValue(activeReplicateCount, StaticCoefficient1, StaticCoefficient2);
CurrentKineticCoefficient = ResolveRepresentativeValue(activeReplicateCount, KineticCoefficient1, KineticCoefficient2); CurrentKineticCoefficient = ResolveRepresentativeValue(activeReplicateCount, KineticCoefficient1, KineticCoefficient2);
TrialProgressPercent = Math.Min(100, frame.DisplacementMm / Math.Max(Recipe.TravelMm, 1) * 100); TrialProgressPercent = Math.Min(100, frame.DisplacementMm / Math.Max(Recipe.TravelMm, 1) * 100);
UpdateReferenceLines();
ForceXAxes[0].MaxLimit = Math.Max(Recipe.TravelMm, frame.DisplacementMm + 5);
ForceYAxes[0].MaxLimit = Math.Max(CurrentPeakForceN * 1.15, 0.5);
_kineticBand.Yj = Math.Max(CurrentPeakForceN * 1.15, 0.5);
OnPropertyChanged(nameof(ForceXAxes));
OnPropertyChanged(nameof(ForceYAxes));
OnPropertyChanged(nameof(ForceSections));
if (frame.IsCompleted) if (frame.IsCompleted)
{ {
RefreshRealtimeChartPresentation(force: true);
FinalizeRun(); FinalizeRun();
} }
else
{
RefreshRealtimeChartPresentation(force: false);
}
RaiseStatusProperties(); RaiseStatusProperties();
} }
@@ -1038,15 +1043,43 @@ public sealed class MainViewModel : ObservableObject, IDisposable
RaiseStatusProperties(); RaiseStatusProperties();
} }
private void UpdatePreviewResult() private void AddRealtimeChartSample(double displacementMm, double forceN)
{ {
CurrentPeakForceN = _forceSamples.Count == 0 ? 0 : _forceSamples.Max(sample => sample.Coordinate.SecondaryValue); var sample = new ObservablePoint(displacementMm, forceN);
if (_forceSamples.Count == 0 || !IsFinite(_lastRealtimeChartDisplacementMm))
if (_forceSamples.Count > 5)
{ {
var kineticWindow = _forceSamples.Skip((int)(_forceSamples.Count * 0.35)).ToArray(); _forceSamples.Add(sample);
CurrentAverageForceN = kineticWindow.Average(sample => sample.Coordinate.SecondaryValue); _lastRealtimeChartDisplacementMm = displacementMm;
return;
} }
if (Math.Abs(displacementMm - _lastRealtimeChartDisplacementMm) >= RealtimeChartDisplacementStepMm)
{
_forceSamples.Add(sample);
_lastRealtimeChartDisplacementMm = displacementMm;
return;
}
_forceSamples[^1] = sample;
}
private void UpdatePreviewResult(double displacementMm, double forceN)
{
_runningPeakForceN = Math.Max(_runningPeakForceN, forceN);
CurrentPeakForceN = _runningPeakForceN;
var kineticStartMm = GetActiveTravelMm() * 0.35;
if (displacementMm >= kineticStartMm)
{
_kineticForceSum += forceN;
_kineticSampleCount++;
CurrentAverageForceN = _kineticForceSum / _kineticSampleCount;
}
}
private double GetActiveTravelMm()
{
return _activeRecipeSnapshot?.TravelMm ?? Recipe.TravelMm;
} }
private int GetActiveReplicateCount() private int GetActiveReplicateCount()
@@ -1072,6 +1105,25 @@ public sealed class MainViewModel : ObservableObject, IDisposable
UpdateReferenceLine(_averageLineSamples, CurrentAverageForceN); UpdateReferenceLine(_averageLineSamples, CurrentAverageForceN);
} }
private void RefreshRealtimeChartPresentation(bool force)
{
var now = DateTime.UtcNow;
if (!force && now - _lastRealtimeChartRefreshAt < RealtimeChartRefreshInterval)
{
return;
}
_lastRealtimeChartRefreshAt = now;
UpdateReferenceLines();
ForceXAxes[0].MaxLimit = Math.Max(GetActiveTravelMm(), CurrentDisplacementMm + 5);
ForceYAxes[0].MaxLimit = Math.Max(CurrentPeakForceN * 1.15, 0.5);
_kineticBand.Yj = Math.Max(CurrentPeakForceN * 1.15, 0.5);
OnPropertyChanged(nameof(ForceXAxes));
OnPropertyChanged(nameof(ForceYAxes));
OnPropertyChanged(nameof(ForceSections));
}
private void UpdateReferenceLine(ObservableCollection<ObservablePoint> target, double y) private void UpdateReferenceLine(ObservableCollection<ObservablePoint> target, double y)
{ {
if (y <= 0) if (y <= 0)
@@ -1080,12 +1132,28 @@ public sealed class MainViewModel : ObservableObject, IDisposable
return; return;
} }
var xEnd = Math.Max(Recipe.TravelMm, CurrentDisplacementMm); var xEnd = Math.Max(GetDisplayRecipeSnapshot().TravelMm, CurrentDisplacementMm);
if (target.Count == 2)
{
target[0] = new ObservablePoint(0, y);
target[1] = new ObservablePoint(xEnd, y);
return;
}
target.Clear(); target.Clear();
target.Add(new ObservablePoint(0, y)); target.Add(new ObservablePoint(0, y));
target.Add(new ObservablePoint(xEnd, y)); target.Add(new ObservablePoint(xEnd, y));
} }
private void ResetRealtimeSamplingMetrics()
{
_runningPeakForceN = 0;
_kineticForceSum = 0;
_kineticSampleCount = 0;
_lastRealtimeChartDisplacementMm = double.NaN;
_lastRealtimeChartRefreshAt = DateTime.MinValue;
}
private TestRecipeSnapshot GetDisplayRecipeSnapshot() private TestRecipeSnapshot GetDisplayRecipeSnapshot()
{ {
return _isShowingHistoricalRun && _displayedRecipeSnapshot is not null return _isShowingHistoricalRun && _displayedRecipeSnapshot is not null
@@ -1435,6 +1503,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
_peakLineSamples.Clear(); _peakLineSamples.Clear();
_averageLineSamples.Clear(); _averageLineSamples.Clear();
_currentPointSample.Clear(); _currentPointSample.Clear();
ResetRealtimeSamplingMetrics();
CurrentForceN = 0; CurrentForceN = 0;
CurrentDisplacementMm = 0; CurrentDisplacementMm = 0;