From f3d3289d51a91411d3c0c1ff5829743f9cd2d708 Mon Sep 17 00:00:00 2001 From: "GukSang.Jin" Date: Sat, 9 May 2026 18:32:35 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- COFTester/MainWindow.xaml | 2 + COFTester/Services/RunExportService.cs | 18 +- COFTester/ViewModels/MainViewModel.cs | 409 ++++++++++++++++++++----- 3 files changed, 336 insertions(+), 93 deletions(-) diff --git a/COFTester/MainWindow.xaml b/COFTester/MainWindow.xaml index bdecdb1..bc562a2 100644 --- a/COFTester/MainWindow.xaml +++ b/COFTester/MainWindow.xaml @@ -797,6 +797,8 @@ LegendPosition="Hidden" ZoomMode="None" TooltipPosition="Hidden" + AnimationsSpeed="0:0:0" + EasingFunction="{x:Null}" DrawMarginFrame="{x:Null}" /> diff --git a/COFTester/Services/RunExportService.cs b/COFTester/Services/RunExportService.cs index 79699a8..a3554bf 100644 --- a/COFTester/Services/RunExportService.cs +++ b/COFTester/Services/RunExportService.cs @@ -115,8 +115,7 @@ public sealed class RunExportService { return sample.SampleIndex > 0 && IsFinite(sample.DisplacementMm) - && IsFinite(sample.ForceN) - && IsFinite(sample.SpeedMmPerMin); + && IsFinite(sample.ForceN); } private static bool IsFinite(double value) @@ -305,7 +304,10 @@ public sealed class RunExportService worksheet.Cell(rowIndex, 16).Style.DateFormat.Format = "yyyy-mm-dd hh:mm:ss.000"; worksheet.Cell(rowIndex, 17).Value = sample.DisplacementMm; worksheet.Cell(rowIndex, 18).Value = sample.ForceN; - worksheet.Cell(rowIndex, 19).Value = sample.SpeedMmPerMin; + if (IsFinite(sample.SpeedMmPerMin)) + { + worksheet.Cell(rowIndex, 19).Value = sample.SpeedMmPerMin; + } worksheet.Cell(rowIndex, 4).Style.Fill.BackgroundColor = ToXlColor(item.CurveColorHex); worksheet.Cell(rowIndex, 4).Style.Font.FontColor = XLColor.White; @@ -458,15 +460,13 @@ public sealed class RunExportService var charts = new List(); var perRunStartRow = Math.Max(10, runs.Count + 8); const int chartHeight = 11; - const int chartWidth = 9; + const int chartWidth = 19; const int rowGap = 2; for (var index = 0; index < perRunSeries.Count; index++) { - var chartRowGroup = index / 2; - var chartColumnGroup = index % 2; - var fromColumn = chartColumnGroup == 0 ? 0 : 10; - var fromRow = perRunStartRow + chartRowGroup * (chartHeight + rowGap); + var fromColumn = 0; + var fromRow = perRunStartRow + index * (chartHeight + rowGap); var toColumn = fromColumn + chartWidth; var toRow = fromRow + chartHeight; var runSeries = perRunSeries[index]; @@ -492,7 +492,7 @@ public sealed class RunExportService private static int CalculateChartSheetLastRow(IReadOnlyList runs) { var chartCount = runs.Count(IsChartableRun); - return Math.Max(24, runs.Count + 22 + ((chartCount + 1) / 2) * 13); + return Math.Max(24, runs.Count + 22 + chartCount * 13); } private static void ApplyDefaultWorksheetStyle(IXLWorksheet worksheet) diff --git a/COFTester/ViewModels/MainViewModel.cs b/COFTester/ViewModels/MainViewModel.cs index 11647bb..12b6ae9 100644 --- a/COFTester/ViewModels/MainViewModel.cs +++ b/COFTester/ViewModels/MainViewModel.cs @@ -130,8 +130,11 @@ public sealed class MainViewModel : ObservableObject, IDisposable private double _runningPeakForceN; private double _kineticForceSum; private int _kineticSampleCount; + private double _runStartHorizontalPositionMm = double.NaN; private double _lastRealtimeChartDisplacementMm = double.NaN; private DateTime _lastRealtimeChartRefreshAt = DateTime.MinValue; + private double _lastForceXAxisMaxLimit; + private double _lastForceYAxisMaxLimit; public MainViewModel() { @@ -179,10 +182,6 @@ public sealed class MainViewModel : ObservableObject, IDisposable var seriesStroke = new SolidColorPaint(new SKColor(0, 105, 180), 3); var seriesGeometryFill = new SolidColorPaint(new SKColor(0, 131, 154)); - var axisLabelPaint = new SolidColorPaint(new SKColor(74, 92, 108)); - var axisNamePaint = new SolidColorPaint(new SKColor(33, 49, 61)); - var separatorPaint = new SolidColorPaint(new SKColor(209, 219, 227), 1); - var subsectionPaint = new SolidColorPaint(new SKColor(229, 236, 242), 1); var peakStroke = new SolidColorPaint(new SKColor(198, 107, 88), 1.5f); var averageStroke = new SolidColorPaint(new SKColor(196, 150, 69), 1.5f); var currentPointFill = new SolidColorPaint(new SKColor(0, 105, 180)); @@ -230,39 +229,10 @@ public sealed class MainViewModel : ObservableObject, IDisposable } ]; - ForceXAxes = - [ - new Axis - { - Name = "位移 / mm", - NameTextSize = 12, - TextSize = 11, - MinLimit = 0, - MinStep = 25, - LabelsPaint = axisLabelPaint, - NamePaint = axisNamePaint, - SeparatorsPaint = separatorPaint, - SubseparatorsPaint = subsectionPaint, - Padding = new Padding(8, 0, 0, 0) - } - ]; - - ForceYAxes = - [ - new Axis - { - Name = "力值 / N", - NameTextSize = 12, - TextSize = 11, - MinLimit = 0, - MinStep = 0.1, - LabelsPaint = axisLabelPaint, - NamePaint = axisNamePaint, - SeparatorsPaint = separatorPaint, - SubseparatorsPaint = subsectionPaint, - Padding = new Padding(0, 0, 8, 0) - } - ]; + _lastForceXAxisMaxLimit = 1; + _lastForceYAxisMaxLimit = 0.5; + ForceXAxes = CreateForceXAxes(_lastForceXAxisMaxLimit); + ForceYAxes = CreateForceYAxes(_lastForceYAxisMaxLimit); _kineticBand = new RectangularSection { @@ -841,6 +811,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable InterlockMessage = "试验已停止,可重新装样或调整参数。"; AddWarningEvent("试验被人工停止。"); RaiseStatusProperties(); + RaiseCommandStates(); } private async void Rise() @@ -900,6 +871,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable StateBrush = BrushFromHex("#6C8E78"); InterlockMessage = "设备已复位,可重新开始试验。"; RaiseStatusProperties(); + RaiseCommandStates(); } private void CompleteCalibration() @@ -938,37 +910,15 @@ public sealed class MainViewModel : ObservableObject, IDisposable return; } - _currentRunSamples.Add(new RawSampleRecord + if (!AppendRunningSample(frame, out var isCompleted)) { - RunId = _activeRunId, - SampleIndex = _currentRunSamples.Count + 1, - CapturedAt = DateTime.Now, - DisplacementMm = frame.DisplacementMm, - ForceN = CurrentForceN, - SpeedMmPerMin = frame.SpeedMmPerMin - }); - AddRealtimeChartSample(frame.DisplacementMm, CurrentForceN); - UpdateCurrentPoint(frame.DisplacementMm, CurrentForceN); - UpdatePreviewResult(frame.DisplacementMm, CurrentForceN); - StaticCoefficient1 = frame.StaticCoefficient1; - KineticCoefficient1 = frame.KineticCoefficient1; - StandardDeviation1 = frame.StandardDeviation1; - StaticCoefficient2 = frame.StaticCoefficient2; - KineticCoefficient2 = frame.KineticCoefficient2; - StandardDeviation2 = frame.StandardDeviation2; - var activeReplicateCount = GetActiveReplicateCount(); - CurrentStaticCoefficient = ResolveRepresentativeValue(activeReplicateCount, StaticCoefficient1, StaticCoefficient2); - CurrentKineticCoefficient = ResolveRepresentativeValue(activeReplicateCount, KineticCoefficient1, KineticCoefficient2); - TrialProgressPercent = Math.Min(100, frame.DisplacementMm / Math.Max(Recipe.TravelMm, 1) * 100); - - if (frame.IsCompleted) - { - RefreshRealtimeChartPresentation(force: true); - FinalizeRun(); + RaiseStatusProperties(); + return; } - else + + if (isCompleted) { - RefreshRealtimeChartPresentation(force: false); + FinalizeRun(); } RaiseStatusProperties(); @@ -993,7 +943,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable private void UpdateLiveProcessSnapshot(ProcessFrame frame) { CurrentForceN = Math.Max(frame.ForceN, 0.001); - CurrentDisplacementMm = frame.DisplacementMm; + CurrentDisplacementMm = frame.HorizontalPosition; CurrentSpeedMmPerMin = frame.SpeedMmPerMin; CurrentLiftSpeed = frame.LiftSpeed; CurrentLiftDisplacement = frame.LiftDisplacement; @@ -1063,6 +1013,76 @@ public sealed class MainViewModel : ObservableObject, IDisposable _forceSamples[^1] = sample; } + private bool AppendRunningSample(ProcessFrame frame, out bool isCompleted) + { + isCompleted = false; + if (!TryResolveRunningPosition(frame, out var horizontalPositionMm, out var travelDeltaMm)) + { + AddWarningEvent("实时采样包含无效水平位置,已跳过本次曲线点。"); + return false; + } + + var forceN = CurrentForceN; + if (!IsFinite(forceN)) + { + AddWarningEvent("实时采样包含无效力值,已跳过本次曲线点。"); + return false; + } + + CurrentDisplacementMm = horizontalPositionMm; + var isFirstChartSample = _forceSamples.Count == 0; + _currentRunSamples.Add(new RawSampleRecord + { + RunId = _activeRunId, + SampleIndex = _currentRunSamples.Count + 1, + CapturedAt = DateTime.Now, + DisplacementMm = horizontalPositionMm, + ForceN = forceN, + SpeedMmPerMin = frame.SpeedMmPerMin + }); + + AddRealtimeChartSample(horizontalPositionMm, forceN); + UpdateCurrentPoint(horizontalPositionMm, forceN); + UpdatePreviewResult(travelDeltaMm, forceN); + StaticCoefficient1 = frame.StaticCoefficient1; + KineticCoefficient1 = frame.KineticCoefficient1; + StandardDeviation1 = frame.StandardDeviation1; + StaticCoefficient2 = frame.StaticCoefficient2; + KineticCoefficient2 = frame.KineticCoefficient2; + StandardDeviation2 = frame.StandardDeviation2; + var activeReplicateCount = GetActiveReplicateCount(); + CurrentStaticCoefficient = ResolveRepresentativeValue(activeReplicateCount, StaticCoefficient1, StaticCoefficient2); + CurrentKineticCoefficient = ResolveRepresentativeValue(activeReplicateCount, KineticCoefficient1, KineticCoefficient2); + TrialProgressPercent = Math.Min(100, travelDeltaMm / Math.Max(Recipe.TravelMm, 1) * 100); + isCompleted = travelDeltaMm >= GetActiveTravelMm(); + RefreshRealtimeChartPresentation(force: isFirstChartSample || isCompleted); + if (isFirstChartSample) + { + _exportReportCommand.RaiseCanExecuteChanged(); + } + + return true; + } + + private bool TryResolveRunningPosition(ProcessFrame frame, out double horizontalPositionMm, out double travelDeltaMm) + { + horizontalPositionMm = 0; + travelDeltaMm = 0; + if (!IsFinite(frame.HorizontalPosition)) + { + return false; + } + + horizontalPositionMm = frame.HorizontalPosition; + if (!IsFinite(_runStartHorizontalPositionMm)) + { + _runStartHorizontalPositionMm = horizontalPositionMm; + } + + travelDeltaMm = Math.Abs(horizontalPositionMm - _runStartHorizontalPositionMm); + return IsFinite(horizontalPositionMm) && IsFinite(travelDeltaMm); + } + private void UpdatePreviewResult(double displacementMm, double forceN) { _runningPeakForceN = Math.Max(_runningPeakForceN, forceN); @@ -1116,12 +1136,155 @@ public sealed class MainViewModel : ObservableObject, IDisposable _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); + var visibleForceN = Math.Max(CurrentPeakForceN, CurrentForceN); + var yMax = Math.Max(visibleForceN * 1.15, 0.5); + var xMax = CalculateRealtimeXAxisMaxLimit(); + UpdateAxisLimits(xMax, yMax); + if (UpdateKineticBand(yMax)) + { + OnPropertyChanged(nameof(ForceSections)); + } + } + + private double CalculateRealtimeXAxisMaxLimit() + { + var sampleMax = _forceSamples.Count == 0 + ? 0 + : _forceSamples.Max(sample => sample.X ?? 0); + var currentMax = Math.Max(CurrentDisplacementMm, CurrentHorizontalPosition); + var visibleMax = Math.Max(sampleMax, currentMax); + return Math.Max(visibleMax + 5, 1); + } + + private void UpdateAxisLimits(double xMax, double yMax) + { + var changed = false; + if (ShouldUpdateAxisLimit(_lastForceXAxisMaxLimit, xMax)) + { + _lastForceXAxisMaxLimit = xMax; + ForceXAxes[0].MaxLimit = xMax; + changed = true; + } + + if (ShouldUpdateAxisLimit(_lastForceYAxisMaxLimit, yMax)) + { + _lastForceYAxisMaxLimit = yMax; + ForceYAxes[0].MaxLimit = yMax; + changed = true; + } + + if (changed) + { + OnPropertyChanged(nameof(ForceXAxes)); + OnPropertyChanged(nameof(ForceYAxes)); + } + } + + private void SetAxisLimits(double xMax, double yMax) + { + _lastForceXAxisMaxLimit = xMax; + _lastForceYAxisMaxLimit = yMax; + ForceXAxes[0].MaxLimit = xMax; + ForceYAxes[0].MaxLimit = yMax; OnPropertyChanged(nameof(ForceXAxes)); OnPropertyChanged(nameof(ForceYAxes)); - OnPropertyChanged(nameof(ForceSections)); + } + + private static bool ShouldUpdateAxisLimit(double currentLimit, double nextLimit) + { + if (!IsFinite(currentLimit) || !IsFinite(nextLimit)) + { + return false; + } + + return nextLimit > currentLimit || currentLimit - nextLimit > Math.Max(2, currentLimit * 0.1); + } + + private bool UpdateKineticBand(double yMax) + { + var travelMm = GetActiveTravelMm(); + double xi; + double xj; + if (IsFinite(_runStartHorizontalPositionMm)) + { + var direction = CurrentHorizontalPosition >= _runStartHorizontalPositionMm ? 1 : -1; + var kineticStart = _runStartHorizontalPositionMm + direction * travelMm * 0.35; + var kineticEnd = _runStartHorizontalPositionMm + direction * travelMm; + xi = Math.Min(kineticStart, kineticEnd); + xj = Math.Max(kineticStart, kineticEnd); + } + else + { + xi = travelMm * 0.35; + xj = travelMm; + } + + var changed = HasMeaningfulChange(_kineticBand.Xi ?? double.NaN, xi) + || HasMeaningfulChange(_kineticBand.Xj ?? double.NaN, xj) + || HasMeaningfulChange(_kineticBand.Yj ?? double.NaN, yMax) + || HasMeaningfulChange(_kineticBand.Yi ?? double.NaN, 0); + if (!changed) + { + return false; + } + + _kineticBand.Xi = xi; + _kineticBand.Xj = xj; + _kineticBand.Yi = 0; + _kineticBand.Yj = yMax; + return true; + } + + private static bool HasMeaningfulChange(double current, double next) + { + if (!IsFinite(current) || !IsFinite(next)) + { + return true; + } + + return Math.Abs(current - next) > 0.001; + } + + private static Axis[] CreateForceXAxes(double maxLimit) + { + return + [ + new Axis + { + Name = "位移 / mm", + NameTextSize = 12, + TextSize = 11, + MinLimit = 0, + MaxLimit = Math.Max(maxLimit, 1), + MinStep = 25, + LabelsPaint = new SolidColorPaint(new SKColor(74, 92, 108)), + NamePaint = new SolidColorPaint(new SKColor(33, 49, 61)), + SeparatorsPaint = new SolidColorPaint(new SKColor(209, 219, 227), 1), + SubseparatorsPaint = new SolidColorPaint(new SKColor(229, 236, 242), 1), + Padding = new Padding(8, 0, 0, 0) + } + ]; + } + + private static Axis[] CreateForceYAxes(double maxLimit) + { + return + [ + new Axis + { + Name = "力值 / N", + NameTextSize = 12, + TextSize = 11, + MinLimit = 0, + MaxLimit = Math.Max(maxLimit, 0.5), + MinStep = 0.1, + LabelsPaint = new SolidColorPaint(new SKColor(74, 92, 108)), + NamePaint = new SolidColorPaint(new SKColor(33, 49, 61)), + SeparatorsPaint = new SolidColorPaint(new SKColor(209, 219, 227), 1), + SubseparatorsPaint = new SolidColorPaint(new SKColor(229, 236, 242), 1), + Padding = new Padding(0, 0, 8, 0) + } + ]; } private void UpdateReferenceLine(ObservableCollection target, double y) @@ -1132,7 +1295,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable return; } - var xEnd = Math.Max(GetDisplayRecipeSnapshot().TravelMm, CurrentDisplacementMm); + var xEnd = Math.Max(_lastForceXAxisMaxLimit, CurrentDisplacementMm); if (target.Count == 2) { target[0] = new ObservablePoint(0, y); @@ -1150,6 +1313,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable _runningPeakForceN = 0; _kineticForceSum = 0; _kineticSampleCount = 0; + _runStartHorizontalPositionMm = double.NaN; _lastRealtimeChartDisplacementMm = double.NaN; _lastRealtimeChartRefreshAt = DateTime.MinValue; } @@ -1343,7 +1507,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable private bool CanExportHistoricalReport() { - return RunHistory.Count > 0; + return RunHistory.Count > 0 || HasActiveExportSamples(); } private bool CanClearHistoryStable() @@ -1441,14 +1605,16 @@ public sealed class MainViewModel : ObservableObject, IDisposable TrialProgressPercent = Math.Min(100, CurrentDisplacementMm / Math.Max(data.Recipe.TravelMm, 1) * 100); UpdateReferenceLines(); - ForceXAxes[0].MaxLimit = Math.Max(data.Recipe.TravelMm, CurrentDisplacementMm + 5); - ForceYAxes[0].MaxLimit = Math.Max(CurrentPeakForceN * 1.15, 0.5); - _kineticBand.Xi = data.Recipe.TravelMm * 0.35; - _kineticBand.Xj = data.Recipe.TravelMm; + var historyMaxX = data.Samples.Count == 0 + ? CurrentDisplacementMm + : data.Samples.Max(sample => sample.DisplacementMm); + var yMax = Math.Max(CurrentPeakForceN * 1.15, 0.5); + SetAxisLimits(Math.Max(historyMaxX + 5, 1), yMax); + var historyStartX = data.Samples.Count == 0 ? 0 : data.Samples.Min(sample => sample.DisplacementMm); + _kineticBand.Xi = historyStartX + data.Recipe.TravelMm * 0.35; + _kineticBand.Xj = historyStartX + data.Recipe.TravelMm; _kineticBand.Yi = 0; - _kineticBand.Yj = Math.Max(CurrentPeakForceN * 1.15, 0.5); - OnPropertyChanged(nameof(ForceXAxes)); - OnPropertyChanged(nameof(ForceYAxes)); + _kineticBand.Yj = yMax; OnPropertyChanged(nameof(ForceSections)); RaiseStatusProperties(); OnPropertyChanged(nameof(AnalysisModeSummary)); @@ -1524,15 +1690,12 @@ public sealed class MainViewModel : ObservableObject, IDisposable CurrentKineticCoefficient = 0; TrialProgressPercent = 0; - ForceXAxes[0].MaxLimit = Math.Max(Recipe.TravelMm, 50); - ForceYAxes[0].MaxLimit = 0.5; + SetAxisLimits(1, 0.5); _kineticBand.Xi = Recipe.TravelMm * 0.35; _kineticBand.Xj = Recipe.TravelMm; _kineticBand.Yi = 0; _kineticBand.Yj = 0.5; - OnPropertyChanged(nameof(ForceXAxes)); - OnPropertyChanged(nameof(ForceYAxes)); OnPropertyChanged(nameof(ForceSections)); } @@ -1541,6 +1704,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable _activeRunId = Guid.Empty; _activeRecipeSnapshot = null; _activeRunStartedByPlc = false; + _runStartHorizontalPositionMm = double.NaN; _currentRunSamples.Clear(); } @@ -1743,6 +1907,13 @@ public sealed class MainViewModel : ObservableObject, IDisposable } } + var activeRunData = BuildActiveRunExportData(); + if (activeRunData is not null) + { + historyData.Add(activeRunData); + hasExportableSamples = true; + } + exportData = historyData; if (!hasExportableSamples) { @@ -1781,7 +1952,9 @@ public sealed class MainViewModel : ObservableObject, IDisposable } var workbookPath = _runExportService.ExportHistoricalComparisonReport(exportData, saveFileDialog.FileName); - foreach (var item in exportData.Where(data => data.Samples.Any(RunExportService.IsValidSample))) + foreach (var item in exportData.Where(data => + data.Samples.Any(RunExportService.IsValidSample) + && RunHistory.Any(run => run.RunId == data.Run.RunId))) { _dataRepository.UpdateExportPaths(item.Run.RunId, null, workbookPath); item.Run.ReportExportPath = workbookPath; @@ -1797,6 +1970,74 @@ public sealed class MainViewModel : ObservableObject, IDisposable } } + private bool HasActiveExportSamples() + { + return _activeRunId != Guid.Empty + && _activeRecipeSnapshot is not null + && _currentRunSamples.Any(RunExportService.IsValidSample); + } + + private PersistedRunData? BuildActiveRunExportData() + { + if (!HasActiveExportSamples()) + { + return null; + } + + var activeRecipeSnapshot = _activeRecipeSnapshot; + if (activeRecipeSnapshot is null) + { + return null; + } + + var activeSamples = _currentRunSamples + .Where(RunExportService.IsValidSample) + .Select(sample => new RawSampleRecord + { + RunId = sample.RunId, + SampleIndex = sample.SampleIndex, + CapturedAt = sample.CapturedAt, + DisplacementMm = sample.DisplacementMm, + ForceN = sample.ForceN, + SpeedMmPerMin = sample.SpeedMmPerMin + }) + .ToArray(); + + if (activeSamples.Length == 0) + { + return null; + } + + var activeRun = new RunRecord + { + RunId = _activeRunId, + RunIndex = NextRunIndex, + CompletedAt = DateTime.Now, + BatchNumber = activeRecipeSnapshot.BatchNumber, + TestMode = activeRecipeSnapshot.TestMode, + StaticCoefficient = CurrentStaticCoefficient, + StaticCoefficient1 = StaticCoefficient1, + StaticCoefficient2 = StaticCoefficient2, + KineticCoefficient = CurrentKineticCoefficient, + KineticCoefficient1 = KineticCoefficient1, + KineticCoefficient2 = KineticCoefficient2, + StandardDeviation = ResolveRepresentativeValue(GetActiveReplicateCount(), StandardDeviation1, StandardDeviation2), + StandardDeviation1 = StandardDeviation1, + StandardDeviation2 = StandardDeviation2, + PeakForceN = CurrentPeakForceN, + AverageForceN = CurrentAverageForceN, + Judgement = "采样中", + SampleCount = activeSamples.Length + }; + + return new PersistedRunData + { + Run = activeRun, + Recipe = activeRecipeSnapshot, + Samples = activeSamples + }; + } + private void ClearHistoryData() { var historyCount = RunHistory.Count;