更新
This commit is contained in:
@@ -797,6 +797,8 @@
|
|||||||
LegendPosition="Hidden"
|
LegendPosition="Hidden"
|
||||||
ZoomMode="None"
|
ZoomMode="None"
|
||||||
TooltipPosition="Hidden"
|
TooltipPosition="Hidden"
|
||||||
|
AnimationsSpeed="0:0:0"
|
||||||
|
EasingFunction="{x:Null}"
|
||||||
DrawMarginFrame="{x:Null}" />
|
DrawMarginFrame="{x:Null}" />
|
||||||
</Border>
|
</Border>
|
||||||
</Border>
|
</Border>
|
||||||
|
|||||||
@@ -115,8 +115,7 @@ public sealed class RunExportService
|
|||||||
{
|
{
|
||||||
return sample.SampleIndex > 0
|
return sample.SampleIndex > 0
|
||||||
&& IsFinite(sample.DisplacementMm)
|
&& IsFinite(sample.DisplacementMm)
|
||||||
&& IsFinite(sample.ForceN)
|
&& IsFinite(sample.ForceN);
|
||||||
&& IsFinite(sample.SpeedMmPerMin);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsFinite(double value)
|
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, 16).Style.DateFormat.Format = "yyyy-mm-dd hh:mm:ss.000";
|
||||||
worksheet.Cell(rowIndex, 17).Value = sample.DisplacementMm;
|
worksheet.Cell(rowIndex, 17).Value = sample.DisplacementMm;
|
||||||
worksheet.Cell(rowIndex, 18).Value = sample.ForceN;
|
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.Fill.BackgroundColor = ToXlColor(item.CurveColorHex);
|
||||||
worksheet.Cell(rowIndex, 4).Style.Font.FontColor = XLColor.White;
|
worksheet.Cell(rowIndex, 4).Style.Font.FontColor = XLColor.White;
|
||||||
@@ -458,15 +460,13 @@ public sealed class RunExportService
|
|||||||
var charts = new List<ChartDefinition>();
|
var charts = new List<ChartDefinition>();
|
||||||
var perRunStartRow = Math.Max(10, runs.Count + 8);
|
var perRunStartRow = Math.Max(10, runs.Count + 8);
|
||||||
const int chartHeight = 11;
|
const int chartHeight = 11;
|
||||||
const int chartWidth = 9;
|
const int chartWidth = 19;
|
||||||
const int rowGap = 2;
|
const int rowGap = 2;
|
||||||
|
|
||||||
for (var index = 0; index < perRunSeries.Count; index++)
|
for (var index = 0; index < perRunSeries.Count; index++)
|
||||||
{
|
{
|
||||||
var chartRowGroup = index / 2;
|
var fromColumn = 0;
|
||||||
var chartColumnGroup = index % 2;
|
var fromRow = perRunStartRow + index * (chartHeight + rowGap);
|
||||||
var fromColumn = chartColumnGroup == 0 ? 0 : 10;
|
|
||||||
var fromRow = perRunStartRow + chartRowGroup * (chartHeight + rowGap);
|
|
||||||
var toColumn = fromColumn + chartWidth;
|
var toColumn = fromColumn + chartWidth;
|
||||||
var toRow = fromRow + chartHeight;
|
var toRow = fromRow + chartHeight;
|
||||||
var runSeries = perRunSeries[index];
|
var runSeries = perRunSeries[index];
|
||||||
@@ -492,7 +492,7 @@ public sealed class RunExportService
|
|||||||
private static int CalculateChartSheetLastRow(IReadOnlyList<ExportCurveData> runs)
|
private static int CalculateChartSheetLastRow(IReadOnlyList<ExportCurveData> runs)
|
||||||
{
|
{
|
||||||
var chartCount = runs.Count(IsChartableRun);
|
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)
|
private static void ApplyDefaultWorksheetStyle(IXLWorksheet worksheet)
|
||||||
|
|||||||
@@ -130,8 +130,11 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||||||
private double _runningPeakForceN;
|
private double _runningPeakForceN;
|
||||||
private double _kineticForceSum;
|
private double _kineticForceSum;
|
||||||
private int _kineticSampleCount;
|
private int _kineticSampleCount;
|
||||||
|
private double _runStartHorizontalPositionMm = double.NaN;
|
||||||
private double _lastRealtimeChartDisplacementMm = double.NaN;
|
private double _lastRealtimeChartDisplacementMm = double.NaN;
|
||||||
private DateTime _lastRealtimeChartRefreshAt = DateTime.MinValue;
|
private DateTime _lastRealtimeChartRefreshAt = DateTime.MinValue;
|
||||||
|
private double _lastForceXAxisMaxLimit;
|
||||||
|
private double _lastForceYAxisMaxLimit;
|
||||||
|
|
||||||
public MainViewModel()
|
public MainViewModel()
|
||||||
{
|
{
|
||||||
@@ -179,10 +182,6 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||||||
|
|
||||||
var seriesStroke = new SolidColorPaint(new SKColor(0, 105, 180), 3);
|
var seriesStroke = new SolidColorPaint(new SKColor(0, 105, 180), 3);
|
||||||
var seriesGeometryFill = new SolidColorPaint(new SKColor(0, 131, 154));
|
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 peakStroke = new SolidColorPaint(new SKColor(198, 107, 88), 1.5f);
|
||||||
var averageStroke = new SolidColorPaint(new SKColor(196, 150, 69), 1.5f);
|
var averageStroke = new SolidColorPaint(new SKColor(196, 150, 69), 1.5f);
|
||||||
var currentPointFill = new SolidColorPaint(new SKColor(0, 105, 180));
|
var currentPointFill = new SolidColorPaint(new SKColor(0, 105, 180));
|
||||||
@@ -230,39 +229,10 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
ForceXAxes =
|
_lastForceXAxisMaxLimit = 1;
|
||||||
[
|
_lastForceYAxisMaxLimit = 0.5;
|
||||||
new Axis
|
ForceXAxes = CreateForceXAxes(_lastForceXAxisMaxLimit);
|
||||||
{
|
ForceYAxes = CreateForceYAxes(_lastForceYAxisMaxLimit);
|
||||||
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)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
_kineticBand = new RectangularSection
|
_kineticBand = new RectangularSection
|
||||||
{
|
{
|
||||||
@@ -841,6 +811,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||||||
InterlockMessage = "试验已停止,可重新装样或调整参数。";
|
InterlockMessage = "试验已停止,可重新装样或调整参数。";
|
||||||
AddWarningEvent("试验被人工停止。");
|
AddWarningEvent("试验被人工停止。");
|
||||||
RaiseStatusProperties();
|
RaiseStatusProperties();
|
||||||
|
RaiseCommandStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Rise()
|
private async void Rise()
|
||||||
@@ -900,6 +871,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||||||
StateBrush = BrushFromHex("#6C8E78");
|
StateBrush = BrushFromHex("#6C8E78");
|
||||||
InterlockMessage = "设备已复位,可重新开始试验。";
|
InterlockMessage = "设备已复位,可重新开始试验。";
|
||||||
RaiseStatusProperties();
|
RaiseStatusProperties();
|
||||||
|
RaiseCommandStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CompleteCalibration()
|
private void CompleteCalibration()
|
||||||
@@ -938,37 +910,15 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentRunSamples.Add(new RawSampleRecord
|
if (!AppendRunningSample(frame, out var isCompleted))
|
||||||
{
|
{
|
||||||
RunId = _activeRunId,
|
RaiseStatusProperties();
|
||||||
SampleIndex = _currentRunSamples.Count + 1,
|
return;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (isCompleted)
|
||||||
{
|
{
|
||||||
RefreshRealtimeChartPresentation(force: false);
|
FinalizeRun();
|
||||||
}
|
}
|
||||||
|
|
||||||
RaiseStatusProperties();
|
RaiseStatusProperties();
|
||||||
@@ -993,7 +943,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||||||
private void UpdateLiveProcessSnapshot(ProcessFrame frame)
|
private void UpdateLiveProcessSnapshot(ProcessFrame frame)
|
||||||
{
|
{
|
||||||
CurrentForceN = Math.Max(frame.ForceN, 0.001);
|
CurrentForceN = Math.Max(frame.ForceN, 0.001);
|
||||||
CurrentDisplacementMm = frame.DisplacementMm;
|
CurrentDisplacementMm = frame.HorizontalPosition;
|
||||||
CurrentSpeedMmPerMin = frame.SpeedMmPerMin;
|
CurrentSpeedMmPerMin = frame.SpeedMmPerMin;
|
||||||
CurrentLiftSpeed = frame.LiftSpeed;
|
CurrentLiftSpeed = frame.LiftSpeed;
|
||||||
CurrentLiftDisplacement = frame.LiftDisplacement;
|
CurrentLiftDisplacement = frame.LiftDisplacement;
|
||||||
@@ -1063,6 +1013,76 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||||||
_forceSamples[^1] = sample;
|
_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)
|
private void UpdatePreviewResult(double displacementMm, double forceN)
|
||||||
{
|
{
|
||||||
_runningPeakForceN = Math.Max(_runningPeakForceN, forceN);
|
_runningPeakForceN = Math.Max(_runningPeakForceN, forceN);
|
||||||
@@ -1116,12 +1136,155 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||||||
_lastRealtimeChartRefreshAt = now;
|
_lastRealtimeChartRefreshAt = now;
|
||||||
UpdateReferenceLines();
|
UpdateReferenceLines();
|
||||||
|
|
||||||
ForceXAxes[0].MaxLimit = Math.Max(GetActiveTravelMm(), CurrentDisplacementMm + 5);
|
var visibleForceN = Math.Max(CurrentPeakForceN, CurrentForceN);
|
||||||
ForceYAxes[0].MaxLimit = Math.Max(CurrentPeakForceN * 1.15, 0.5);
|
var yMax = Math.Max(visibleForceN * 1.15, 0.5);
|
||||||
_kineticBand.Yj = Math.Max(CurrentPeakForceN * 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(ForceXAxes));
|
||||||
OnPropertyChanged(nameof(ForceYAxes));
|
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<ObservablePoint> target, double y)
|
private void UpdateReferenceLine(ObservableCollection<ObservablePoint> target, double y)
|
||||||
@@ -1132,7 +1295,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var xEnd = Math.Max(GetDisplayRecipeSnapshot().TravelMm, CurrentDisplacementMm);
|
var xEnd = Math.Max(_lastForceXAxisMaxLimit, CurrentDisplacementMm);
|
||||||
if (target.Count == 2)
|
if (target.Count == 2)
|
||||||
{
|
{
|
||||||
target[0] = new ObservablePoint(0, y);
|
target[0] = new ObservablePoint(0, y);
|
||||||
@@ -1150,6 +1313,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||||||
_runningPeakForceN = 0;
|
_runningPeakForceN = 0;
|
||||||
_kineticForceSum = 0;
|
_kineticForceSum = 0;
|
||||||
_kineticSampleCount = 0;
|
_kineticSampleCount = 0;
|
||||||
|
_runStartHorizontalPositionMm = double.NaN;
|
||||||
_lastRealtimeChartDisplacementMm = double.NaN;
|
_lastRealtimeChartDisplacementMm = double.NaN;
|
||||||
_lastRealtimeChartRefreshAt = DateTime.MinValue;
|
_lastRealtimeChartRefreshAt = DateTime.MinValue;
|
||||||
}
|
}
|
||||||
@@ -1343,7 +1507,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||||||
|
|
||||||
private bool CanExportHistoricalReport()
|
private bool CanExportHistoricalReport()
|
||||||
{
|
{
|
||||||
return RunHistory.Count > 0;
|
return RunHistory.Count > 0 || HasActiveExportSamples();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanClearHistoryStable()
|
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);
|
TrialProgressPercent = Math.Min(100, CurrentDisplacementMm / Math.Max(data.Recipe.TravelMm, 1) * 100);
|
||||||
|
|
||||||
UpdateReferenceLines();
|
UpdateReferenceLines();
|
||||||
ForceXAxes[0].MaxLimit = Math.Max(data.Recipe.TravelMm, CurrentDisplacementMm + 5);
|
var historyMaxX = data.Samples.Count == 0
|
||||||
ForceYAxes[0].MaxLimit = Math.Max(CurrentPeakForceN * 1.15, 0.5);
|
? CurrentDisplacementMm
|
||||||
_kineticBand.Xi = data.Recipe.TravelMm * 0.35;
|
: data.Samples.Max(sample => sample.DisplacementMm);
|
||||||
_kineticBand.Xj = data.Recipe.TravelMm;
|
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.Yi = 0;
|
||||||
_kineticBand.Yj = Math.Max(CurrentPeakForceN * 1.15, 0.5);
|
_kineticBand.Yj = yMax;
|
||||||
OnPropertyChanged(nameof(ForceXAxes));
|
|
||||||
OnPropertyChanged(nameof(ForceYAxes));
|
|
||||||
OnPropertyChanged(nameof(ForceSections));
|
OnPropertyChanged(nameof(ForceSections));
|
||||||
RaiseStatusProperties();
|
RaiseStatusProperties();
|
||||||
OnPropertyChanged(nameof(AnalysisModeSummary));
|
OnPropertyChanged(nameof(AnalysisModeSummary));
|
||||||
@@ -1524,15 +1690,12 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||||||
CurrentKineticCoefficient = 0;
|
CurrentKineticCoefficient = 0;
|
||||||
TrialProgressPercent = 0;
|
TrialProgressPercent = 0;
|
||||||
|
|
||||||
ForceXAxes[0].MaxLimit = Math.Max(Recipe.TravelMm, 50);
|
SetAxisLimits(1, 0.5);
|
||||||
ForceYAxes[0].MaxLimit = 0.5;
|
|
||||||
_kineticBand.Xi = Recipe.TravelMm * 0.35;
|
_kineticBand.Xi = Recipe.TravelMm * 0.35;
|
||||||
_kineticBand.Xj = Recipe.TravelMm;
|
_kineticBand.Xj = Recipe.TravelMm;
|
||||||
_kineticBand.Yi = 0;
|
_kineticBand.Yi = 0;
|
||||||
_kineticBand.Yj = 0.5;
|
_kineticBand.Yj = 0.5;
|
||||||
|
|
||||||
OnPropertyChanged(nameof(ForceXAxes));
|
|
||||||
OnPropertyChanged(nameof(ForceYAxes));
|
|
||||||
OnPropertyChanged(nameof(ForceSections));
|
OnPropertyChanged(nameof(ForceSections));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1541,6 +1704,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||||||
_activeRunId = Guid.Empty;
|
_activeRunId = Guid.Empty;
|
||||||
_activeRecipeSnapshot = null;
|
_activeRecipeSnapshot = null;
|
||||||
_activeRunStartedByPlc = false;
|
_activeRunStartedByPlc = false;
|
||||||
|
_runStartHorizontalPositionMm = double.NaN;
|
||||||
_currentRunSamples.Clear();
|
_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;
|
exportData = historyData;
|
||||||
if (!hasExportableSamples)
|
if (!hasExportableSamples)
|
||||||
{
|
{
|
||||||
@@ -1781,7 +1952,9 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
var workbookPath = _runExportService.ExportHistoricalComparisonReport(exportData, saveFileDialog.FileName);
|
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);
|
_dataRepository.UpdateExportPaths(item.Run.RunId, null, workbookPath);
|
||||||
item.Run.ReportExportPath = 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()
|
private void ClearHistoryData()
|
||||||
{
|
{
|
||||||
var historyCount = RunHistory.Count;
|
var historyCount = RunHistory.Count;
|
||||||
|
|||||||
Reference in New Issue
Block a user