更新20260622
This commit is contained in:
@@ -34,6 +34,14 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
private const double StaticPeakDropToleranceN = 0.5;
|
||||
private const double SlidingStartDisplacementThresholdMm = 0.05;
|
||||
private const double MinimumAnalysisLoadRatio = 0.8;
|
||||
// 预触发缓冲:滑动检测前持续缓存的力/位移历史长度,用于回溯真实滑动起点(保证第一个摩擦力峰值被采到)。
|
||||
private const double PreTriggerWindowSeconds = 0.5;
|
||||
// 滑动检测:载荷达标后,水平摩擦力较静置接触基线上升超过该值即判定已进入滑动。
|
||||
private const double SlidingFrictionTriggerRiseN = 5.0;
|
||||
// 回溯滑动起点:从检测点向前回溯,直到摩擦力回到基线附近(该裕度内)即认定为 onset(t=0)。
|
||||
private const double SlidingOnsetFrictionMarginN = 2.0;
|
||||
// 曲线/分析窗口:滑动开始后只保留该时长,剔除加载段与滑动后回程,对应标准曲线图的有效区间。
|
||||
private const double CurveEndSeconds = 1.0;
|
||||
private const int StaticPeakDropConfirmationPointCount = 3;
|
||||
private const int MinimumDynamicWindowPointCount = 10;
|
||||
private const int StandardTrialCount = 3;
|
||||
@@ -52,6 +60,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
private readonly DispatcherTimer refreshTimer;
|
||||
private readonly Stopwatch runStopwatch = new();
|
||||
private readonly List<SlipDataPoint> currentRun = [];
|
||||
private readonly List<SlipDataPoint> preTriggerBuffer = [];
|
||||
private readonly ObservableCollection<ObservablePoint> verticalLoadPoints = [];
|
||||
private readonly ObservableCollection<ObservablePoint> horizontalFrictionPoints = [];
|
||||
private readonly ObservableCollection<ObservablePoint> frictionCoefficientPoints = [];
|
||||
@@ -72,6 +81,8 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
private double runStartDisplacementMm;
|
||||
private double slidingStartDisplacementMm;
|
||||
private double? slidingStartTimeSeconds;
|
||||
private double slidingBaselineFrictionN;
|
||||
private bool hasSlidingBaseline;
|
||||
private int activeRunLubricantIndex;
|
||||
private DateTime lastLicenseCheckAt = DateTime.MinValue;
|
||||
private bool isLicenseLockPending;
|
||||
@@ -811,6 +822,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
private void BeginRun()
|
||||
{
|
||||
currentRun.Clear();
|
||||
preTriggerBuffer.Clear();
|
||||
verticalLoadPoints.Clear();
|
||||
horizontalFrictionPoints.Clear();
|
||||
frictionCoefficientPoints.Clear();
|
||||
@@ -818,6 +830,8 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
runStartDisplacementMm = deviceService.CurrentSnapshot.DisplacementMm;
|
||||
slidingStartDisplacementMm = runStartDisplacementMm;
|
||||
slidingStartTimeSeconds = null;
|
||||
slidingBaselineFrictionN = 0;
|
||||
hasSlidingBaseline = false;
|
||||
activeRunLubricantIndex = SelectedLubricantIndex;
|
||||
runStopwatch.Restart();
|
||||
UploadProgress = 0;
|
||||
@@ -834,7 +848,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
private void RecordPoint(SlipDeviceSnapshot device)
|
||||
{
|
||||
var elapsedSinceTestStart = runStopwatch.Elapsed.TotalSeconds;
|
||||
var detectionPoint = new SlipDataPoint(
|
||||
var samplePoint = new SlipDataPoint(
|
||||
device.Timestamp,
|
||||
elapsedSinceTestStart,
|
||||
device.VerticalLoadN,
|
||||
@@ -842,25 +856,114 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
device.DisplacementMm,
|
||||
device.FrictionCoefficient);
|
||||
|
||||
TryMarkSlidingStart(detectionPoint);
|
||||
// 滑动起点尚未确定:把样本缓存进滚动预触发缓冲,并尝试检测滑动起点。
|
||||
if (!slidingStartTimeSeconds.HasValue)
|
||||
{
|
||||
UpdatePreTrigger(samplePoint);
|
||||
return;
|
||||
}
|
||||
|
||||
var standardTime = elapsedSinceTestStart - slidingStartTimeSeconds.Value;
|
||||
// 只保留滑动开始后 CurveEndSeconds 区间,剔除滑动结束后的回程/抬升段。
|
||||
if (standardTime > CurveEndSeconds)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var standardTime = Math.Max(0, elapsedSinceTestStart - slidingStartTimeSeconds.Value);
|
||||
AppendCurvePoint(samplePoint, standardTime);
|
||||
}
|
||||
|
||||
// 滑动检测前持续缓存样本,并在载荷达标后用水平摩擦力陡升判定滑动起点。
|
||||
private void UpdatePreTrigger(SlipDataPoint samplePoint)
|
||||
{
|
||||
preTriggerBuffer.Add(samplePoint);
|
||||
var oldestAllowed = samplePoint.TimeSeconds - PreTriggerWindowSeconds;
|
||||
while (preTriggerBuffer.Count > 0 && preTriggerBuffer[0].TimeSeconds < oldestAllowed)
|
||||
{
|
||||
preTriggerBuffer.RemoveAt(0);
|
||||
}
|
||||
|
||||
var minimumAnalysisLoad = GetMinimumAnalysisLoad();
|
||||
if (samplePoint.VerticalLoadN < minimumAnalysisLoad)
|
||||
{
|
||||
// 载荷尚未到达规定压力(静置接触前),基线需在载荷达标后重新捕获。
|
||||
hasSlidingBaseline = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasSlidingBaseline)
|
||||
{
|
||||
// 载荷首次达标:以当前(仅法向加载、尚未水平滑动)的摩擦力为静置接触基线。
|
||||
slidingBaselineFrictionN = samplePoint.HorizontalFrictionN;
|
||||
hasSlidingBaseline = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var frictionRose = samplePoint.HorizontalFrictionN - slidingBaselineFrictionN >= SlidingFrictionTriggerRiseN;
|
||||
var displacementMoved = Math.Abs(samplePoint.DisplacementMm - runStartDisplacementMm) >= SlidingStartDisplacementThresholdMm;
|
||||
if (frictionRose || displacementMoved)
|
||||
{
|
||||
MarkSlidingStartFromBuffer(frictionRose ? "FrictionRise" : "DisplacementMove");
|
||||
}
|
||||
}
|
||||
|
||||
// 回溯预触发缓冲到摩擦力刚离开基线的那一刻设为 t=0,并把该时刻起的缓冲点补进曲线,保证第一个峰值被采到。
|
||||
private void MarkSlidingStartFromBuffer(string trigger)
|
||||
{
|
||||
var onsetIndex = preTriggerBuffer.Count - 1;
|
||||
while (onsetIndex > 0
|
||||
&& preTriggerBuffer[onsetIndex - 1].HorizontalFrictionN > slidingBaselineFrictionN + SlidingOnsetFrictionMarginN)
|
||||
{
|
||||
onsetIndex--;
|
||||
}
|
||||
|
||||
// 多回退一个点(≈基线水平)作为 t=0,使曲线起点的摩擦力接近基线、随后升至首峰,与标准曲线一致。
|
||||
onsetIndex = Math.Max(0, onsetIndex - 1);
|
||||
var onset = preTriggerBuffer[onsetIndex];
|
||||
slidingStartTimeSeconds = onset.TimeSeconds;
|
||||
slidingStartDisplacementMm = onset.DisplacementMm;
|
||||
|
||||
for (var index = onsetIndex; index < preTriggerBuffer.Count; index++)
|
||||
{
|
||||
var buffered = preTriggerBuffer[index];
|
||||
var standardTime = buffered.TimeSeconds - onset.TimeSeconds;
|
||||
if (standardTime > CurveEndSeconds)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
AppendCurvePoint(buffered, standardTime);
|
||||
}
|
||||
|
||||
preTriggerBuffer.Clear();
|
||||
|
||||
CurrentStatus = "已检测到有效滑动开始:曲线从 0.000 s 开始记录";
|
||||
Log.Information(
|
||||
"检测到有效滑动开始并建立曲线零点:TestNumber={TestNumber}, Trigger={Trigger}, SlidingStartTime={SlidingStartTime:F3}s, BaselineFriction={BaselineFriction:F3}N, OnsetDisplacement={OnsetDisplacement:F3}mm, FlushedPointCount={FlushedPointCount}",
|
||||
TestNumber,
|
||||
trigger,
|
||||
onset.TimeSeconds,
|
||||
slidingBaselineFrictionN,
|
||||
slidingStartDisplacementMm,
|
||||
currentRun.Count);
|
||||
}
|
||||
|
||||
// 把一个样本按标准时间(相对滑动起点)追加到数据与曲线集合,保持各曲线序列同步(节流到 SampleIntervalSeconds)。
|
||||
private void AppendCurvePoint(SlipDataPoint source, double standardTime)
|
||||
{
|
||||
standardTime = Math.Max(0, standardTime);
|
||||
if (currentRun.Count > 0 && standardTime - currentRun[^1].TimeSeconds < SampleIntervalSeconds)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var point = new SlipDataPoint(
|
||||
device.Timestamp,
|
||||
source.Timestamp,
|
||||
standardTime,
|
||||
device.VerticalLoadN,
|
||||
device.HorizontalFrictionN,
|
||||
Math.Abs(device.DisplacementMm - slidingStartDisplacementMm),
|
||||
device.FrictionCoefficient);
|
||||
source.VerticalLoadN,
|
||||
source.HorizontalFrictionN,
|
||||
Math.Abs(source.DisplacementMm - slidingStartDisplacementMm),
|
||||
source.FrictionCoefficient);
|
||||
|
||||
currentRun.Add(point);
|
||||
verticalLoadPoints.Add(new ObservablePoint(standardTime, point.VerticalLoadN));
|
||||
@@ -1091,34 +1194,6 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
ResultSummary = $"近 3 次平均 静 {staticAverage:F2} / 动 {dynamicAverage:F2}";
|
||||
}
|
||||
|
||||
private void TryMarkSlidingStart(SlipDataPoint point)
|
||||
{
|
||||
if (slidingStartTimeSeconds.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var minimumAnalysisLoad = GetMinimumAnalysisLoad();
|
||||
if (!IsSlidingStartPoint(point, runStartDisplacementMm, minimumAnalysisLoad))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
slidingStartTimeSeconds = point.TimeSeconds;
|
||||
slidingStartDisplacementMm = point.DisplacementMm;
|
||||
CurrentStatus = "已检测到有效滑动开始:曲线从 0.000 s 开始记录";
|
||||
Log.Information(
|
||||
"检测到有效滑动开始并建立曲线零点:TestNumber={TestNumber}, SlidingStartTime={SlidingStartTime:F3}s, SlidingDetectionDelay={SlidingDetectionDelay:F3}s, StandardTimeOrigin=0.000s, StartDisplacement={StartDisplacement:F3}mm, SlidingStartDisplacement={SlidingStartDisplacement:F3}mm, DisplacementDelta={DisplacementDelta:F3}mm, VerticalLoad={VerticalLoad:F3}N, MinimumAnalysisLoad={MinimumAnalysisLoad:F3}N",
|
||||
TestNumber,
|
||||
point.TimeSeconds,
|
||||
point.TimeSeconds,
|
||||
runStartDisplacementMm,
|
||||
slidingStartDisplacementMm,
|
||||
Math.Abs(point.DisplacementMm - runStartDisplacementMm),
|
||||
point.VerticalLoadN,
|
||||
minimumAnalysisLoad);
|
||||
}
|
||||
|
||||
private double GetMinimumAnalysisLoad() =>
|
||||
TryParseLoadValue(TargetLoadText, out var targetLoad)
|
||||
? Math.Max(1, targetLoad * MinimumAnalysisLoadRatio)
|
||||
@@ -1132,10 +1207,6 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
return double.TryParse(numeric, NumberStyles.Float, CultureInfo.InvariantCulture, out load);
|
||||
}
|
||||
|
||||
private static bool IsSlidingStartPoint(SlipDataPoint point, double startDisplacementMm, double minimumAnalysisLoad) =>
|
||||
point.VerticalLoadN >= minimumAnalysisLoad
|
||||
&& Math.Abs(point.DisplacementMm - startDisplacementMm) >= SlidingStartDisplacementThresholdMm;
|
||||
|
||||
private static StaticPeakSelection FindStaticPeak(IReadOnlyList<SlipDataPoint> points)
|
||||
{
|
||||
var searchWindow = points
|
||||
|
||||
Reference in New Issue
Block a user