fix(resample,formula,clone): 重采样面板拖动/关闭失效 + 统计频率与输入框不一致 + 派生烘焙接入公式映射 + 克隆拆分
一、「重采样」浮动面板拖不动 / ✕ 关不掉(两个独立缺陷)
1. 拖不动:OnLButtonDown 的 resampleMode 分支处理完条目与按钮后无条件 return true,
而「标题栏 -> EDGE_PANEL_MOVE」的判定在面板分支最末尾 => 标题栏按下被吞掉。
改为只消费 insideContent 之内的点击。
2. 拖不动(第二处):OnMouseMove 的 EDGE_PANEL_MOVE -> switch (m_edgePanelDragKind)
漏了 case RESAMPLE => 位置写进 edgePanel、又从 resamplePanel 读 => 纹丝不动。
3. ✕ 关不掉:OnLButtonUp 的 m_pendingPanelClose -> switch (kind) 漏了 RESAMPLE
=> 落进 default 关掉的是「边沿计数」。补 SetLaneResample(lane,false)。
二、统计「最小数据间隔」与「重采样频率」输入框不一致(用户报告)
实例 MF4-Record.MF4 / m_MOT_Current_DCom[0]:最小间隔 0.003696631s
- 统计行先舍入到 3 位有效数字(3.70ms)再换算 => 270.27 Hz;
- 输入框用未舍入原值换算 => 270.517 Hz。
真值 270.5166 Hz,两条路径基准不同。修法:频率一律由 RoundIntervalForDisplay
之后的同一份显示值换算 => 输入框与统计行同为 270.27 Hz、间隔文本同为 3ms 700us。
未改动面板行数与任何 UI 布局。
三、派生烘焙接入「公式映射」
BuildDerivedSignal 与 CloneSignal 各写了一份
gain!=1 || absolute || mirrorVertical || offsetY!=0,漏掉 HasFormula()
=> 带公式的信号派生/克隆后公式被静默丢弃。改为含 HasFormula() 的统一判定。
BuildResampledSignal 本就走 ApplyDrawTransform 全程烘值,天然包含公式。
四、信号右键菜单:克隆拆成两个语义(均不烘焙采样值)
- 「克隆该信号」:原始数据 + 外观 + 仅保留时间轴偏移,其余调整复位;
- 「克隆该信号(带调整)」:原始数据 + 外观 + 整套调整参数。
新增 Application::CloneSignalAdjusted、CMD_SIGNAL_CLONE_ADJUSTED=6、
kMessageCloneSignalAdjusted=WM_APP+6。
五、教训:离屏探针崩溃会经 MemProbe::OnTerminate 弹模态「内存不足」框,
反复重跑即反复弹窗(用户看到十几次报警,真实进程并无内存问题)。
探针应避免 Initialize+Render,或只做数据层验证;必须无缓冲输出阶段标记。
验证:.preflight/r26 三个探针(真实 MF4 数据一致性 SAME;逻辑 7/7;端到端 12/12);
make release 零 warning。
This commit is contained in:
1 parent
74456b9305
commit
9fdbf49841
9 files changed
+278
-37
No files matched your search
@@ -355,17 +355,38 @@ namespace jdi
|
||||
bool makeVisible, int& newIndex);
|
||||
|
||||
/**
|
||||
* @brief 克隆一条信号:把它的「调整」(系数 / 绝对值 / 镜像 / Y 偏移)烘焙进采样值,
|
||||
* 时间轴偏移保留在样式上,名称前置 `*`,插入到原信号下方。
|
||||
* @brief 克隆一条信号(**不烘值**,只沿用源信号的时间轴偏移)。
|
||||
*
|
||||
* 烘值规则与「构建为新信号」完全一致,因此克隆曲线与原信号重合;区别只是
|
||||
* 克隆**保留原外观**(颜色 / 透明度 / 线宽 / 绘制样式),便于“冻住当前效果继续调”。
|
||||
* 语义(用户 2026-11-03 明确要求):
|
||||
* * 采样值 = 源信号的**原始数据**(不做任何烘焙);
|
||||
* * 名称 = `*` + 源信号名,插入到原信号下方;
|
||||
* * 样式:沿用源信号的外观(颜色 / 透明度 / 线宽 / 绘制样式)**与时间轴偏移**
|
||||
* (`offsetTime` 只能留在样式上,无法烘进采样值),
|
||||
* 而**公式映射 / 系数 / 绝对值 / 镜像 / Y 偏移一律恢复默认**。
|
||||
*
|
||||
* 于是「克隆该信号」得到的是一条“去掉所有数值调整、但仍在同一个时刻显示”的原始信号,
|
||||
* 便于与源信号叠加对比。
|
||||
*
|
||||
* @param [IN] sourceRef 源信号引用。
|
||||
* @return 克隆成功返回 true。
|
||||
*/
|
||||
bool CloneSignal(const SignalRef& sourceRef);
|
||||
|
||||
/**
|
||||
* @brief 克隆一条信号(**带调整**:完整沿用源信号的调整参数,不烘值)。
|
||||
*
|
||||
* 语义(用户 2026-11-03 明确要求):
|
||||
* * 采样值 = 源信号的**原始数据**(不做任何烘焙);
|
||||
* * 名称 = `*` + 源信号名,插入到原信号下方;
|
||||
* * 样式:**完整复制**源信号的调整参数(公式映射 / 系数 / Y 偏移 / 时间轴偏移 /
|
||||
* 水平与垂直镜像 / 绝对值)以及外观(颜色 / 透明度 / 线宽 / 绘制样式)。
|
||||
*
|
||||
* 因此新信号与源信号**逐像素重合**,且可以在新信号上继续独立修改调整而不影响源信号。
|
||||
*
|
||||
* @param [IN] sourceRef 源信号引用。
|
||||
* @return 克隆成功返回 true。
|
||||
*/
|
||||
bool CloneSignalAdjusted(const SignalRef& sourceRef);
|
||||
/**
|
||||
* @brief 弹出“打开数据文件”对话框并加载所选文件。
|
||||
*/
|
||||
|
||||
@@ -64,6 +64,14 @@ namespace jdi
|
||||
inline const UINT kMessageRemoveGroup = WM_APP + 1; /**< 面板请求移除分组的自定义消息。 */
|
||||
inline const UINT kMessageRemoveSignal = WM_APP + 2; /**< 面板请求移除单个信号的自定义消息。 */
|
||||
inline const UINT kMessageCloneSignal = WM_APP + 5; /**< 面板请求克隆单个信号的自定义消息。 */
|
||||
/**
|
||||
* @brief 面板请求「克隆该信号(带调整)」的自定义消息。
|
||||
*
|
||||
* `wParam = groupIndex`、`lParam = signalIndex`(与 `kMessageCloneSignal` 同一参数约定)。
|
||||
* 新信号**沿用源信号的全部调整参数**(公式映射 / 系数 / 偏移 / 镜像 / 绝对值),
|
||||
* 不烘值 ⇒ 两条曲线逐像素重合,且新信号的「调整」面板与源信号完全一致。
|
||||
*/
|
||||
inline const UINT kMessageCloneSignalAdjusted = WM_APP + 6; /**< 面板请求「克隆该信号(带调整)」的自定义消息。 */
|
||||
inline const UINT_PTR kHintTimerId = 1; /**< 提示浮层淡出用的定时器 ID。 */
|
||||
inline const UINT kHintTimerPeriodMs = 33; /**< 提示浮层淡出的重绘间隔(约 30fps)。 */
|
||||
inline const UINT_PTR kRamTimerId = 2; /**< 标题栏内存占用刷新的定时器 ID。 */
|
||||
|
||||
@@ -1746,6 +1746,15 @@ namespace jdi
|
||||
CloneSignal(ref);
|
||||
return 0;
|
||||
}
|
||||
case kMessageCloneSignalAdjusted:
|
||||
{
|
||||
// 「克隆该信号(带调整)」:参数约定与 `kMessageCloneSignal` 完全相同。
|
||||
SignalRef ref;
|
||||
ref.groupIndex = static_cast<int>(wParam);
|
||||
ref.signalIndex = static_cast<int>(static_cast<LONG_PTR>(lParam));
|
||||
CloneSignalAdjusted(ref);
|
||||
return 0;
|
||||
}
|
||||
case WM_TIMER:
|
||||
{
|
||||
// 标题栏内存占用:只改窗口标题,不动客户区,因此无需重绘。
|
||||
|
||||
@@ -720,14 +720,19 @@ namespace jdi
|
||||
derived.values.assign(derived_values.begin(), derived_values.end());
|
||||
|
||||
// 2b. 把源信号的「调整」变换**烘进派生采样值**:
|
||||
// * 数值域(系数 → 绝对值 → 垂直镜像 → Y 偏移)逐点套用 `ApplyDrawTransform`;
|
||||
// * 数值域(**公式映射** → 系数 → 绝对值 → 垂直镜像 → Y 偏移)逐点套用
|
||||
// `ApplyDrawTransform`(它本来就是“公式最先结算”,与绘制路径同源);
|
||||
// * 水平镜像 = 数据反序,等价于把采样数组整体反转。
|
||||
// ⚠️ `ApplyDrawTransform` 是逐点函数,因此"先变换再反转"与"先反转再变换"等价。
|
||||
// ⚠️⚠️ **公式映射必须算作“有变换”**(用户 2026-11-03):旧判定只列了系数/绝对值/
|
||||
// 垂直镜像/Y 偏移,于是带公式的信号派生出来的新信号**丢掉了公式**(不烘也不继承)
|
||||
// ⇒ 新信号曲线与预览曲线不一致。
|
||||
if (hasSourceStyle)
|
||||
{
|
||||
const bool valueTransform =
|
||||
(sourceTransform.gain != 1.0f || sourceTransform.absolute ||
|
||||
sourceTransform.mirrorVertical || sourceTransform.offsetY != 0.0f);
|
||||
sourceTransform.HasFormula() || sourceTransform.gain != 1.0f ||
|
||||
sourceTransform.absolute || sourceTransform.mirrorVertical ||
|
||||
sourceTransform.offsetY != 0.0f;
|
||||
if (valueTransform)
|
||||
{
|
||||
for (double& value : derived.values)
|
||||
@@ -806,6 +811,8 @@ namespace jdi
|
||||
|
||||
// 1. 收集「变换后」的采样点(升序)。变换与绘制一致:
|
||||
// 公式映射 → 系数 → 绝对值 → 垂直镜像 → Y 偏移(时间轴偏移不烘值)。
|
||||
// ⚠️ 判定统一走 `HasValueTransform()`(含公式映射),否则带公式的信号
|
||||
// 重采样时会**丢掉公式**(用户 2026-11-03 报告)。
|
||||
const std::size_t count = std::min(axis.time.size(), source.values.size());
|
||||
std::vector<double> sourceTimes;
|
||||
std::vector<double> sourceValues;
|
||||
@@ -978,7 +985,7 @@ namespace jdi
|
||||
}
|
||||
const SignalData& source = group->signals[static_cast<std::size_t>(sourceRef.signalIndex)];
|
||||
|
||||
// 源信号的「调整」快照(插入会移动信号存储,之后不能再解引用 `source`)。
|
||||
// 源信号的样式快照(插入会移动信号存储,之后不能再解引用 `source`)。
|
||||
SignalStyle transform;
|
||||
bool hasStyle = false;
|
||||
if (const SignalStyle* style = m_dataSet.Style(sourceRef))
|
||||
@@ -987,29 +994,17 @@ namespace jdi
|
||||
hasStyle = true;
|
||||
}
|
||||
|
||||
// 克隆 = 把「调整」烘进采样值(**时间轴偏移除外**,它留在样式上)。
|
||||
// 与「构建为新信号」完全同一套烘值规则,因此克隆出来的曲线与原信号**逐像素重合**,
|
||||
// 但它的「调整」面板回到默认值,可以继续在此基础上继续叠加新调整。
|
||||
// ⚠️ 用户 2026-11-03 明确要求:「克隆该信号」**不做任何烘焙**。
|
||||
// 新信号直接使用源信号的**原始采样数据**,配方上只沿用:
|
||||
// * 外观(颜色 / 自定义色 / 透明度 / 线宽 / 绘制方式);
|
||||
// * **时间轴偏移**(`offsetTime` 只能留在样式上 —— 共用同一条时间轴,
|
||||
// 烘焙进采样值会在越界段写 NaN,见 `BuildDerivedSignal` 2c 的说明)。
|
||||
// 公式映射 / 系数 / 绝对值 / 镜像 / Y 偏移一律**保持默认**(不烘也不应用)。
|
||||
// 需要“连调整一起复制”请用「克隆该信号(带调整)」(`CloneSignalAdjusted`)。
|
||||
SignalData clone;
|
||||
clone.name = L"*" + source.name;
|
||||
clone.axisIndex = source.axisIndex;
|
||||
clone.values = source.values;
|
||||
if (hasStyle)
|
||||
{
|
||||
const bool valueTransform = (transform.gain != 1.0f || transform.absolute ||
|
||||
transform.mirrorVertical || transform.offsetY != 0.0f);
|
||||
if (valueTransform)
|
||||
{
|
||||
for (double& value : clone.values)
|
||||
{
|
||||
value = ApplyDrawTransform(transform, value);
|
||||
}
|
||||
}
|
||||
if (transform.mirrorHorizontal)
|
||||
{
|
||||
std::reverse(clone.values.begin(), clone.values.end());
|
||||
}
|
||||
}
|
||||
|
||||
int newIndex = -1;
|
||||
if (!InsertSignalBelowSource(sourceRef.groupIndex, sourceRef.signalIndex, std::move(clone),
|
||||
@@ -1018,8 +1013,7 @@ namespace jdi
|
||||
return false;
|
||||
}
|
||||
|
||||
// 外观:克隆要**看起来与原信号一样**(颜色 / 透明度 / 线宽 / 绘制样式),
|
||||
// 只把被烘掉的「调整」复位为默认值,并保留时间轴偏移。
|
||||
// 外观:克隆要**看起来与原信号一样**,但所有数值调整保持默认值。
|
||||
if (SignalStyle* cloned = m_dataSet.Style(SignalRef{sourceRef.groupIndex, newIndex}))
|
||||
{
|
||||
if (hasStyle)
|
||||
@@ -1029,6 +1023,7 @@ namespace jdi
|
||||
cloned->opacity = transform.opacity;
|
||||
cloned->lineWidth = transform.lineWidth;
|
||||
cloned->mode = transform.mode;
|
||||
// ⚠️ 只保留时间轴偏移,其余调整保持默认(用户 2026-11-03 明确要求)。
|
||||
cloned->offsetTime = transform.offsetTime;
|
||||
}
|
||||
// ⚠️ 单点信号默认会改成「散点 + 最大线宽」(`InsertDerivedSignal` 里的规则),
|
||||
@@ -1044,6 +1039,70 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::CloneSignalAdjusted(const SignalRef& sourceRef)
|
||||
{
|
||||
const SignalGroup* group = m_dataSet.GroupAt(sourceRef.groupIndex);
|
||||
if (group == nullptr || sourceRef.signalIndex < 0 ||
|
||||
sourceRef.signalIndex >= static_cast<int>(group->signals.size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const SignalData& source = group->signals[static_cast<std::size_t>(sourceRef.signalIndex)];
|
||||
|
||||
SignalStyle transform;
|
||||
bool hasStyle = false;
|
||||
if (const SignalStyle* style = m_dataSet.Style(sourceRef))
|
||||
{
|
||||
transform = *style;
|
||||
hasStyle = true;
|
||||
}
|
||||
|
||||
// 「克隆该信号(带调整)」:同样是**原始采样数据**(不做烘焙),
|
||||
// 区别只在样式——完整沿用源信号的整套调整参数(公式映射 / 系数 / Y 偏移 /
|
||||
// 时间轴偏移 / 水平与垂直镜像 / 绝对值),因此两条曲线**逐像素重合**,
|
||||
// 且新信号可以独立继续调参而不影响源信号(用户 2026-11-03 明确要求)。
|
||||
SignalData clone;
|
||||
clone.name = L"*" + source.name;
|
||||
clone.axisIndex = source.axisIndex;
|
||||
clone.values = source.values;
|
||||
|
||||
int newIndex = -1;
|
||||
if (!InsertSignalBelowSource(sourceRef.groupIndex, sourceRef.signalIndex, std::move(clone),
|
||||
true, newIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SignalStyle* cloned = m_dataSet.Style(SignalRef{sourceRef.groupIndex, newIndex}))
|
||||
{
|
||||
if (hasStyle)
|
||||
{
|
||||
// 外观。
|
||||
cloned->color = transform.color;
|
||||
cloned->customColor = transform.customColor;
|
||||
cloned->opacity = transform.opacity;
|
||||
cloned->lineWidth = transform.lineWidth;
|
||||
cloned->mode = transform.mode;
|
||||
// 整套「调整」参数(与源信号完全一致)。
|
||||
cloned->formula = transform.formula;
|
||||
cloned->gain = transform.gain;
|
||||
cloned->absolute = transform.absolute;
|
||||
cloned->mirrorVertical = transform.mirrorVertical;
|
||||
cloned->mirrorHorizontal = transform.mirrorHorizontal;
|
||||
cloned->offsetY = transform.offsetY;
|
||||
cloned->offsetTime = transform.offsetTime;
|
||||
}
|
||||
}
|
||||
|
||||
SyncTabsToDataSet();
|
||||
SetStatusMessage(L"已克隆信号(带调整):" + m_dataSet.GroupAt(sourceRef.groupIndex)
|
||||
->signals[static_cast<std::size_t>(newIndex)]
|
||||
.name,
|
||||
false);
|
||||
InvalidateRect(m_hwnd, nullptr, TRUE);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::ApplyPendingPanelScroll()
|
||||
{
|
||||
SignalRef ref;
|
||||
|
||||
Reference in new issue
Block a user