refactor(vtk): 清退2D拾取拖动死交互子系统 + 段图标上限钉为3 + 注释除锈

Fix 1 (死代码清退): E1 退役 per-ds 拖Z 2D 交互后, render/interact 层的另一半残留
接线为空。grep 确认零活调用方后, 移除整条 2D 交互通路:
- InteractionManager::setMode2D / pickStyle (decl+def, 唯一调用方 analysisModeChanged
  lambda 已删)
- PickInteractorStyle::lock2D_/setLock2D/isLock2D, onPick2D/onDrag2D/onDrag2DEnd/onWheel2D,
  worldPerPixelZ/kWheelStepPx/dragging2D_/lastDragY_ 及各事件处理器内的 lock2D_ 分支
仅删 2D 锁分支, 完整保留 3D 拾取/绕支点旋转/切片拖动/双击正视/滚轮推进/Esc 取消。

Fix 2 (spec §6): CategorySection 段头图标条上限由 setMaxIcons(acts.size()) 钉为
命名常量 kDefaultMaxIcons=3, 恢复计数溢出折叠分支(原恒=操作数→永不触发)。宽度挤压分支不动。

Fix 3 (注释除锈): 修正失真注释 —— categoryConfigs/CategorySpec → categoryCatalog()/
CategoryDescriptor; 删除 section("trajectory")返回 nullptr 的过期断言(C2 已构造该段);
VtkSceneController 注释引用已删的 gridCache_ → sectionGridCache_。
This commit is contained in:
gaozheng 2026-07-01 00:43:58 +08:00
parent e8df41b9f2
commit dc4671e09e
8 changed files with 15 additions and 119 deletions

View File

