diff --git a/docs/superpowers/plans/2026-06-16-vtk-3d-three-column-refactor.md b/docs/superpowers/plans/2026-06-16-vtk-3d-three-column-refactor.md
new file mode 100644
index 0000000..34fd5b1
--- /dev/null
+++ b/docs/superpowers/plans/2026-06-16-vtk-3d-three-column-refactor.md
@@ -0,0 +1,869 @@
+# VTK 三栏结构重构 Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 把 VTK 工作台的"旧二维/三维切换 + 三浮层"过渡态,重构成需求 A1 的「三个子列表栏」(三维数据集 / 二维数据集 / 三维分析),内嵌在唯一的中央「VTK视图」左侧,并接通已有渲染/切片能力;同时给 VTK视图 + 数据详情 加全屏按钮。
+
+**Architecture:** 三栏抽成独立 widget(`src/app/panels/columns/`),各自只发信号、不依赖控制器;`ColumnDrawer` 作为 `vtkWidget` 在 HBox 中的**左侧兄弟控件**(非 GL 浮层,规避原生 GL 浮层 z 序/圆角伪影),可折叠。`main.cpp::buildWorkbench` 删三浮层+分段切换,改挂三栏并把信号接到既有 `VtkSceneController`/`InteractionManager`/`DatasetDetailController`。数据集列表由 `WorkbenchNavController` 取 `DsRow`、按 `I3dSceneRepository::dimensionOf` 过滤后分发到三栏。
+
+**Tech Stack:** C++17, Qt6 Widgets, VTK9, Qt-ADS。构建 `build.bat`(见"构建/验证铁律"),测试 GoogleTest/CTest。
+
+---
+
+## ⚠️ 构建/验证铁律(每个 Task 都遵守)
+
+- 构建:从 Git Bash 调 `cmd.exe /c "chcp 65001 >nul && cd /d D:\Git\lanbingtech\geopro && .\build.bat test"`。命令:`app`/`test`/`rebuild`(全量)/`configure`。
+- **ninja 偶发漏编** → 改头/布局后用 `build.bat rebuild`;验 exe 新鲜:`stat -c '%y' build/release/src/app/geopro_desktop.exe`。
+- **切勿 `rm -rf build/release`**(vcpkg 重编依赖极慢)。
+- **Claude 工具跑 build 偶被 Start-Process 钩子劫持静默不跑** → **所有"用户实测"步骤必须由用户在其终端 `build.bat rebuild` 跑并目视**。Claude 不能 GUI 测 VTK 交互。
+- 纯逻辑(如维度过滤)抽函数 + GoogleTest 单测;UI/交互靠 build 绿 + 用户实测清单。
+
+## 测试方式约定(本计划特例,覆盖默认 TDD-everywhere)
+
+- **逻辑步骤**(标 `[逻辑]`):先写失败测试 → 跑红 → 实现 → 跑绿 → 提交。
+- **UI 步骤**(标 `[UI]`):实现 → `build.bat rebuild` 编绿 → **用户实测清单** → 提交。Claude 不声称"已验证"交互,只验证编译通过 + 代码读校。
+
+---
+
+## File Structure
+
+**新建:**
+- `src/app/panels/columns/ColumnDrawer.hpp/.cpp` — 抽屉容器(QTabWidget 三 tab + 折叠开关)。
+- `src/app/panels/columns/Column3DDataset.hpp/.cpp` — 三维数据集栏(4 工具条栏位 + 3D 数据集树)。
+- `src/app/panels/columns/Column2DDataset.hpp/.cpp` — 二维数据集栏(地图/2D视图控件 + 2D 数据集树)。
+- `src/app/panels/columns/Column3DAnalysis.hpp/.cpp` — 三维分析栏(对象→三维体→切片 树 + 两个右键菜单)。
+- `src/app/DatasetDimension.hpp/.cpp` — 纯函数 `splitByDimension(...)`(可单测)。
+- `tests/app/test_dataset_dimension.cpp` — 维度过滤单测。
+
+**修改:**
+- `src/app/main.cpp` — `buildWorkbench`:删三浮层(393-556)/分段切换(380-389,832-843)/showLayerPanel(804-827)/相关 connect;改挂 ColumnDrawer;接信号;rename vtkDock;bump dockState 版本;接维度过滤;全屏按钮。
+- `src/app/Glyphs.hpp/.cpp` — 加 `Glyph::Fullscreen` + SVG。
+- `src/app/CMakeLists.txt` — 加新源文件。
+- `tests/CMakeLists.txt`(或对应)— 加 test_dataset_dimension。
+
+---
+
+## Task 1: 加 Fullscreen 图标 [UI]
+
+**Files:**
+- Modify: `src/app/Glyphs.hpp:15-35`(Glyph 枚举)
+- Modify: `src/app/Glyphs.cpp`(SVG path 映射,参照现有 case)
+
+- [ ] **Step 1: 在 Glyph 枚举加 Fullscreen**
+
+`src/app/Glyphs.hpp`,在 `Collapse,` 之后加:
+```cpp
+ Collapse, // 折叠(双箭头)
+ Fullscreen, // 全屏 / 最大化
+```
+
+- [ ] **Step 2: 在 Glyphs.cpp 的 svg 映射加 case**
+
+找到 `makeGlyph`/svg path 的 `switch`(参照 `case Glyph::Collapse:`),加:
+```cpp
+ case Glyph::Fullscreen:
+ return QStringLiteral("");
+```
+(若已有 restore/缩小语义图标可复用,但需一个独立 Fullscreen 项。)
+
+- [ ] **Step 3: 编译**
+
+Run: `cmd.exe /c "chcp 65001 >nul && cd /d D:\Git\lanbingtech\geopro && .\build.bat app"`
+Expected: 编译通过(exe 重新生成)。
+
+- [ ] **Step 4: 提交**
+```bash
+git add src/app/Glyphs.hpp src/app/Glyphs.cpp
+git commit -m "feat(vtk): 加 Glyph::Fullscreen 图标(三栏重构全屏按钮用)"
+```
+
+---
+
+## Task 2: 维度过滤纯函数 [逻辑]
+
+把"DsRow 列表 → 按维度分三组"抽成可单测纯函数。`dimensionOf` 已在 `I3dSceneRepository`(接口),这里做的是**列表分流**。
+
+**Files:**
+- Create: `src/app/DatasetDimension.hpp`
+- Create: `src/app/DatasetDimension.cpp`
+- Test: `tests/app/test_dataset_dimension.cpp`
+- Modify: `src/app/CMakeLists.txt`、`tests/CMakeLists.txt`
+
+- [ ] **Step 1: 写失败测试**
+
+`tests/app/test_dataset_dimension.cpp`:
+```cpp
+#include
+#include "app/DatasetDimension.hpp"
+#include "data/repo/RepoTypes.hpp"
+
+using geopro::data::DsRow;
+using geopro::app::splitByDimension;
+using geopro::app::DimBuckets;
+
+static DsRow row(const char* id, const char* ddCode) {
+ DsRow r; r.id = id; r.ddCode = ddCode; return r;
+}
+
+TEST(DatasetDimension, SplitsByDdCode) {
+ std::vector in{
+ row("a", "dd_section"), // 3D
+ row("b", "dd_voxel"), // 3D
+ row("c", "dd_trajectory_data"), // 2D
+ row("d", "dd_slice"), // Analysis
+ row("e", "dd_unknownxyz"), // Other → 不入任何栏
+ };
+ DimBuckets b = splitByDimension(in);
+ ASSERT_EQ(b.dim3D.size(), 2u);
+ EXPECT_EQ(b.dim3D[0].id, "a");
+ EXPECT_EQ(b.dim3D[1].id, "b");
+ ASSERT_EQ(b.dim2D.size(), 1u);
+ EXPECT_EQ(b.dim2D[0].id, "c");
+ ASSERT_EQ(b.analysis.size(), 1u);
+ EXPECT_EQ(b.analysis[0].id, "d");
+}
+
+TEST(DatasetDimension, EmptyInput) {
+ DimBuckets b = splitByDimension({});
+ EXPECT_TRUE(b.dim3D.empty());
+ EXPECT_TRUE(b.dim2D.empty());
+ EXPECT_TRUE(b.analysis.empty());
+}
+```
+
+- [ ] **Step 2: 跑测试确认失败**
+
+Run: `cmd.exe /c "chcp 65001 >nul && cd /d D:\Git\lanbingtech\geopro && .\build.bat test"`
+Expected: FAIL(`DatasetDimension.hpp` 不存在 / 链接失败)。
+
+- [ ] **Step 3: 写实现**
+
+`src/app/DatasetDimension.hpp`:
+```cpp
+#pragma once
+#include
+#include "data/repo/RepoTypes.hpp"
+
+namespace geopro::app {
+
+struct DimBuckets {
+ std::vector dim3D;
+ std::vector dim2D;
+ std::vector analysis;
+};
+
+// 按 ddCode 把 ds 分流到 三维数据集 / 二维数据集 / 三维分析 三栏。
+// Other 维度不入任何栏(保留 parentId 顺序,调用方可直接喂 populateDatasetList)。
+DimBuckets splitByDimension(const std::vector& rows);
+
+} // namespace geopro::app
+```
+
+`src/app/DatasetDimension.cpp`:
+```cpp
+#include "app/DatasetDimension.hpp"
+
+namespace geopro::app {
+
+namespace {
+// 与 LocalSample3dRepository::dimensionOf 同一映射(spec §6.1)。
+// 抽到此处以便纯函数单测;将来后端返 dimension 字段时此函数改读字段即可。
+enum class Dim { D3, D2, Analysis, Other };
+Dim dimOf(const std::string& c) {
+ if (c == "dd_voxel" || c == "dd_Structual3D" || c == "dd_Property3D" ||
+ c == "dd_section" || c == "dd_inversion_data")
+ return Dim::D3;
+ if (c == "dd_slice") return Dim::Analysis;
+ if (c == "dd_trajectory_data") return Dim::D2;
+ return Dim::Other;
+}
+} // namespace
+
+DimBuckets splitByDimension(const std::vector& rows) {
+ DimBuckets b;
+ for (const auto& r : rows) {
+ switch (dimOf(r.ddCode)) {
+ case Dim::D3: b.dim3D.push_back(r); break;
+ case Dim::D2: b.dim2D.push_back(r); break;
+ case Dim::Analysis: b.analysis.push_back(r); break;
+ case Dim::Other: break;
+ }
+ }
+ return b;
+}
+
+} // namespace geopro::app
+```
+
+> 注:`dimensionOf` 同时存在于 `LocalSample3dRepository`(渲染编排用)。此处复制映射是**有意**——纯函数便于单测、且与"将来后端返 dimension 字段"解耦。后续若收敛为单一真源,再让本函数调用注入的 repo。落地时若 reviewer 要求单一真源,可改签名 `splitByDimension(rows, const I3dSceneRepository&)`,本期按纯函数。
+
+- [ ] **Step 4: 注册到 CMake**
+
+`src/app/CMakeLists.txt`:把 `DatasetDimension.cpp` 加入 app 目标源列表(仿照同目录 .cpp 的加法)。
+`tests/CMakeLists.txt`(或 tests/app):把 `test_dataset_dimension.cpp` 加入测试目标(仿照 `test_3d_repo` 的注册)。
+
+- [ ] **Step 5: 跑测试确认通过**
+
+Run: `cmd.exe /c "chcp 65001 >nul && cd /d D:\Git\lanbingtech\geopro && .\build.bat test"`
+Expected: `DatasetDimension.*` 2 项 PASS;总数 ≥ 223/223。
+
+- [ ] **Step 6: 提交**
+```bash
+git add src/app/DatasetDimension.hpp src/app/DatasetDimension.cpp tests/app/test_dataset_dimension.cpp src/app/CMakeLists.txt tests/CMakeLists.txt
+git commit -m "feat(vtk): 维度过滤纯函数 splitByDimension + 单测"
+```
+
+---
+
+## Task 3: 三维数据集栏 widget [UI]
+
+独立 widget:4 工具条栏位(坐标轴设置 / 水平垂直比例 / 快捷视图 / 缩放)+ 数据集树。只发信号。控件创建可**搬运** `main.cpp:433-516`(axisBar)的 combo/slider/button 构造与样式,重排成 4 分组(参照原型 `docs/superpowers/mockups/2026-06-16-three-column-layout.html` 的 `toolbar3D`)。
+
+**Files:**
+- Create: `src/app/panels/columns/Column3DDataset.hpp/.cpp`
+- Modify: `src/app/CMakeLists.txt`
+
+- [ ] **Step 1: 写头文件(信号 API)**
+
+`src/app/panels/columns/Column3DDataset.hpp`:
+```cpp
+#pragma once
+#include
+#include
+#include
+#include "controller/I3dSceneView.hpp" // AxesMode/AxesUnit/ViewDir
+#include "data/repo/RepoTypes.hpp"
+
+class QTreeWidget;
+
+namespace geopro::app {
+
+// 三维数据集栏:坐标轴设置 + 水平/垂直比例 + 快捷视图 + 缩放 + 3D 数据集列表。
+class Column3DDataset : public QWidget {
+ Q_OBJECT
+public:
+ explicit Column3DDataset(QWidget* parent = nullptr);
+ // 用 3D 维度的 ds 填充列表(调用 populateDatasetList)。
+ void setDatasets(const std::vector& rows);
+
+signals:
+ void axesModeChanged(geopro::controller::AxesMode mode);
+ void axesUnitChanged(geopro::controller::AxesUnit unit);
+ void verticalExaggerationChanged(double ve);
+ void viewRequested(geopro::controller::ViewDir dir);
+ void zoomInRequested();
+ void zoomOutRequested();
+ void fitRequested();
+ void oPointClicked(); // O点位置按钮(本期弹框留 stub)
+ void fontClicked(); // 字体按钮(本期 stub)
+ void checkedDatasetsChanged(const QStringList& dsIds); // 列表勾选变化
+
+private:
+ QTreeWidget* list_ = nullptr;
+};
+
+} // namespace geopro::app
+```
+
+- [ ] **Step 2: 写实现(构造 4 分组 + 列表)**
+
+`src/app/panels/columns/Column3DDataset.cpp`:用 `QVBoxLayout` 堆 4 个分组 `QGroupBox`/`QFrame`(标题 + 表单行)+ `QTreeWidget` 列表。控件构造照搬 `main.cpp:464-500`(axesModeCombo/axesUnitCombo/veSlider/btnFront..btnFit),样式用 `applyTokenizedStyleSheet` 照搬 `main.cpp:437-457`。各控件 `connect` 到本类 `emit ...`。要点(完整骨架):
+```cpp
+#include "app/panels/columns/Column3DDataset.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "app/Theme.hpp"
+#include "app/panels/DatasetListPanel.hpp" // populateDatasetList
+
+using geopro::controller::AxesMode;
+using geopro::controller::AxesUnit;
+using geopro::controller::ViewDir;
+
+namespace geopro::app {
+
+Column3DDataset::Column3DDataset(QWidget* parent) : QWidget(parent) {
+ auto* root = new QVBoxLayout(this);
+ root->setContentsMargins(space::kMd, space::kMd, space::kMd, space::kMd);
+ root->setSpacing(space::kMd);
+
+ // —— 坐标轴设置 组:显示方式▾ / O点位置(按钮) / 刻度▾ / 字体(按钮) ——
+ {
+ auto* form = new QFormLayout();
+ auto* mode = new QComboBox();
+ mode->addItem(QStringLiteral("标准"), static_cast(AxesMode::Standard));
+ mode->addItem(QStringLiteral("三维立体"), static_cast(AxesMode::Stereo));
+ mode->addItem(QStringLiteral("不显示"), static_cast(AxesMode::None));
+ connect(mode, qOverload(&QComboBox::currentIndexChanged), this,
+ [this, mode](int){ emit axesModeChanged(static_cast(mode->currentData().toInt())); });
+ auto* oPoint = new QPushButton(QStringLiteral("设置…"));
+ connect(oPoint, &QPushButton::clicked, this, &Column3DDataset::oPointClicked);
+ auto* unit = new QComboBox();
+ unit->addItem(QStringLiteral("无刻度"), static_cast(AxesUnit::None));
+ unit->addItem(QStringLiteral("米"), static_cast(AxesUnit::Meter));
+ unit->addItem(QStringLiteral("英尺"), static_cast(AxesUnit::Feet));
+ unit->addItem(QStringLiteral("经纬度"), static_cast(AxesUnit::LatLon));
+ unit->setCurrentIndex(1);
+ connect(unit, qOverload(&QComboBox::currentIndexChanged), this,
+ [this, unit](int){ emit axesUnitChanged(static_cast(unit->currentData().toInt())); });
+ auto* font = new QPushButton(QStringLiteral("设置…"));
+ connect(font, &QPushButton::clicked, this, &Column3DDataset::fontClicked);
+ form->addRow(QStringLiteral("显示方式"), mode);
+ form->addRow(QStringLiteral("O点位置"), oPoint);
+ form->addRow(QStringLiteral("刻度"), unit);
+ form->addRow(QStringLiteral("字体"), font);
+ root->addWidget(new QLabel(QStringLiteral("坐标轴设置")));
+ root->addLayout(form);
+ }
+ // —— 水平/垂直比例 组:单个滑块 + 数值 ——
+ {
+ auto* row = new QHBoxLayout();
+ auto* slider = new QSlider(Qt::Horizontal);
+ slider->setMinimum(1); slider->setMaximum(10); slider->setValue(2);
+ auto* val = new QLabel(QStringLiteral("2.0×"));
+ connect(slider, &QSlider::valueChanged, this, [this, val](int v){
+ val->setText(QStringLiteral("%1.0×").arg(v));
+ emit verticalExaggerationChanged(static_cast(v));
+ });
+ row->addWidget(slider, 1); row->addWidget(val);
+ root->addWidget(new QLabel(QStringLiteral("水平/垂直比例")));
+ root->addLayout(row);
+ }
+ // —— 快捷视图 组:前/后/左/右/上/下 ——
+ {
+ auto* row = new QHBoxLayout();
+ struct V { const char* t; ViewDir d; };
+ for (V v : { V{"前",ViewDir::Front}, V{"后",ViewDir::Back}, V{"左",ViewDir::Left},
+ V{"右",ViewDir::Right}, V{"上",ViewDir::Top}, V{"下",ViewDir::Bottom} }) {
+ auto* b = new QPushButton(QString::fromUtf8(v.t));
+ ViewDir d = v.d;
+ connect(b, &QPushButton::clicked, this, [this, d]{ emit viewRequested(d); });
+ row->addWidget(b);
+ }
+ root->addWidget(new QLabel(QStringLiteral("快捷视图")));
+ root->addLayout(row);
+ }
+ // —— 缩放 组:放大/缩小/适配 ——
+ {
+ auto* row = new QHBoxLayout();
+ auto* in = new QPushButton(QStringLiteral("放大"));
+ auto* out = new QPushButton(QStringLiteral("缩小"));
+ auto* fit = new QPushButton(QStringLiteral("适配"));
+ connect(in, &QPushButton::clicked, this, &Column3DDataset::zoomInRequested);
+ connect(out, &QPushButton::clicked, this, &Column3DDataset::zoomOutRequested);
+ connect(fit, &QPushButton::clicked, this, &Column3DDataset::fitRequested);
+ row->addWidget(in); row->addWidget(out); row->addWidget(fit);
+ root->addWidget(new QLabel(QStringLiteral("缩放")));
+ root->addLayout(row);
+ }
+ // —— 数据集列表(3D 维度)——
+ list_ = new QTreeWidget();
+ list_->setHeaderHidden(true);
+ list_->setRootIsDecorated(true);
+ applyDatasetCardDelegate(list_);
+ connect(list_, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem*, int){
+ QStringList ids;
+ for (QTreeWidgetItemIterator it(list_); *it; ++it)
+ if ((*it)->checkState(0) == Qt::Checked)
+ ids << (*it)->data(0, /*kDsIdRole*/ Qt::UserRole + 1).toString();
+ emit checkedDatasetsChanged(ids);
+ });
+ root->addWidget(list_, 1);
+}
+
+void Column3DDataset::setDatasets(const std::vector& rows) {
+ populateDatasetList(list_, rows, /*append=*/false);
+ // 列表项需可勾选:populateDatasetList 后给每项加 Qt::ItemIsUserCheckable + Unchecked。
+ for (QTreeWidgetItemIterator it(list_); *it; ++it) {
+ (*it)->setFlags((*it)->flags() | Qt::ItemIsUserCheckable);
+ if ((*it)->checkState(0) == Qt::Unchecked || (*it)->checkState(0) == Qt::Checked) {}
+ else (*it)->setCheckState(0, Qt::Unchecked);
+ }
+}
+
+} // namespace geopro::app
+```
+> 注 1:`kDsIdRole` 的真实值见 `src/app/panels/DatasetListPanel.cpp`(`Qt::UserRole+? `)。落地时 include 该常量或用其公开定义,勿硬编码 `UserRole+1`——读 DatasetListPanel.hpp/.cpp 取真实 role 常量。
+> 注 2:`populateDatasetList` 生成的项默认不可勾选;本栏需勾选渲染,故 setDatasets 后补 `ItemIsUserCheckable` + `Unchecked`(见上)。
+> 注 3:分组标题/表单样式照原型;可用 `applyTokenizedStyleSheet` 套深色令牌(搬 `main.cpp:437-457`)。
+
+- [ ] **Step 3: 注册 CMake + 编译**
+
+`src/app/CMakeLists.txt` 加 `panels/columns/Column3DDataset.cpp`。
+Run: `cmd.exe /c "chcp 65001 >nul && cd /d D:\Git\lanbingtech\geopro && .\build.bat app"`
+Expected: 编译通过。
+
+- [ ] **Step 4: 提交**
+```bash
+git add src/app/panels/columns/Column3DDataset.hpp src/app/panels/columns/Column3DDataset.cpp src/app/CMakeLists.txt
+git commit -m "feat(vtk): 三维数据集栏 widget(4工具条栏位+3D数据集列表,只发信号)"
+```
+
+---
+
+## Task 4: 二维数据集栏 widget [UI]
+
+**Files:**
+- Create: `src/app/panels/columns/Column2DDataset.hpp/.cpp`
+- Modify: `src/app/CMakeLists.txt`
+
+- [ ] **Step 1: 写头文件**
+
+`Column2DDataset.hpp`:
+```cpp
+#pragma once
+#include
+#include
+#include
+#include "data/repo/RepoTypes.hpp"
+class QTreeWidget;
+namespace geopro::app {
+class Column2DDataset : public QWidget {
+ Q_OBJECT
+public:
+ explicit Column2DDataset(QWidget* parent = nullptr);
+ void setDatasets(const std::vector& rows);
+signals:
+ void basemapChanged(int index); // 0 天地图 / 1 Google / 2 隐藏
+ void view2DModeChanged(int index); // 0 关闭 /1 Z=0 /2 顶部 /3 底部 /4 自定义
+ void customZChanged(double z); // 世界绝对高程(米),向上为正
+ void checkedDatasetsChanged(const QStringList& dsIds);
+private:
+ QTreeWidget* list_ = nullptr;
+};
+}
+```
+
+- [ ] **Step 2: 写实现**
+
+`Column2DDataset.cpp`:地图 combo(天地图/Google Map/隐藏)→ `basemapChanged`;2D视图 combo(关闭/Z=0/顶部/底部/自定义)→ `view2DModeChanged`,选"自定义"时显一个 `QDoubleSpinBox`(范围 ±1e6,后缀 " m")→ `customZChanged`;`QTreeWidget` 列表同 Task3(可勾选 + setDatasets 用 populateDatasetList)。骨架同 Column3DDataset 模式(QFormLayout 两组 + 列表),此处不赘述控件 connect(与 Task3 同形)。自定义 Z 输入框默认 `setVisible(false)`,在 `view2DModeChanged` 槽里 `zEdit->setVisible(index==4)`。
+
+- [ ] **Step 3: 注册 CMake + 编译**
+
+`src/app/CMakeLists.txt` 加 `panels/columns/Column2DDataset.cpp`。
+Run: `cmd.exe /c "chcp 65001 >nul && cd /d D:\Git\lanbingtech\geopro && .\build.bat app"`
+Expected: 编译通过。
+
+- [ ] **Step 4: 提交**
+```bash
+git add src/app/panels/columns/Column2DDataset.hpp src/app/panels/columns/Column2DDataset.cpp src/app/CMakeLists.txt
+git commit -m "feat(vtk): 二维数据集栏 widget(地图/2D视图+自定义Z输入+2D列表)"
+```
+
+---
+
+## Task 5: 三维分析栏 widget + 两个右键菜单 [UI]
+
+**Files:**
+- Create: `src/app/panels/columns/Column3DAnalysis.hpp/.cpp`
+- Modify: `src/app/CMakeLists.txt`
+
+- [ ] **Step 1: 写头文件**
+
+`Column3DAnalysis.hpp`:
+```cpp
+#pragma once
+#include
+#include
+#include
+#include "data/repo/RepoTypes.hpp"
+#include "render/interact/SlicePlaneMath.hpp" // SliceAxis
+class QTreeWidget;
+class QTreeWidgetItem;
+namespace geopro::app {
+// 三维分析栏:对象→三维体模型→切片 树 + 三维体/切片两套右键菜单。
+class Column3DAnalysis : public QWidget {
+ Q_OBJECT
+public:
+ explicit Column3DAnalysis(QWidget* parent = nullptr);
+ void setDatasets(const std::vector& rows); // Analysis 维度(三维体/切片)
+signals:
+ // 三维体右键:切片▸(上下/前后/左右/任意)
+ void sliceRequested(geopro::render::interact::SliceAxis axis);
+ void colorScaleRequested(const QString& dsId); // 三维体&切片(本期 stub)
+ void visibilityToggled(const QString& dsId); // 显示/隐藏
+ void detailRequested(const QString& dsId, const QString& ddCode, const QString& name);
+ // 切片右键(本期 stub,菜单可见但发信号给上层提示"待实现")
+ void sliceSaveRequested(const QString& dsId);
+ void sliceSaveAsRequested(const QString& dsId);
+ void sliceExportRequested(const QString& dsId);
+ void sliceDeleteRequested(const QString& dsId);
+ void checkedItemsChanged(const QStringList& dsIds);
+private:
+ void onContextMenu(const QPoint& pos);
+ QTreeWidget* tree_ = nullptr;
+};
+}
+```
+
+- [ ] **Step 2: 写实现(树 + 右键分派)**
+
+`Column3DAnalysis.cpp`:`tree_` 设 `setContextMenuPolicy(Qt::CustomContextMenu)`,connect `customContextMenuRequested` → `onContextMenu`。节点类型用 `item->data(0, role)` 区分"三维体" vs "切片"(建树时按 ddCode:`dd_voxel/dd_Structual3D/dd_Property3D/dd_section` 为三维体;`dd_slice` 为切片)。右键分派(核心):
+```cpp
+void Column3DAnalysis::onContextMenu(const QPoint& pos) {
+ QTreeWidgetItem* it = tree_->itemAt(pos);
+ if (!it) return;
+ const QString dsId = it->data(0, kDsIdRole).toString();
+ const QString ddCode = it->data(0, kDsDdCodeRole).toString();
+ const QString name = it->data(0, kDsNameRole).toString();
+ const bool isSlice = (ddCode == QStringLiteral("dd_slice"));
+ QMenu menu(this);
+ if (!isSlice) {
+ // 三维体数据集:切片▸(上下/前后/左右/任意) / 色阶 / 显示·隐藏 / 数据详情
+ QMenu* sub = menu.addMenu(QStringLiteral("切片"));
+ using SA = geopro::render::interact::SliceAxis;
+ sub->addAction(QStringLiteral("上下"), this, [this]{ emit sliceRequested(SA::UpDown); });
+ sub->addAction(QStringLiteral("前后"), this, [this]{ emit sliceRequested(SA::FrontBack); });
+ sub->addAction(QStringLiteral("左右"), this, [this]{ emit sliceRequested(SA::LeftRight); });
+ sub->addAction(QStringLiteral("任意"), this, [this]{ emit sliceRequested(SA::Oblique); });
+ menu.addAction(QStringLiteral("色阶"), this, [this, dsId]{ emit colorScaleRequested(dsId); });
+ menu.addAction(QStringLiteral("显示 / 隐藏"), this, [this, dsId]{ emit visibilityToggled(dsId); });
+ menu.addAction(QStringLiteral("数据详情"), this, [this, dsId, ddCode, name]{ emit detailRequested(dsId, ddCode, name); });
+ } else {
+ // 切片数据集:保存/保存为/导出/删除 — 色阶/显示·隐藏/数据详情
+ menu.addAction(QStringLiteral("保存"), this, [this, dsId]{ emit sliceSaveRequested(dsId); });
+ menu.addAction(QStringLiteral("保存为"), this, [this, dsId]{ emit sliceSaveAsRequested(dsId); });
+ menu.addAction(QStringLiteral("导出"), this, [this, dsId]{ emit sliceExportRequested(dsId); });
+ menu.addAction(QStringLiteral("删除"), this, [this, dsId]{ emit sliceDeleteRequested(dsId); });
+ menu.addSeparator();
+ menu.addAction(QStringLiteral("色阶"), this, [this, dsId]{ emit colorScaleRequested(dsId); });
+ menu.addAction(QStringLiteral("显示 / 隐藏"), this, [this, dsId]{ emit visibilityToggled(dsId); });
+ menu.addAction(QStringLiteral("数据详情"), this, [this, dsId, ddCode, name]{ emit detailRequested(dsId, ddCode, name); });
+ }
+ menu.exec(tree_->viewport()->mapToGlobal(pos));
+}
+```
+> `kDsIdRole/kDsDdCodeRole/kDsNameRole`:用 DatasetListPanel 的公开 role 常量(读 DatasetListPanel.hpp)。树构建:用 `populateDatasetList(tree_, analysisRows, false)` 起步(它已按 parentId 建树:切片挂三维体下),再补可勾选标志(同 Task3 注 2)。
+
+- [ ] **Step 3: 注册 CMake + 编译**
+
+`src/app/CMakeLists.txt` 加 `panels/columns/Column3DAnalysis.cpp`。
+Run: `cmd.exe /c "chcp 65001 >nul && cd /d D:\Git\lanbingtech\geopro && .\build.bat app"`
+Expected: 编译通过。
+
+- [ ] **Step 4: 提交**
+```bash
+git add src/app/panels/columns/Column3DAnalysis.hpp src/app/panels/columns/Column3DAnalysis.cpp src/app/CMakeLists.txt
+git commit -m "feat(vtk): 三维分析栏 widget(对象→三维体→切片树+两套右键菜单)"
+```
+
+---
+
+## Task 6: 抽屉容器 ColumnDrawer [UI]
+
+**Files:**
+- Create: `src/app/panels/columns/ColumnDrawer.hpp/.cpp`
+- Modify: `src/app/CMakeLists.txt`
+
+- [ ] **Step 1: 写头文件**
+
+`ColumnDrawer.hpp`:
+```cpp
+#pragma once
+#include
+namespace geopro::app {
+class Column3DDataset; class Column2DDataset; class Column3DAnalysis;
+// VTK视图左侧内嵌抽屉:三 tab(三维数据集/二维数据集/三维分析) + 折叠开关。
+class ColumnDrawer : public QWidget {
+ Q_OBJECT
+public:
+ explicit ColumnDrawer(QWidget* parent = nullptr);
+ Column3DDataset* col3D() const { return col3D_; }
+ Column2DDataset* col2D() const { return col2D_; }
+ Column3DAnalysis* colAnalysis() const { return colAnalysis_; }
+public slots:
+ void toggleCollapsed(); // 折叠/展开(宽度切换)
+private:
+ Column3DDataset* col3D_ = nullptr;
+ Column2DDataset* col2D_ = nullptr;
+ Column3DAnalysis* colAnalysis_ = nullptr;
+ QWidget* body_ = nullptr; // QTabWidget 容器,折叠时隐藏
+ bool collapsed_ = false;
+};
+}
+```
+
+- [ ] **Step 2: 写实现**
+
+`QTabWidget` 三页(三维数据集/二维数据集/三维分析)放入 `body_`;旁边一个细长折叠按钮(◀/▶,调 `toggleCollapsed`)。`toggleCollapsed`:`collapsed_ = !collapsed_; body_->setVisible(!collapsed_);` 并切按钮箭头。固定展开宽度约 300(`setFixedWidth` 或 `setMaximumWidth`,折叠时设 0/隐藏 body)。
+
+- [ ] **Step 3: 注册 CMake + 编译**
+
+Run: `cmd.exe /c "chcp 65001 >nul && cd /d D:\Git\lanbingtech\geopro && .\build.bat app"`
+Expected: 编译通过。
+
+- [ ] **Step 4: 提交**
+```bash
+git add src/app/panels/columns/ColumnDrawer.hpp src/app/panels/columns/ColumnDrawer.cpp src/app/CMakeLists.txt
+git commit -m "feat(vtk): ColumnDrawer 抽屉容器(三tab+折叠)"
+```
+
+---
+
+## Task 7: main.cpp 装配——删三浮层/切换,挂抽屉,接信号,改名 [UI]
+
+这是核心整合。**一次性**替换,因 axisBar/sliceBar/layerPanel 的 connect 互相牵连,无法逐控件保持编译。改完一次编绿。
+
+**Files:**
+- Modify: `src/app/main.cpp`(多处,见下)
+
+- [ ] **Step 1: 删旧 UI 构造**
+
+删除:
+- `layerPanel` 块(393-429)、`axisBar` 块(433-516)、`RightTopAnchor`(520)、`sliceBar` 块(525-556)、`BottomLeftAnchor`(556)。
+- 分段切换:`buildSegmentedHeader`/`viewHeader`/`act2D`/`act3D`(380-389)改为**简单标题头**(见 Step 3)。
+- `showLayerPanel` lambda(804-827)及其所有调用。
+- `updateSliceButtons`(559-569)、`addSlice`(572-575)、sliceBar 按钮 connect(576-590)。
+- 旧 connect:layer checkboxes(846-851)、axisBar 控件(857-889)、act2D/act3D(832-843)。
+- 保留 `interactionMgr` 创建(309-321)、`emptyState`、`sceneCtrl`、`vtkWidget`。
+
+- [ ] **Step 2: 建 ColumnDrawer + 改 centerWidget 布局为 [抽屉 | GL]**
+
+在 `centerWidget` 构造处(374 一带)改为:顶部一个标题头(Step 3),下面一个 `QHBoxLayout` 装 `drawer` + `vtkWidget`:
+```cpp
+#include "app/panels/columns/ColumnDrawer.hpp"
+#include "app/panels/columns/Column3DDataset.hpp"
+#include "app/panels/columns/Column2DDataset.hpp"
+#include "app/panels/columns/Column3DAnalysis.hpp"
+// ...
+auto* drawer = new geopro::app::ColumnDrawer(centerWidget);
+auto* viewRow = new QHBoxLayout();
+viewRow->setContentsMargins(0,0,0,0); viewRow->setSpacing(0);
+viewRow->addWidget(drawer); // 左侧抽屉
+viewRow->addWidget(vtkWidget, 1); // 右侧 GL 画布
+// centerLayout: [标题头] + [viewRow]
+centerLayout->addWidget(viewHeader); // Step 3 的新标题头
+centerLayout->addLayout(viewRow, 1);
+```
+> 设计:抽屉是 vtkWidget 的**布局兄弟**(非 GL 子浮层),规避 `main.cpp:397-399` 注释提到的原生 GL 浮层圆角/底色伪影。视觉等同原型(栏在左、画布在右)。
+
+- [ ] **Step 3: 新标题头(含全屏按钮,Task 8 接线)**
+
+替换分段头为 `buildPanelHeader`:
+```cpp
+auto* viewHeader = geopro::app::buildPanelHeader(
+ geopro::app::Glyph::Map, QStringLiteral("VTK视图"),
+ {{geopro::app::Glyph::Fullscreen, QStringLiteral("全屏")}});
+```
+(全屏按钮的 connect 在 Task 8。)
+
+- [ ] **Step 4: 接三维数据集栏信号 → VtkSceneController**
+```cpp
+auto* c3 = drawer->col3D();
+QObject::connect(c3, &geopro::app::Column3DDataset::axesModeChanged, sceneCtrl, &VtkSceneController::setAxesMode);
+QObject::connect(c3, &geopro::app::Column3DDataset::axesUnitChanged, sceneCtrl, &VtkSceneController::setAxesUnit);
+QObject::connect(c3, &geopro::app::Column3DDataset::verticalExaggerationChanged, sceneCtrl, &VtkSceneController::setVerticalExaggeration);
+QObject::connect(c3, &geopro::app::Column3DDataset::viewRequested, sceneCtrl, &VtkSceneController::applyView);
+QObject::connect(c3, &geopro::app::Column3DDataset::zoomInRequested, sceneCtrl, &VtkSceneController::zoomIn);
+QObject::connect(c3, &geopro::app::Column3DDataset::zoomOutRequested, sceneCtrl, &VtkSceneController::zoomOut);
+QObject::connect(c3, &geopro::app::Column3DDataset::fitRequested, sceneCtrl, &VtkSceneController::fit);
+// O点位置/字体本期 stub:connect 到一个提示(可空 lambda)。
+```
+> 类型匹配:`Column3DDataset` 的枚举即 `geopro::controller::AxesMode/AxesUnit/ViewDir`(同 I3dSceneView.hpp),与 `setAxesMode/setAxesUnit/applyView` 形参一致,可直接连。
+
+- [ ] **Step 5: 接三维分析栏「切片」→ InteractionManager**
+```cpp
+auto* ca = drawer->colAnalysis();
+QObject::connect(ca, &geopro::app::Column3DAnalysis::sliceRequested, vtkWidget,
+ [interactionMgr](geopro::render::interact::SliceAxis axis){
+ interactionMgr->addSlice(axis);
+ });
+QObject::connect(ca, &geopro::app::Column3DAnalysis::detailRequested, &detailCtrl,
+ [&detailCtrl](const QString& dsId, const QString& ddCode, const QString& name){
+ detailCtrl.openDataset(dsId, ddCode, name);
+ });
+// colorScale/visibility/slice CRUD 本期 stub:connect 到提示 lambda(如 statusBar 显"待实现")。
+```
+
+- [ ] **Step 6: 编译(维度过滤接线在 Task 9,此处先空列表)**
+
+Run: `cmd.exe /c "chcp 65001 >nul && cd /d D:\Git\lanbingtech\geopro && .\build.bat rebuild"`
+Expected: 全量编译通过,exe 刷新。
+
+- [ ] **Step 7: 用户实测清单**(用户在其终端跑)
+ - [ ] app 启动,中央改名「VTK视图」,左侧出现三 tab 抽屉。
+ - [ ] 旧「二维地图/三维视图」分段按钮已消失;左上/右上/左下三浮层消失。
+ - [ ] 抽屉折叠开关:点 ◀ 收起、画布变宽;点 ▶ 展开。
+ - [ ] 三维数据集栏工具条:坐标轴下拉/比例滑块/快捷视图 6 钮/缩放 3 钮可点(功能接通后续 Task 9 验,但点击不崩)。
+ - [ ] 三维分析栏右键三维体 → 出「切片▸(上下/前后/左右/任意)/色阶/显隐/详情」;右键切片 → 出「保存/保存为/导出/删除/色阶/显隐/详情」。
+
+- [ ] **Step 8: 提交**
+```bash
+git add src/app/main.cpp
+git commit -m "refactor(vtk): 删三浮层+分段切换,改挂三栏抽屉,接信号,中央改名VTK视图"
+```
+
+---
+
+## Task 8: dockState 版本 bump + 全屏按钮 [UI]
+
+**Files:**
+- Modify: `src/app/main.cpp`(dockState 键 1428-1442;全屏 connect)
+
+- [ ] **Step 1: bump dock 布局版本**
+
+`main.cpp:1430` 与 1440:把 `ui/dockState_v2` 两处改为 `ui/dockState_v3`(dock 名/结构已变,旧布局须丢弃回落默认排布;遵循 1428-1430 注释)。
+
+- [ ] **Step 2: 全屏切换实现**
+
+全屏 = 隐藏其余 dock,仅留目标 dock 充满 dock 区;再点还原。用 ADS `CDockWidget::toggleView(bool)`。加一个 lambda + 状态:
+```cpp
+// 全屏:隐藏其余 dock,仅留 target;再点还原。docks 列表见 hideDockTitleBars(733-740)。
+bool* vtkFs = new bool(false); // 或用 QObject property,避免裸 new:可挂到 window
+auto makeFullscreen = [dockManager](ads::CDockWidget* target, const QList& others, bool on){
+ for (ads::CDockWidget* d : others) d->toggleView(!on); // on→隐藏其余
+ Q_UNUSED(target);
+};
+```
+> 落地建议:用 `QToolButton::setCheckable(true)` 的全屏按钮 + `toggled(bool)` 切换;状态存按钮 checked,免裸指针。`others` = 除目标外的全部 dock(vtkDock 全屏时 others={leftDock,datasetDock,detailDock,rightDock,propDock};detailDock 全屏时 others={其余})。
+
+- [ ] **Step 3: 接全屏按钮**
+
+用 `findHeaderAction(box, Glyph::Fullscreen)`(main.cpp:1016-1020 的 helper)取到 VTK视图标题头与 数据详情头里的全屏按钮,connect 到 `makeFullscreen`。VTK视图头是 Step3(Task7) 的 `viewHeader`;数据详情头是 `detailDock` 的 `wrapWithHeader`(654-663)——给它加 `{{Glyph::Fullscreen,"全屏"}}` action。
+```cpp
+// 数据详情头加全屏 action(修改 654-663 的 wrapWithHeader 调用,加 actions 参数)。
+// 然后:
+auto* vtkFsBtn = findHeaderAction(viewHeader, geopro::app::Glyph::Fullscreen);
+auto* detFsBtn = findHeaderAction(detailHeaderBox, geopro::app::Glyph::Fullscreen);
+// 各自 setCheckable(true) + connect(&QToolButton::toggled, ... makeFullscreen ...)
+```
+
+- [ ] **Step 4: 编译**
+
+Run: `cmd.exe /c "chcp 65001 >nul && cd /d D:\Git\lanbingtech\geopro && .\build.bat rebuild"`
+Expected: 编译通过。
+
+- [ ] **Step 5: 用户实测清单**
+ - [ ] 点 VTK视图标题栏右侧全屏按钮 → VTK视图充满工作区(其余 dock 隐藏);再点 → 还原。
+ - [ ] 点 数据详情标题栏全屏按钮 → 同理。
+ - [ ] 首次启动(旧布局丢弃)dock 排布为默认,无错位。
+
+- [ ] **Step 6: 提交**
+```bash
+git add src/app/main.cpp
+git commit -m "feat(vtk): dockState bump v3 + VTK视图/数据详情 全屏按钮(隐藏其余dock)"
+```
+
+---
+
+## Task 9: 维度过滤接线——三栏数据集列表数据驱动 [UI+逻辑]
+
+把"勾选对象 → 取 ds → 按维度分三栏"接通,替换 `main.cpp:891-899` 的 "grid1" 假实现。
+
+**Files:**
+- Modify: `src/app/main.cpp`
+
+- [ ] **Step 1: 取勾选对象的 ds 行**
+
+现状:`checkedTmsChanged(QStringList tmIds)` → 假 "grid1"。改为:用 `WorkbenchNavController`/`repo_.loadRowsAsync` 对每个勾选 TM 取 `DsRow`,汇总。`nav` 已有 `datasetsLoaded(tmObjectId, rows, total, append)` 信号(WorkbenchNavController.hpp:52)。**最简路径**:复用 nav 的取数,但 nav 现按"单击对象"取数(selectObject),勾选多 TM 需逐个取并合并。
+
+实现:在 `checkedTmsChanged` 槽里,对每个 tmId 调 `nav.selectObject(tmId, 2)` 不合适(会刷左下列表)。改为直接调 repo:
+```cpp
+// 汇总所有勾选 TM 的 ds,按维度分三栏。projectRepo 是 IAsyncProjectRepository。
+QObject::connect(objectTree, &geopro::app::ObjectTreePanel::checkedTmsChanged, &window,
+ [&projectRepo, drawer, sceneCtrl, emptyState, &window](const QStringList& tmIds){
+ emptyState->setVisible(tmIds.isEmpty());
+ auto acc = std::make_shared>();
+ auto remaining = std::make_shared(tmIds.size());
+ if (tmIds.isEmpty()) {
+ drawer->col3D()->setDatasets({});
+ drawer->col2D()->setDatasets({});
+ drawer->colAnalysis()->setDatasets({});
+ sceneCtrl->setCheckedDatasets({});
+ return;
+ }
+ for (const QString& tm : tmIds) {
+ // classifyType=3, pageNo=1, 大 pageSize 取整树(同 WorkbenchNavController kFetchAllPageSize)
+ geopro::data::NavRequest* req = projectRepo.loadRowsAsync(
+ currentProjectIdStdString, tm.toStdString(), /*parentConfType*/2, /*classifyType*/3, 1, 100000);
+ req->onDone = [acc, remaining, drawer](const geopro::data::DsPage& page){
+ acc->insert(acc->end(), page.rows.begin(), page.rows.end());
+ if (--(*remaining) == 0) {
+ geopro::app::DimBuckets b = geopro::app::splitByDimension(*acc);
+ drawer->col3D()->setDatasets(b.dim3D);
+ drawer->col2D()->setDatasets(b.dim2D);
+ drawer->colAnalysis()->setDatasets(b.analysis);
+ }
+ };
+ // req->onFail 同样 --remaining 并在归零时刷新(避免一个失败卡死)。
+ }
+ });
+#include "app/DatasetDimension.hpp"
+```
+> 注:`NavRequest` 的回调字段真名见 `src/data/repo/IAsyncProjectRepository.hpp` / NavRequest 定义(onDone/onFail 或 done/failed)——落地按真实字段。`currentProjectIdStdString` 取当前项目 id(main.cpp 里已有项目 id 来源,搜 `currentProjectId`/`projectId`)。
+
+- [ ] **Step 2: 勾选数据集 → 渲染**
+
+三栏列表勾选 → `setCheckedDatasets`。汇总三栏勾选的 dsId:
+```cpp
+auto pushChecked = [drawer, sceneCtrl]{
+ QStringList ids;
+ // 收集三栏当前勾选(各栏暴露 checkedDatasetsChanged;此处也可各自直接连)
+ // 简化:各栏 checkedDatasetsChanged 直接 setCheckedDatasets(合并)。
+};
+QObject::connect(drawer->col3D(), &geopro::app::Column3DDataset::checkedDatasetsChanged,
+ sceneCtrl, &VtkSceneController::setCheckedDatasets);
+// 若需三栏合并,改为聚合后再 setCheckedDatasets;本期可先只接 col3D(3D 渲染主路径)。
+```
+> 本期渲染主路径是 3D 数据集(帘面/体素/地形),故先接 `col3D` 的勾选 → `setCheckedDatasets`。2D/分析渲染随各自维度后续完善。
+
+- [ ] **Step 3: 编译**
+
+Run: `cmd.exe /c "chcp 65001 >nul && cd /d D:\Git\lanbingtech\geopro && .\build.bat rebuild"`
+Expected: 编译通过。
+
+- [ ] **Step 4: 用户实测清单**
+ - [ ] 勾选含 ds 的对象 → 三维数据集栏列表出现 3D 维度 ds(样本 "剖面网格数据1" dd_section)。
+ - [ ] 勾选三维数据集栏里的 ds → 中央渲染帘面(原 grid1 路径效果)。
+ - [ ] 取消全部勾选 → 三栏列表清空、中央清场、引导层 emptyState 显示。
+ - [ ] (样本数据若无 2D/切片 ds,2D/分析栏为空属正常;可后续在 LocalSample 加样本演示。)
+
+- [ ] **Step 5: 提交**
+```bash
+git add src/app/main.cpp
+git commit -m "feat(vtk): 三栏数据集列表按维度过滤数据驱动(替换grid1假实现)"
+```
+
+---
+
+## Task 10: 全量回归 + 收尾 [逻辑]
+
+- [ ] **Step 1: 全量 build + ctest**
+
+Run: `cmd.exe /c "chcp 65001 >nul && cd /d D:\Git\lanbingtech\geopro && .\build.bat rebuild && .\build.bat test"`
+Expected: 编译通过;ctest 全绿(≥ 223/223,含新 DatasetDimension.* 2 项)。
+
+- [ ] **Step 2: 派 cpp-reviewer 审查本分支改动**
+
+对 `src/app/panels/columns/*`、`DatasetDimension.*`、`main.cpp` diff 跑 cpp-reviewer,修 CRITICAL/HIGH(重点:裸 new/生命周期、信号槽断连、ADS toggleView 还原正确性、role 常量硬编码)。
+
+- [ ] **Step 3: 对照 spec 验收**
+
+逐条核对 `2026-06-16-vtk-3d-three-column-refactor-design.md` §0 IN 项全部落地、OUT 项为 stub/禁用。
+
+- [ ] **Step 4: 用户最终实测**(完整走查实测清单 Task7/8/9)
+
+- [ ] **Step 5: 提交收尾(如有修改)**
+```bash
+git add -A && git commit -m "chore(vtk): 三栏重构 review 修复 + 回归"
+```
+
+---
+
+## Self-Review(写计划后自查)
+
+**Spec 覆盖:**
+- A1 三栏 → Task 3-7 ✅;单一 VTK视图/删切换/改名 → Task 7 ✅;三浮层收编 → Task 7 ✅;维度过滤列表 → Task 2+9 ✅;三维数据集 4 栏位 → Task 3 ✅;二维 地图/2D视图/自定义Z → Task 4 ✅;三维分析树+两右键菜单+切片接 SliceTool → Task 5+7 ✅;全屏 → Task 1+8 ✅;自定义Z绝对高程 → Task 4(spec 已记) ✅;dock 版本 bump → Task 8 ✅。
+- OUT 项(CRUD/色阶/底图/异常体/详情/任务)→ stub 信号(Task 5),不实现 ✅。
+
+**占位符扫描:** 已用真实 API/行号;少数"读真实 role 常量/NavRequest 字段名"是**有意指向源文件**(避免硬编码错值),非 TODO。
+
+**类型一致性:** `AxesMode/AxesUnit/ViewDir`(I3dSceneView.hpp) 跨 Task3/7 一致;`SliceAxis`(SlicePlaneMath.hpp) 跨 Task5/7 一致;`DsRow/DsPage/DimBuckets` 跨 Task2/3/9 一致;`splitByDimension` 签名 Task2 定义、Task9 使用一致。
+
+**风险:** Task 9 的多 TM 异步汇总 + NavRequest 回调字段名是最大不确定点(落地前先读 IAsyncProjectRepository.hpp 确认);全屏 toggleView 还原需保证 dock 顺序/可见性正确(Task8 用户实测把关)。