From 955b233e63f6e1d8d631b8b5f1188aebbb74ff56 Mon Sep 17 00:00:00 2001 From: "DeepSeek V4.1 Flash" <> Date: Mon, 2 Nov 2026 20:42:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(style):=20=E3=80=8C=E7=B3=BB=E6=95=B0/?= =?UTF-8?q?=E5=81=8F=E7=A7=BB=E3=80=8D=E6=94=B9=E4=B8=BA=E3=80=8C=E6=98=A0?= =?UTF-8?q?=E5=B0=84/=E5=81=8F=E7=A7=BB=E3=80=8D=E5=B9=B6=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=8C=E5=85=AC=E5=BC=8F=E6=98=A0=E5=B0=84=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 按钮文案改为「映射/偏移」;激活态 = 公式非空 或 系数≠1 或 任一偏移≠0 - 二级面板顶部新增「公式映射」(标题栏带还原图标):Y = + 表达式输入框,默认 f(t) - 结算顺序固定:公式映射 -> 系数 -> 绝对值 -> 垂直镜像 -> Y 偏移 -> 时间轴偏移 - 表达式规则:支持 + - * / ( ) 一元正负、常量 pi、自变量 X/x/f/f(t);保存前把 X 归一化为 f(t) - 非法表达式不写入样式,警告走 SignalPanel::TakePendingHint -> PlotView::ShowPersistentHint(自绘浮层,不用 MessageBox) - 公式参与折线缓存键 / SceneContentHash / SceneStyleEntry / HaloLayerCache; DrawTransformOrderPreserving 返回 false(非保序),自适应幅值在 HasFormula() 时逐点扫描 - 派生信号与克隆的烘焙自动包含公式(时间轴偏移除外) - Documents/agent_mem.md 追加「第二十四轮」硬约束 --- .../SignalPanel/Src/SignalPanelMenu.cpp | 69 ++++++++++++++++++- .../SignalPanel/Src/SignalPanelPaint.cpp | 7 +- Documents/agent_mem.md | 57 +++++++++++++++ 3 files changed, 127 insertions(+), 6 deletions(-) diff --git a/Components/SignalPanel/Src/SignalPanelMenu.cpp b/Components/SignalPanel/Src/SignalPanelMenu.cpp index a716488..ea2a10e 100644 --- a/Components/SignalPanel/Src/SignalPanelMenu.cpp +++ b/Components/SignalPanel/Src/SignalPanelMenu.cpp @@ -20,12 +20,32 @@ namespace jdi return false; } - // 二级面板:两张“标题(带还原图标)+ 输入框”的组合: - // 1. 「系数调整」:一整行系数输入框; - // 2. 「偏移调整」:下方两行分别为「Y轴」与「时间轴」的偏移输入框。 + // 二级面板:三组“标题(带还原图标)+ 输入框”的组合: + // 1. 「公式映射」:`Y =` + 表达式输入框(自变量写作 f(t)); + // 2. 「系数调整」:一整行系数输入框; + // 3. 「偏移调整」:下方两行分别为「Y轴」与「时间轴」的偏移输入框。 //(与网格 Y 轴上下限的菜单内联编辑同一套做法:提交后面板保持打开,可连续微调。) + // ⚠️ 结算顺序固定为「公式映射 → 系数 → 偏移」(用户 2026-11-02 明确要求)。 std::vector items; + // 「公式映射」:默认 `f(t)`(= 等值照搬)。 + MenuItem formulaHeader; + formulaHeader.kind = MenuItemKind::HEADER_ACTION; + formulaHeader.label = L"公式映射"; + formulaHeader.id = CMD_FORMULA_RESET; // 点击还原图标 → 恢复为 f(t) + items.push_back(formulaHeader); + + const std::wstring formulaText = + style->formula.empty() ? std::wstring(L"f(t)") : style->formula; + MenuItem formulaEdit; + formulaEdit.kind = MenuItemKind::EDIT; + formulaEdit.label = L"Y ="; // 输入框独占右侧剩余整行 + formulaEdit.id = CMD_FORMULA_EDIT; + formulaEdit.value = formulaText; + formulaEdit.editValue = formulaText; + formulaEdit.filter = EditFilter::ANY; + items.push_back(formulaEdit); + MenuItem gainHeader; gainHeader.kind = MenuItemKind::HEADER_ACTION; gainHeader.label = L"系数调整"; @@ -337,6 +357,35 @@ namespace jdi const bool valid = !trimmed.empty() && stop != trimmed.c_str() && std::isfinite(parsed); switch (result.id) { + case CMD_FORMULA_EDIT: + { + // 「公式映射」:把自变量写法统一为 `f(t)`,非法表达式**不写入**并给出警告 + //(警告走自绘浮层,见 `TakePendingHint`;不使用 Windows 消息框)。 + const std::wstring normalized = NormalizeFormulaText(trimmed); + if (normalized.empty()) + { + style->formula.clear(); + } + else + { + std::wstring error; + if (!ValidateFormula(normalized, &error)) + { + m_pendingHint = L"公式无法解析:" + error; + } + else + { + style->formula = normalized; + } + } + if (m_menu != nullptr) + { + const std::wstring display = + style->formula.empty() ? std::wstring(L"f(t)") : style->formula; + m_menu->UpdateItemValue(CMD_FORMULA_EDIT, display, display); + } + return false; + } case CMD_GAIN_EDIT: { if (valid) @@ -391,6 +440,20 @@ namespace jdi } } + // 标题栏右侧的「还原」图标:公式恢复为默认的 `f(t)`(= 等值照搬)。 + if (result.id == CMD_FORMULA_RESET) + { + if (SignalStyle* style = StyleOf(SelectedRef())) + { + style->formula.clear(); + if (m_menu != nullptr) + { + m_menu->UpdateItemValue(CMD_FORMULA_EDIT, L"f(t)", L"f(t)"); + } + } + return false; + } + // 标题栏右侧的「还原」图标:系数重置为默认值 1(面板保持打开,输入框立即回显)。 if (result.id == CMD_GAIN_RESET) { diff --git a/Components/SignalPanel/Src/SignalPanelPaint.cpp b/Components/SignalPanel/Src/SignalPanelPaint.cpp index 563bea7..34b3751 100644 --- a/Components/SignalPanel/Src/SignalPanelPaint.cpp +++ b/Components/SignalPanel/Src/SignalPanelPaint.cpp @@ -904,9 +904,10 @@ namespace jdi UiText(g, label, m_fonts->uiSmall, rect, checked ? m_theme->textStrong : m_theme->text, TextAlign::CENTER); }; - // 系数/偏移:系数不等于默认值 1、或任一偏移不为 0 时显示为"已启用"。 - paintToggle(kAdjustGain, L"系数/偏移", - !GainIsDefault(style->gain) || style->Offset(), StyleHit::GAIN_ADJUST); + // 映射/偏移:公式映射非空、系数不等于默认值 1、或任一偏移不为 0 时显示为"已启用"。 + paintToggle(kAdjustGain, L"映射/偏移", + style->HasFormula() || !GainIsDefault(style->gain) || style->Offset(), + StyleHit::GAIN_ADJUST); // 镜像:水平或垂直任一被勾选即显示为激活态。 paintToggle(kAdjustMirror, L"镜像", style->Mirrored(), StyleHit::MIRROR_ADJUST); paintToggle(kAdjustAbsolute, L"绝对值", style->absolute, StyleHit::ABSOLUTE_TOGGLE); diff --git a/Documents/agent_mem.md b/Documents/agent_mem.md index 5b0b9b4..ef87f93 100644 --- a/Documents/agent_mem.md +++ b/Documents/agent_mem.md @@ -9498,3 +9498,60 @@ make release / make debug 零 warning;Release 814,080 B `paintOverlays()` 里,否则只会在整幅重绘的那一帧出现。 * ⚠️ PowerShell 5.1 的 `-replace`/多行字符串在含中文/引号时会把命令搞坏;改文件内容优先 用编辑工具或 `.ps1` + `[System.IO.File]::WriteAllText`。 + + +## 2026-11-02 第二十四轮(本机,`dev` 分支):实验性功能「重采样」+ 样式区「映射/偏移」公式映射 + +> 本条为**硬约束**,后续改动若与本条冲突,以本条为准(远端合并时请保留并合并,不要覆盖)。 + +### A. 「重采样」(实验性功能,位于「自由标记」**上方**) + +1. **数据模型**:`Lane::ResampleItem { ref, interval, initialized, maxInterval, minInterval, collapsed }` + + `Lane::{resample, resampleItems, resamplePanel, resampleScroll}`; + `LaneCarry` / `SplitLane` / `PruneLaneSignalItems` / `RemapAfterSignalInsert` / + `RemapAfterSignalRemoval` **必须全部同步**(漏一处就会出现"拆网格后功能复制/条目指向错信号")。 +2. **面板**:与「均值滤波 / 中心轴 / 弱峰调整」同一套**可滚动分组列表**(信号色叠底条目栏 + + 右侧复选框 + 展开三角);展开后 4 行:最大数据间隔(只读)/ 最小数据间隔(只读)/ + 重采样间隔(输入,秒)/ 重采样频率(输入,Hz)。底部「构建为新信号」固定不滚动。 +3. ⚠️ **最大/最小数据间隔绝不能在绘制时计算**:千万级信号逐帧扫描会卡死。勾选该信号时 + (`SetLaneResample` / `ToggleLaneResampleSignal`)一次性算好并缓存进 `ResampleItem`, + 绘制只读字段。 +4. ⚠️ **重采样产生新的时间刻度** ⇒ 必须 `DataSet::AppendGroupAxis()` 追加一条新 `TimeAxis`, + 再 `InsertDerivedSignal`(`axisIndex` 指向新轴)。**不能复用源信号的时间轴**(否则采样与时间错位)。 +5. 采样值先按「公式映射 → 系数 → 绝对值 → 垂直镜像 → Y 偏移」变换(与绘制一致), + 再在 `[t0, t1]` 上等间隔线性插值;**时间轴偏移不烘值**(留在新信号样式上,与其它派生信号同规则)。 +6. 时间轴非单调 / 有效点 < 2 / 间隔 > 跨度 / 点数 > 2000 万 ⇒ **拒绝构建**并用 + `PlotView::ShowPersistentHint` 提示(不弹 Windows 消息框)。 +7. 新信号名 = `<原信号> (重采样 <频率>)`,频率单位在 Hz/kHz/MHz/GHz 自适应(`Utf8Util::FormatFrequency`)。 + +### B. 样式区「系数/偏移」→「映射/偏移」+ 公式映射 + +1. 按钮文案改为 **「映射/偏移」**;激活态 = 公式非空 **或** 系数 ≠ 1 **或** 任一偏移 ≠ 0。 +2. 二级面板顶部新增 **「公式映射」**(标题栏带还原图标):`Y =` + 文本输入框,默认 `f(t)`。 + 复位 ⇒ `style->formula.clear()`(即以 `f(t)` 显示/生效)。 +3. ⚠️ **结算顺序固定为:公式映射 → 系数 → 绝对值 → 垂直镜像 → Y 偏移 → 时间轴偏移** + (唯一实现在 `ApplyDrawTransform`,不要在别处重复实现公式)。 +4. 表达式编译/求值唯一实现 = `Utilities/Inc|Src/FormulaEval.{hpp,cpp}`: + 语法 `+ - * / ( )`、一元正负、数字(含 e/E 指数)、常量 `pi`、自变量 `X` / `x` / `f` / `f(t)`。 + ⚠️ 求值走 `thread_local` 单条缓存(按 `wstring.data()` 指针 + 内容判定), + **绝不逐采样点重新解析**(千万级采样会慢到不可用)。 +5. 非法表达式:**不写入样式**,警告经 `SignalPanel::TakePendingHint` → 宿主 + `m_plot.ShowPersistentHint()`(与"加载失败"同一套自绘浮层,**不用 MessageBox**)。 + 输入合法时把 `X`/`x`/`f` 归一化为 `f(t)` 再保存(`NormalizeFormulaText`)。 +6. ⚠️ **公式会影响缓存与包围盒,四处必须同步**: + * `AcquirePolyline` 折线缓存键; + * `SceneContentHash`(→ 场景/宽基底缓存失效); + * `SceneStyleEntry`(→ 局部重画判据)+ `HaloLayerCache`(→ 悬停光晕); + * `DrawTransformOrderPreserving()` 返回 **false**(公式不保序,粗粒度包络层不能用); + 自动幅值范围在 `HasFormula()` 时必须**逐点扫描**(`TabConfig::ComputeLaneRange`)。 +7. 派生信号 / 克隆的烘焙自动包含公式(两者都走 `ApplyDrawTransform`),**时间轴偏移除外**。 + +### C. 验证(针对性,仅本轮功能) + +`.preflight/devcheck`(`build.ps1` + `main.cpp`,**`.preflight` 已被 gitignore**)62/62: +公式解析/求值/归一化/结算顺序/非法回退、重采样端到端(新轴、5 点、插值精确、名称尾缀、 +面板几何、默认勾选最小间隔)、`SignalDataInterval`、频率格式化、公式影响自适应量程。 +⚠️ 链接 Build/Debug/*.o(含 LTO)**必须加 `-flto -flto-partition=one`**,否则 `binds_to_current_def_p` ICE。 +⚠️ 探针里必须先 `Tab().SetVisible({0,0}, true)` 再开面板:新加载信号默认不勾选, +否则 `SetLaneResample` 找不到"第一个可见信号"、面板布局为空。 +