@ -106,8 +106,8 @@ void CategoryAnalysisTab::relayoutSections() {
}
void CategoryAnalysisTab::setBuckets(const CategoryBuckets& b) {
// splitByCategory 现按 categoryCatalog() 分桶(含 trajectory 第 5 桶,自然分发到轨迹段)。
// trajectory 段在 Task C2 才加入构造,当前 section("trajectory") 返回 nullptr → guard 跳过
// splitByCategory 现按 categoryCatalog() 分桶(含 trajectory 桶,自然分发到轨迹段)。
// 轨迹段已随 catalog 在构造时建出Task C2section() 命中存在的段;下方 guard 仍保平衡兜底
const auto& cat = geopro::data::categoryCatalog();
for (std::size_t i = 0; i < cat.size() && i < b.segments.size(); ++i) {
// voxel(三维体) 段数据来自 mock voxelTree(体/切片/异常),由调用方单独 section("voxel")->setDatasets

View File

@ -26,10 +26,10 @@ class CategoryAnalysisTab : public QWidget {
public:
explicit CategoryAnalysisTab(geopro::data::DatasetFieldDictionary* dict, QWidget* parent = nullptr);
void setBuckets(const CategoryBuckets& b); // 分发到 5 段(与 categoryConfigs 同序)
void setBuckets(const CategoryBuckets& b); // 分发到各大类段(与 categoryCatalog() 同序)
void setStructure(const std::vector<geopro::data::StructNode>& nodes); // 转发各段
void refreshArrayFilters(); // 装置枚举异步加载后,重填各段装置筛选下拉
CategorySection* section(const std::string& id) const; // 按 CategorySpec.id 取段
CategorySection* section(const std::string& id) const; // 按 CategoryDescriptor.id 取段
// ── 三维体生成后:自动勾选触发渲染 + 标题前等待动画(spinner)反馈(转发到含该 ds 的段)──
void setItemChecked(const QString& dsId, bool on); // 自动勾选(触发渲染)
void setItemBusy(const QString& dsId, bool busy); // 复选框↔等待 spinner 切换
@ -68,7 +68,7 @@ private:
void updatePlaceholderAndVisibility();
std::map<std::string, CategorySection*> sections_;
std::vector<CategorySection*> ordered_; // 按 categoryConfigs 顺序relayout 遍历用)
std::vector<CategorySection*> ordered_; // 按 categoryCatalog() 顺序relayout 遍历用)
QScrollArea* scroll_ = nullptr; // 外层滚动区scrollItemToTop 定位用)
QWidget* content_ = nullptr; // 滚动内容容器(坐标映射用)
QVBoxLayout* col_ = nullptr; // 段堆叠布局(末项=尾部弹簧)

View File

@ -30,6 +30,12 @@ namespace geopro::app {
using geopro::data::DsRow;
using geopro::data::DsTypeFields;
namespace {
// 段头图标条默认上限spec §6超过则末位收进「…」下拉。钉死为常量而非随操作数浮动
// 否则上限恒=操作数、计数溢出折叠分支永不触发(当前各段操作 ≤3对用户不可见但保证分支正确
constexpr int kDefaultMaxIcons = 3;
} // namespace
CategorySection::CategorySection(const geopro::data::CategoryDescriptor& desc,
geopro::data::DatasetFieldDictionary* dict, QWidget* parent)
: QWidget(parent), desc_(desc), dict_(dict) {
@ -92,7 +98,7 @@ CategorySection::CategorySection(const geopro::data::CategoryDescriptor& desc,
break;
}
}
iconBar_->setMaxIcons(static_cast<int>(acts.size())); // 段头操作数即可见上限(够宽全显,不够收进「…」)
iconBar_->setMaxIcons(kDefaultMaxIcons); // spec §6 默认上限 3够宽全显超数/不够宽收进「…」)
iconBar_->setActions(acts);
hl->addWidget(iconBar_);
root->addWidget(headerRow);

View File

@ -143,7 +143,7 @@ private:
// 缓存(按 dsId避免重复读盘/插值。
std::map<std::string, geopro::core::ColorScale> colorScaleCache_;
// 帘面源网格缓存:帘面重着色需 grid 重建 addCurtainloadSection 的 s.grid 不在 gridCache_)。
// 帘面源网格缓存:帘面重着色需 grid 重建 addCurtainloadSection 的 s.grid 缓存于此)。
std::map<std::string, geopro::core::Grid> sectionGridCache_;
std::map<std::string, data::VolumeGrid> volumeCache_;
// 三维体色阶缓存mock 体在 dsRepo_ 无条目,色阶随 loadVolume 一起交付并缓存于此。

View File

@ -293,27 +293,6 @@ void InteractionManager::closeAll() {
safeRender();
}
PickInteractorStyle* InteractionManager::pickStyle() const { return style_; }
void InteractionManager::setMode2D(bool is2D) {
// 进入二维分析:主动取消「三维前视图」的所有选中。否则残留的选中切片会让 onWheel 持续消费滚轮
// (二维下无法缩放),且切回三维仍残留高亮。清 selected_ + 切片高亮;再经 onSliceSelectionChanged("")
// 联动清三维分析列表选中行与异常高亮app 层接线)。与 VtkSceneView::setAnalysisMode2D 离开二维时
// clearMapLineSelection 清足迹选中相对称。
if (is2D) {
if (selected_ >= 0) {
selected_ = -1;
updateSelectionVisual(); // 清切片高亮(切回三维不残留选中)
}
if (onSliceSelectionChanged) onSliceSelectionChanged(std::string{});
}
// 切片属三维内容:二维分析隐藏(不销毁→切回零重建)、三维分析显示。
for (auto& s : slices_)
if (s) s->setVisible(!is2D);
if (style_) style_->setLock2D(is2D); // 二维=禁旋转、左键平移(仅平移+缩放)
// 不在此渲染:相机/地形/底图/维度显隐及统一 Render 由 VtkSceneView::setAnalysisMode2D 收尾。
}
void InteractionManager::flipView() {
if (!renderer_) return;
auto* cam = renderer_->GetActiveCamera();

View File

@ -68,9 +68,6 @@ public:
// 关闭并释放所有切片(切到二维 / 清场 / 体素重建前调)。
void closeAll();
// 切二维分析(is2D=true)/三维分析(false):翻所有切片显隐(不销毁,切回零重建) + 锁/解锁交互样式
// (二维=仅平移+缩放、禁旋转)。地形/底图/相机由 VtkSceneView::setAnalysisMode2D 处理,此处不渲染。
void setMode2D(bool is2D);
// 关闭并释放某体下的所有切片(该体移除/重建时;不动其它体的切片)。
void closeSlicesOfVolume(const std::string& volumeDsId);
@ -126,10 +123,6 @@ public:
void installStyle();
void uninstallStyle();
// 暴露交互样式:供 app 层注入二维分析 B 期的足迹拾取/Z 拖动回调onPick2D/onDrag2D/onDrag2DEnd
// 定义在 .cpp此处 PickInteractorStyle 仅前置声明vtkSmartPointer→裸指针下转需完整类型
PickInteractorStyle* pickStyle() const;
private:
// 拾取回调实现PickInteractorStyle 注入)。
void onPicked(const Vec3& worldPoint); // 选中所在切片 + 焦点

View File

@ -1,7 +1,6 @@
#include "interact/PickInteractorStyle.hpp"
#include <chrono>
#include <cmath>
#include <cstring>
#include <vtkCallbackCommand.h>
@ -50,22 +49,6 @@ bool PickInteractorStyle::pickWorld(Vec3& out) {
void PickInteractorStyle::OnLeftButtonDown() {
auto* iren = this->GetInteractor();
// 二维分析:左键命中足迹→进入高程 Z 拖动(B 期);否则=平移(等同中键),禁旋转。抬键由 OnLeftButtonUp 收尾。
if (lock2D_) {
const int* p = iren ? iren->GetEventPosition() : nullptr;
if (p) this->FindPokedRenderer(p[0], p[1]);
if (!this->CurrentRenderer) return;
const bool additive = iren && iren->GetControlKey(); // Ctrl=多选
if (onPick2D && p && onPick2D(p[0], p[1], additive)) { // 命中足迹 → Z 拖动
dragging2D_ = true;
lastDragY_ = p[1];
this->GrabFocus(this->EventCallbackCommand);
return;
}
this->GrabFocus(this->EventCallbackCommand); // 未命中 → 平移
this->StartPan();
return;
}
Vec3 world;
const bool hit = pickWorld(world); // 仍用于取选中所需世界点(onPick)
// 命中切片【精确判定】:光标射线穿过某切片真实矩形内才算(不靠带容差的 picker 点)。
@ -107,7 +90,6 @@ void PickInteractorStyle::OnLeftButtonDown() {
}
void PickInteractorStyle::Rotate() {
if (lock2D_) return; // 二维分析禁旋转(仅平移+缩放)
if (!this->CurrentRenderer || !hasRotatePivot_) {
Superclass::Rotate(); // 无支点 → 默认绕焦点旋转
return;
@ -154,49 +136,14 @@ void PickInteractorStyle::Rotate() {
rwi->Render();
}
double PickInteractorStyle::worldPerPixelZ() const {
if (!this->CurrentRenderer) return 1.0;
auto* cam = this->CurrentRenderer->GetActiveCamera();
auto* rw = this->CurrentRenderer->GetRenderWindow();
if (!cam || !rw) return 1.0;
const int* sz = rw->GetSize();
const double h = (sz && sz[1] > 0) ? static_cast<double>(sz[1]) : 800.0;
if (cam->GetParallelProjection())
return 2.0 * cam->GetParallelScale() / h; // 平行投影:可见世界高度=2*parallelScale
// 透视:可见世界高度 = 2*d*tan(viewAngle/2)d=相机到焦点距离。
double pos[3], fp[3];
cam->GetPosition(pos);
cam->GetFocalPoint(fp);
const double dx = pos[0] - fp[0], dy = pos[1] - fp[1], dz = pos[2] - fp[2];
const double d = std::sqrt(dx * dx + dy * dy + dz * dz);
const double va = vtkMath::RadiansFromDegrees(cam->GetViewAngle());
return 2.0 * d * std::tan(va * 0.5) / h;
}
void PickInteractorStyle::OnMouseMove() {
if (dragging2D_) { // B 期:竖向拖动 → 选中足迹 Z 增量(仅改 Z)。鼠标上移(y 增)→ 抬高。
auto* rwi = this->Interactor;
if (rwi) {
const int y = rwi->GetEventPosition()[1];
const int dyPix = y - lastDragY_;
lastDragY_ = y;
if (dyPix != 0 && onDrag2D) onDrag2D(worldPerPixelZ() * dyPix);
}
return; // 不走基类(不平移/不旋转)
}
Superclass::OnMouseMove();
}
void PickInteractorStyle::OnLeftButtonUp() {
if (dragging2D_) { // 结束 Z 拖动
dragging2D_ = false;
if (this->Interactor) this->ReleaseFocus();
if (onDrag2DEnd) onDrag2DEnd();
return;
}
// 单击(抬键位移<阈值=非拖动)且按下未命中切片 → 取消选中(点空/点体;体 PickableOff 故点体也 hit=false
// 拖空白旋转:抬键位移大 → 不取消,保留"绕选中切片旋转"。Esc 仍是完全拉近时的兜底。
if (!lock2D_ && !downHitSlice_ && onDeselect) {
if (!downHitSlice_ && onDeselect) {
auto* iren = this->GetInteractor();
const int* up = iren ? iren->GetEventPosition() : nullptr;
if (up) {
@ -208,19 +155,13 @@ void PickInteractorStyle::OnLeftButtonUp() {
Superclass::OnLeftButtonUp(); // 平移/旋转/缩放等由基类按 State 收尾
}
namespace {
constexpr double kWheelStepPx = 24.0; // 滚轮一格升降 ≈ 拖动 24 像素的世界 Z 量(与拖动手感一致)
}
void PickInteractorStyle::OnMouseWheelForward() {
// 二维分析有选中足迹 → 滚轮抬升其高程(消费滚轮);否则按切片推进 / 默认缩放。
if (lock2D_ && onWheel2D && onWheel2D(worldPerPixelZ() * kWheelStepPx)) return;
// 有选中切片 → 沿法向推进(消费滚轮);否则默认缩放。
if (onWheelStep && onWheelStep(+1)) return; // 有选中切片 → 推进,消费滚轮
Superclass::OnMouseWheelForward(); // 否则默认缩放
}
void PickInteractorStyle::OnMouseWheelBackward() {
if (lock2D_ && onWheel2D && onWheel2D(-worldPerPixelZ() * kWheelStepPx)) return;
if (onWheelStep && onWheelStep(-1)) return;
Superclass::OnMouseWheelBackward();
}

View File

@ -37,20 +37,6 @@ public:
// 点帘面/其它非切片物/边界外 → 返回 false → 单击即取消选中。
std::function<bool()> hitTestSlice;
// 二维分析锁:开 → 左键拖动改为平移、禁旋转(仅平移+缩放);关 → 恢复三维拾取/旋转交互。
void setLock2D(bool on) { lock2D_ = on; }
bool isLock2D() const { return lock2D_; }
// ── 二维分析 B 期:选中足迹沿高程 Z 拖动 ──(仅 lock2D 下生效;回调由 app 层注入)
// onPick2D左键按下时在(x,y)拾取足迹(additive=Ctrl 多选),返回是否有选中→有则进入 Z 拖动、否则平移。
// onDrag2D拖动中把竖向像素换算成的世界 Z 增量(本类按相机算)交给 app 施加到选中足迹(仅改 Z)。
// onDrag2DEnd松开结束拖动(供 app 收起高程读数浮层)。
std::function<bool(int x, int y, bool additive)> onPick2D;
std::function<void(double worldDz)> onDrag2D;
std::function<void()> onDrag2DEnd;
// 滚轮升降:有选中足迹时滚轮改其高程 Z(本类按相机算 worldDz)app 施加并返回是否消费(无选中→false→默认缩放)。
std::function<bool(double worldDz)> onWheel2D;
void OnMouseMove() override;
void OnLeftButtonUp() override;
@ -68,8 +54,6 @@ protected:
private:
// 在当前鼠标位置拾取世界点;命中返回 true 并填 out。
bool pickWorld(Vec3& out);
// 当前相机下:竖向一屏幕像素对应的世界 Z米/像素),用于把拖动像素换算成 Z 增量。
double worldPerPixelZ() const;
// 手动双击判定QVTK+Windows 下 vtkRenderWindowInteractor::GetRepeatCount() 不可靠(评审 M5
// 记上次左键按下时刻+屏幕位置,两次按下间隔 < kDoubleClickMs 且位置相近视为双击。
@ -84,13 +68,6 @@ private:
// 选中切片=其中心;否则=光标射线穿过的体中段点。无则 hasRotatePivot_=false→默认绕焦点。
Vec3 rotatePivot_{};
bool hasRotatePivot_ = false;
// 二维分析模式:左键=平移、禁旋转(仅平移+缩放)。由 InteractionManager 在切 tab 时设。
bool lock2D_ = false;
// B 期足迹 Z 拖动状态:左键命中足迹时进入,记上次鼠标 y 以算增量。
bool dragging2D_ = false;
int lastDragY_ = 0;
};
} // namespace geopro::render::interact